SponsoredHosinger Special Offer

WP+ WordPress Plugin Development

Build WordPress plugins from scratch: actions and filters, shortcodes, settings pages, custom post types, meta boxes, AJAX, REST endpoints, custom tables, cron and blocks.

Meta Boxes and Post Meta

WordPress Plugin Development Lesson 16 of 27 ~5 min read

Overview

A meta box is a panel on the post edit screen where your plugin collects extra fields — an ISBN, a page count — which are saved as post meta with update_post_meta() when the post is saved.

Step 1: add the meta box

<?php
function gdc_books_add_meta_boxes() {
    add_meta_box(
        'gdc_books_details',                       // ID.
        __( 'Book Details', 'gdc-books' ),         // Title.
        'gdc_books_render_details_box',            // Render callback.
        'gdc_book',                                // Post type.
        'side',                                    // Context: normal, side or advanced.
        'high'                                     // Priority within the context.
    );
}
add_action( 'add_meta_boxes', 'gdc_books_add_meta_boxes' );

Step 2: render the fields

<?php
function gdc_books_render_details_box( WP_Post $post ) {
    wp_nonce_field( 'gdc_books_save_details', 'gdc_books_details_nonce' );

    $isbn  = get_post_meta( $post->ID, '_gdc_books_isbn', true );
    $pages = get_post_meta( $post->ID, '_gdc_books_pages', true );
    ?>
    <p>
        <label for="gdc_books_isbn"><?php esc_html_e( 'ISBN', 'gdc-books' ); ?></label>
        <input type="text" id="gdc_books_isbn" name="gdc_books_isbn"
               value="<?php echo esc_attr( $isbn ); ?>" class="widefat">
    </p>
    <p>
        <label for="gdc_books_pages"><?php esc_html_e( 'Pages', 'gdc-books' ); ?></label>
        <input type="number" id="gdc_books_pages" name="gdc_books_pages" min="1"
               value="<?php echo esc_attr( $pages ); ?>" class="widefat">
    </p>
    <?php
}

Step 3: save securely

<?php
function gdc_books_save_details( $post_id ) {
    // 1. Our nonce: present and valid.
    if ( ! isset( $_POST['gdc_books_details_nonce'] )
        || ! wp_verify_nonce( sanitize_key( $_POST['gdc_books_details_nonce'] ), 'gdc_books_save_details' ) ) {
        return;
    }

    // 2. Autosaves do not submit our fields — skip them.
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // 3. Permission for THIS post.
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    // 4. Sanitise and save — or delete when emptied.
    if ( isset( $_POST['gdc_books_isbn'] ) ) {
        $isbn = preg_replace( '/[^0-9X]/', '', strtoupper( sanitize_text_field( wp_unslash( $_POST['gdc_books_isbn'] ) ) ) );
        if ( '' === $isbn ) {
            delete_post_meta( $post_id, '_gdc_books_isbn' );
        } else {
            update_post_meta( $post_id, '_gdc_books_isbn', $isbn );
        }
    }

    if ( isset( $_POST['gdc_books_pages'] ) ) {
        $pages = absint( $_POST['gdc_books_pages'] );
        $pages ? update_post_meta( $post_id, '_gdc_books_pages', $pages ) : delete_post_meta( $post_id, '_gdc_books_pages' );
    }

    do_action( 'gdc_books_details_saved', $post_id );
}
// save_post_{post_type}: only fires for books.
add_action( 'save_post_gdc_book', 'gdc_books_save_details' );

Each check guards against something real: the nonce against forged requests, the autosave check against wiping fields every 60 seconds, and the capability check against users editing posts that are not theirs.

Why the underscore?

Meta keys starting with _ are protected: hidden from the generic Custom Fields panel, so users cannot edit or break them there. Use them for any field your own UI manages.

Making meta work with the block editor and REST API

Register your meta keys so they are typed, sanitised everywhere, and available to the REST API and to blocks:

<?php
function gdc_books_register_meta() {
    register_post_meta(
        'gdc_book',
        '_gdc_books_pages',
        array(
            'type'              => 'integer',
            'single'            => true,
            'sanitize_callback' => 'absint',
            'show_in_rest'      => true,
            'auth_callback'     => function ( $allowed, $meta_key, $post_id ) {
                return current_user_can( 'edit_post', $post_id );
            },
        )
    );
}
add_action( 'init', 'gdc_books_register_meta' );

Practice

  • Add the Book Details meta box, save an ISBN and page count, and reload the editor to see them.
  • Clear the ISBN, save, and confirm the meta row is deleted rather than stored empty.
  • Remove the autosave check, wait for an autosave, and observe the fields disappear — then restore it.
Create a free account to save which lessons you've finished. Save my progress