add_meta_box()WP 2.5.0

Adds a meta box to one or more screens.

No Hooks.

Return

null. Nothing (null).

Usage

add_meta_box( $id, $title, $callback, $screen, $context, $priority, $callback_args );
$id(string) (required)
Meta box ID (used in the 'id' attribute for the meta box).
$title(string) (required)
Title of the meta box.
$callback(callable) (required)
Function that fills the box with the desired content. The function should echo its output.
$screen(string|array|WP_Screen)
The screen or screens on which to show the box (such as a post type, 'link', or 'comment'). Accepts a single screen ID, WP_Screen object, or array of screen IDs. If you have used add_menu_page() or add_submenu_page() to create a new screen (and hence screen_id), make sure your menu slug conforms to the limits of sanitize_key() otherwise the 'screen' menu may not correctly render on your page.
Default: current screen
$context(string)
The context within the screen where the box should display. Available contexts vary from screen to screen. Post edit screen contexts include 'normal', 'side', and 'advanced'. Comments screen contexts include 'normal' and 'side'. Menus meta boxes (accordion sections) all use the 'side' context. Global default is 'advanced'.
Default: 'advanced'
$priority(string)
The priority within the context where the box should show. Accepts 'high', 'core', 'default', or 'low'.
Default: 'default'
$callback_args(array)
Data that should be set as the $args property of the box array (which is the second parameter passed to your callback).
Default: null

Examples

0

#1 Metabox for posts and pages

An example of adding a custom block to the edit/create pages of posts and static pages:

// Add blocks to the main column on the post/page pages
add_action( 'add_meta_boxes', 'myplugin_add_custom_box' );

// Saving data when the post is saved
add_action( 'save_post', 'myplugin_save_postdata' );

function myplugin_add_custom_box(){
	$screens = array( 'post', 'page' );
	add_meta_box( 'myplugin_sectionid', 'Meta block name', 'myplugin_meta_box_callback', $screens );
}

// HTML code of the block
function myplugin_meta_box_callback( $post, $meta ){
	$screens = $meta['args'];

	// Use nonce for verification
	wp_nonce_field( plugin_basename(__FILE__), 'myplugin_noncename' );

	// field value
	$value = get_post_meta( $post->ID, 'my_meta_key', 1 );

	// Form fields for entering data
	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="'. $value .'" size="25" />';
}

function myplugin_save_postdata( $post_id ) {

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

	// check the nonce of our page, because save_post can be called from another location.
	if ( ! wp_verify_nonce( $_POST['myplugin_noncename'], plugin_basename(__FILE__) ) )
		return;

	// if this is autosave do nothing
	if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) 
		return;

	// check user permission
	if( ! current_user_can( 'edit_post', $post_id ) )
		return;

	// Everything is OK. Now, we need to find and save the data
	// clear the value of the input field.
	$my_data = sanitize_text_field( $_POST['myplugin_new_field'] );

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

#2 New accordion tab (metabox in the accordion menu)

This example shows how to add an additional accordion element to the WordPress menu settings page.

<?php
add_action( 'admin_head-nav-menus.php', 'register_my_meta_box_accordion_nav_menus' );

function register_my_meta_box_accordion_nav_menus() {
	add_meta_box( 'my-custom-meta-box', 'My metabox', 'render_my_meta_box_accordion_nav_menus', 'nav-menus', 'side' );
}

function render_my_meta_box_accordion_nav_menus() {
	?>
	<div class="my-custom-meta-box" id="my-custom-meta-box">
		Metabox content
	</div>
	<?php
}

We get it:

0

#3 Accordion tab with content

Finished code that adds a new item to the navigation menu (in the accordion) and fills it, allowing you to select a media library item (files) and add it into the menu:

<?php

/**
 * Registering and adding a tab in the accordion to create a menu
 *
 * @link https://gist.github.com/nikolov-tmw/8698598
 */
add_action( 'admin_head-nav-menus.php', 'register_menu_files_metabox' );

/**
 * Replaces the link to the media page with a link to the media file itself
 *
 * @param string $link link to the media file page
 * @param string $post_id Media file ID
 *
 * @return string
 */
add_filter( 'attachment_link', 'change_attachment_link', 1000, 2 );

function register_menu_files_metabox() {
	add_meta_box('menu-media-files-metabox', 'Files', 'render_menu_files_metabox', 'nav-menus', 'side', 'default' );
}

/**
 * Displaying the "Files" metabox
 *
 * @param string $object Not used.
 * @param array $args Parameters and arguments.
 */
function render_menu_files_metabox( $object, $args ) {
	global $nav_menu_selected_id;
	$files_ext = [ 'xlsx', 'pdf', 'zip' ];

	$my_items = get_posts( [
		'numberposts'    => - 1,
		'post_type'      => 'attachment',
		'post_mime_type' => get_mime_files_extension( $files_ext ),
	] );

	$walker       = new Walker_Nav_Menu_Checklist();
	$removed_args = [
		'action',
		'customlink-tab',
		'edit-menu-item',
		'menu-item',
		'page-tab',
		'_wpnonce',
	]; ?>
	<div id="tab-files-div">
	<div id="tabs-panel-list-files" class="tabs-panel tabs-panel-active">
		<p>Only files with the extension <b><?php echo implode( ', ', $files_ext ); ?></b>.</p>

		<ul id="tab-files-checklist-pop" class="categorychecklist form-no-clear">
			<?php echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $my_items ), 0, (object) [ 'walker' => $walker ] ); ?>
		</ul>

		<p class="button-controls">
			<span class="list-controls">
				<a href="<?php
				echo esc_url( add_query_arg(
					[
						'list-files' => 'all',
						'selectall'  => 1,
					],
					remove_query_arg( $removed_args )
				) );
				?>#menu-media-files-metabox" class="select-all"><?php _e( 'Select All' ); ?></a>
			</span>

			<span class="add-to-menu">
				<input type="submit"<?php wp_nav_menu_disabled_check( $nav_menu_selected_id ); ?>
					   class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>"
					   name="add-tab-files-menu-item" id="submit-tab-files-div"/>
				<span class="spinner"></span>
			</span>
		</p>
	</div>
	<?php
}

/**
 * Returns mime types of file extensions
 *
 * @param array $files_ext file extensions
 *
 * @return array
 */
function get_mime_files_extension( $files_ext ) {

	// Gets the list of allowed mime types.
	$mimes = get_allowed_mime_types();

	// Search for mime type in the extension array.
	$need_mimes = [];
	foreach ( $files_ext as $file_ext ) {
		foreach ( $mimes as $type => $mime ) {
			if ( false !== strpos( $type, $file_ext ) ) {
				$need_mimes[] = $mime;
			}
		}
	}

	return $need_mimes;
}

function change_attachment_link( $link, $post_id ) {
	return wp_get_attachment_url( $post_id );
}

We get it:

New accordion tab with media file addition

Note: The base of code is taken from the file [custom-menu-panel.php] (https://gist.github.com/nikolov-tmw/8698598), found on github, which implements adding custom objects to the list.

0

#4 PHP class

This example shows how to add an extra block in OOP-style:

/**
 * Call the class on the post editing page.
 */
function call_someClass() {
	new someClass();
}

if ( is_admin() ) {
	add_action( 'load-post.php', 'call_someClass' );
	add_action( 'load-post-new.php', 'call_someClass' );
}

class someClass {

	/**
	 * Set hooks at the moment of class initialization.
	 */
	public function __construct() {
		add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
		add_action( 'save_post', array( $this, 'save' ) );
	}

	/**
	 * Adding an extra block.
	 */
	public function add_meta_box( $post_type ){

			// Set the types of posts to which the block will be added
			$post_types = array('post', 'page');

			if ( in_array( $post_type, $post_types )) {
				add_meta_box(
					'some_meta_box_name',
					__( 'Some Meta Box Headline', 'myplugin_textdomain' ),
					array( $this, 'render_meta_box_content' ),
					$post_type,
					'advanced',
					'high',
				);
			}
	}

	/**
	 * Save the data when you save the post.
	 *
	 * @param int $post_id The ID of the post that is saved.
	 */
	public function save( $post_id ) {

		/*
		 * We need to do a check to make sure that the request came from our page,
		 * because save_post can be called anywhere else.
		 */

		// check if nonce is set.
		if ( ! isset( $_POST['myplugin_inner_custom_box_nonce'] ) )
			return $post_id;

		$nonce = $_POST['myplugin_inner_custom_box_nonce'];

		// We check if nonce is correct.
		if ( ! wp_verify_nonce( $nonce, 'myplugin_inner_custom_box' ) )
			return $post_id;

		// If this is autosave do nothing.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
			return $post_id;

		// Check user rights.
		if ( 'page' == $_POST['post_type'] ) {

			if ( ! current_user_can( 'edit_page', $post_id ) )
				return $post_id;

		} else {

			if ( ! current_user_can( 'edit_post', $post_id ) )
				return $post_id;
		}

		// OK, everything is clear, you can save the data.

		// Clean up the input field.
		$mydata = sanitize_text_field( $_POST['myplugin_new_field'] );

		// Updating the data.
		update_post_meta( $post_id, '_my_meta_value_key', $mydata );
	}

	/**
	 * Auxiliary unit code.
	 *
	 * @param WP_Post $post Post object.
	 */
	public function render_meta_box_content( $post ) {

		// Add a nonce field to be checked when saving.
		wp_nonce_field( 'myplugin_inner_custom_box', 'myplugin_inner_custom_box_nonce' );

		// Retrieve existing data from the database.
		$value = get_post_meta( $post->ID, '_my_meta_value_key', true );

		// Output the form fields using the obtained data.
		echo '<label for="myplugin_new_field">';
		echo __( 'Description for this field', 'myplugin_textdomain' );
		echo '</label> ';
		echo '<input type="text" id="myplugin_new_field" name="myplugin_new_field"';
		echo ' value="' . esc_attr( $value ) . '" size="25" />';
	}

}
0

#5 Using the $callback_args parameter

The first parameter passes post data (array $_POST).

The second has an array of $callback_args arguments.

// This function adds a Block that passes additional parameters to the 
// `callback` my_metabox_callback() function.
function add_my_meta_box() {
	$var1 = "this";
	$var2 = "that";
	$callback_args = array( 'foo'=>$var1,'bar'=>$var2 );

	add_meta_box( 'metabox_id', 'Metabox Title', 'my_metabox_callback', 'page', 'normal', 'low', $callback_args );
}

// $post is an object containing data from the global array $_POST
// $metabox is an array of metabox_id, title, callback
// and the new passed arguments in the $callback_args parameter
function my_metabox_callback( $post, $metabox ){

	echo 'Last Modified: ' . $post->post_modified; // the time the post was last modified
	echo $metabox['args']['foo']; // once
	echo $metabox['args']['bar']; // two
	echo get_post_meta( $post->ID, 'my_custom_field', true ); // arbitrary field value

}
0

#6 Using the anonymous function

You can pass an anonymous function as a callback function to the $callback_args parameter, for example:

add_action( 'add_meta_boxes', 'add_custom_meatbox' );
function add_custom_meatbox(){
	$screens = array( 'post', 'page' );
	add_meta_box( 'wpsb', __( 'Metabox title', 'my-plugin' ), function() {
		_e( 'Metabox content', 'my-plugin' );
	}, $screens );
}

It's a little more convenient if you just need to get something out in the metabox.

0

#7 Display the metabox in an custom place

This example shows how to output the metabox in an custom place: right after the header field. To do this, set your own value for the $context parameter, and then output it with the edit_form_after_title hook. You can see the whole map of hooks on the post editing page here.

add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', null, 'my_custom_context', 'high' );

add_action( 'edit_form_after_title', 'show_custom_meatbox' );

function show_custom_meatbox( $post ) {
	do_meta_boxes( null, 'my_custom_context', $post );
}

Notes

  • Global. Array. $wp_meta_boxes

Changelog

Since 2.5.0 Introduced.
Since 4.4.0 The $screen parameter now accepts an array of screen IDs.

add_meta_box() code WP 6.5.2

function add_meta_box( $id, $title, $callback, $screen = null, $context = 'advanced', $priority = 'default', $callback_args = null ) {
	global $wp_meta_boxes;

	if ( empty( $screen ) ) {
		$screen = get_current_screen();
	} elseif ( is_string( $screen ) ) {
		$screen = convert_to_screen( $screen );
	} elseif ( is_array( $screen ) ) {
		foreach ( $screen as $single_screen ) {
			add_meta_box( $id, $title, $callback, $single_screen, $context, $priority, $callback_args );
		}
	}

	if ( ! isset( $screen->id ) ) {
		return;
	}

	$page = $screen->id;

	if ( ! isset( $wp_meta_boxes ) ) {
		$wp_meta_boxes = array();
	}
	if ( ! isset( $wp_meta_boxes[ $page ] ) ) {
		$wp_meta_boxes[ $page ] = array();
	}
	if ( ! isset( $wp_meta_boxes[ $page ][ $context ] ) ) {
		$wp_meta_boxes[ $page ][ $context ] = array();
	}

	foreach ( array_keys( $wp_meta_boxes[ $page ] ) as $a_context ) {
		foreach ( array( 'high', 'core', 'default', 'low' ) as $a_priority ) {
			if ( ! isset( $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ] ) ) {
				continue;
			}

			// If a core box was previously removed, don't add.
			if ( ( 'core' === $priority || 'sorted' === $priority )
				&& false === $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]
			) {
				return;
			}

			// If a core box was previously added by a plugin, don't add.
			if ( 'core' === $priority ) {
				/*
				 * If the box was added with default priority, give it core priority
				 * to maintain sort order.
				 */
				if ( 'default' === $a_priority ) {
					$wp_meta_boxes[ $page ][ $a_context ]['core'][ $id ] = $wp_meta_boxes[ $page ][ $a_context ]['default'][ $id ];
					unset( $wp_meta_boxes[ $page ][ $a_context ]['default'][ $id ] );
				}
				return;
			}

			// If no priority given and ID already present, use existing priority.
			if ( empty( $priority ) ) {
				$priority = $a_priority;
				/*
				 * Else, if we're adding to the sorted priority, we don't know the title
				 * or callback. Grab them from the previously added context/priority.
				 */
			} elseif ( 'sorted' === $priority ) {
				$title         = $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]['title'];
				$callback      = $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]['callback'];
				$callback_args = $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]['args'];
			}

			// An ID can be in only one priority and one context.
			if ( $priority !== $a_priority || $context !== $a_context ) {
				unset( $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ] );
			}
		}
	}

	if ( empty( $priority ) ) {
		$priority = 'low';
	}

	if ( ! isset( $wp_meta_boxes[ $page ][ $context ][ $priority ] ) ) {
		$wp_meta_boxes[ $page ][ $context ][ $priority ] = array();
	}

	$wp_meta_boxes[ $page ][ $context ][ $priority ][ $id ] = array(
		'id'       => $id,
		'title'    => $title,
		'callback' => $callback,
		'args'     => $callback_args,
	);
}