arrow_back
Back

WordPress meta boxes: add_meta_box, save_post, and post meta

Andrew Dorokhov Andrew Dorokhov schedule 3 min read
menu_book Table of Contents

To add a new meta box:

function fairways_upload_dates_box() {
    $screens = [ 'post' ];
    foreach ( $screens as $screen ) {
        add_meta_box(
            'fairways_upload_dates_box',
            'Availability dates',
            'fairways_upload_dates_box_html',
            $screen
        );
    }
}
add_action( 'add_meta_boxes', 'fairways_upload_dates_box' );

function fairways_upload_dates_box_html( $post ) {
    echo "Your form is here";
}

Parameters of add_meta_box()

add_meta_box( $id, $title, $callback, $post_type, $context, $priority, $args );
Parameter * Description
$id Required The HTML id attribute that will be assigned to the meta box block.
$title Required The heading of the box.
$callback Required The function that fills the box. Passed as a string with the function name (or a callable).
$post_type Required The post type to show the box for.
$context Which part of the page to place the box in (normal, side, advanced). Default — advanced.
$priority The priority: the higher it is, the closer to the top of the page the box appears (high, core, default, low). Default — default.
$args Arguments passed to the callback function.

Example:

function add_meta_boxes() {
    add_meta_box( 'settings', 'Settings', 'print_settings_box', 'post', 'normal', 'high' );
}

add_action( 'add_meta_boxes', 'add_meta_boxes' );

Printing the fields

The callback simply echoes the HTML — these are ordinary input elements. The only addition is a nonce field, which is needed to verify the request when the post is saved.

function print_settings_box() {

    wp_nonce_field( plugin_basename( __FILE__ ), 'myplugin_noncename' );

    echo '<label for="myplugin_new_field">' . __( "Description for this field", 'myplugin_textdomain' ) . '</label> ';
    echo '<input type="text" id="myplugin_new_field" name="myplugin_new_field" value="whatever" size="25" />';
}

Saving the data

The values are saved on the save_post hook. This hook fires in a lot of situations, so all four checks below matter:

add_action( 'save_post', 'myplugin_save_postdata' );

function myplugin_save_postdata( $post_id ) {

    // Make sure the field is set.
    if ( ! isset( $_POST['myplugin_new_field'] ) )
        return;

    // Verify the nonce of our page, because save_post can be triggered from somewhere else.
    if ( ! wp_verify_nonce( $_POST['myplugin_noncename'], plugin_basename( __FILE__ ) ) )
        return;

    // Do nothing on autosave.
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
        return;

    // Check the user permissions.
    if ( ! current_user_can( 'edit_post', $post_id ) )
        return;

    // Everything is fine. Sanitize the value of the input.
    $my_data = sanitize_text_field( $_POST['myplugin_new_field'] );

    // Update the data in the database.
    update_post_meta( $post_id, '_my_meta_value_key', $my_data );
}

Reading the value in a template

$client_position = get_post_meta( get_the_ID(), 'team-member-position', true );

The third argument makes the function return a single value instead of an array.

Sometimes a post type needs a second image alongside the standard featured image. The approach is a meta box that stores an attachment ID and reuses the media library UI. The original write-up is open_in_new here .

Registering the box:

add_action( 'add_meta_boxes', 'add_banner_image_metabox' );

function add_banner_image_metabox() {
    add_meta_box( 'bottom-image', 'Bottom image', 'print_bottom_image_metabox', 'success-story', 'side', 'low' );
}

Rendering it:

function print_bottom_image_metabox( $post ) {

    global $content_width, $_wp_additional_image_sizes;

    $image_id = get_post_meta( $post->ID, 'bottom_image_id', true );

    $old_content_width = $content_width;

    $content_width = 254;

    if ( $image_id && get_post( $image_id ) ) {
        if ( ! isset( $_wp_additional_image_sizes['post-thumbnail'] ) ) {
            $thumbnail_html = wp_get_attachment_image( $image_id, array( $content_width, $content_width ) );
        } else {
            $thumbnail_html = wp_get_attachment_image( $image_id, 'post-thumbnail' );
        }
        if ( ! empty( $thumbnail_html ) ) {
            $content = $thumbnail_html;
            $content .= '<p class="hide-if-no-js"><a href="javascript:;" id="remove_listing_image_button" >' . esc_html__( 'Remove listing image', 'text-domain' ) . '</a></p>';
            $content .= '<input type="hidden" id="upload_listing_image" name="_listing_cover_image" value="' . esc_attr( $image_id ) . '" />';
        }
        $content_width = $old_content_width;
    } else {
        $content = '<img src="" style="width:' . esc_attr( $content_width ) . 'px;height:auto;border:0;display:none;" />';
        $content .= '<p class="hide-if-no-js"><a title="' . esc_attr__( 'Set listing image', 'text-domain' ) . '" href="javascript:;" id="upload_listing_image_button" data-uploader_title="' . esc_attr__( 'Choose an image', 'text-domain' ) . '" data-uploader_button_text="' . esc_attr__( 'Set listing image', 'text-domain' ) . '">' . esc_html__( 'Set listing image', 'text-domain' ) . '</a></p>';
        $content .= '<input type="hidden" id="upload_listing_image" name="_listing_cover_image" value="" />';
    }

    echo $content;
}
code

Need Help with Development?

Happy to help — reach out via the contacts or go straight to my Upwork profile.

work View Upwork Profile arrow_forward
Next Article

WordPress taxonomies: register_taxonomy, terms, categories, and tags

arrow_forward