Building a secure custom meta box with nonce verification
Meta boxes let you attach structured data to posts — event dates, custom prices, video URLs, or any other field — without installing a full custom fields plugin. The WordPress meta box API has been available since version 2.5 and gives you complete control over how the data is displayed in the editor and saved to the database.
Problem: How do you add a custom data field to the post edit screen, render it correctly, and save its value securely?
Solution: The example below registers a meta box on the standard post edit screen, renders an input field with nonce verification, and saves the value securely when the post is saved.
<?php
// 1. Register the meta box
add_action( 'add_meta_boxes', 'register_my_meta_box' );
function register_my_meta_box() {
add_meta_box(
'my_custom_meta', // unique ID
__( 'Extra Details', 'domain' ), // title shown in the editor
'render_my_meta_box', // callback that outputs the HTML
'post', // post type
'normal', // position: normal | side | advanced
'high' // priority: high | default | low
);
}
// 2. Render the meta box HTML
function render_my_meta_box( WP_Post $post ) {
wp_nonce_field( 'save_my_meta_box', 'my_meta_nonce' );
$value = get_post_meta( $post->ID, '_my_custom_field', true );
?>
<label for="my_custom_field"><?php esc_html_e( 'Custom field:', 'domain' ); ?></label>
<input type="text"
id="my_custom_field"
name="my_custom_field"
value="<?php echo esc_attr( $value ); ?>"
style="width: 100%;">
<?php
}
// 3. Save the meta box data
add_action( 'save_post', 'save_my_meta_box' );
function save_my_meta_box( int $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( empty( $_POST['my_meta_nonce'] ) ) return;
if ( ! wp_verify_nonce( $_POST['my_meta_nonce'], 'save_my_meta_box' ) ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( isset( $_POST['my_custom_field'] ) ) {
update_post_meta(
$post_id,
'_my_custom_field',
sanitize_text_field( $_POST['my_custom_field'] )
);
}
}
NOTE: Always verify the nonce and check capabilities in save_post. Without these checks, anyone who can trigger a post save could inject arbitrary data. Use sanitize_text_field() for plain text, esc_url_raw() for URLs, and absint() for integers. When outputting, always escape with esc_attr(), esc_html(), or esc_url().