add_meta_box()WP 2.5.0

Adds additional blocks (meta box) on the edit/create post, page, or custom post type screens in the admin panel.

It needs to be called on the hook add_meta_boxes or later.

Use wp_add_dashboard_widget() to add a meta box on the admin panel "Dashboard", "Multisite Dashboard".

All registered data is located in the global array $wp_meta_boxes, which contains a multidimensional array of all registered meta boxes. It contains their IDs, parameters (args), callback functions (callback), and titles of all post types, including custom ones.

Gutenberg

Disabling the Gutenberg editor via meta box

If the meta box does not work with Gutenberg, and updating it to work with it is not possible, then the Gutenberg editor can be disabled via the meta box.

To do this, you need to pass the argument __block_editor_compatible_meta_box = false in the $callback_args parameter (parameters passed to the callback function). After this, WordPress will revert to the classic editor with the familiar meta box:

add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', null, 'normal', 'high',
	[
		'__block_editor_compatible_meta_box' => false,
	]
);

Support for the Gutenberg editor

When the meta box fully supports the Gutenberg block editor, you can disable the output of the classic meta field block by adding the argument __back_compat_meta_box = false. Now, when Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now exists only for backward compatibility. However, it will still be displayed in the Classic editor if Gutenberg is disabled.

add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', null, 'normal',  'high',
	[
		'__back_compat_meta_box' => false,
	]
);

No Hooks.

Returns

null. Returns nothing.

Usage

add_meta_box( $id, $title, $callback, $screen, $context, $priority, $callback_args );
$id(string) (required)
id attribute of the HTML tag, container of the block.
$title(string) (required)
Title/name of the block. Visible to users.
$callback(string) (required)

The function that outputs the HTML content of the block. Must output the result to the screen.

Gets the post object as the first parameter.

$screen(string/array/WP_Screen)

The name of the screen for which the block is added. See get_current_screen(). For example, it can be:

  • For post types: post, page, link, attachment or custom_post_type
  • Or for other admin pages: link, comment.
  • You can specify multiple types in an array: array('post', 'page'). Since version 4.4.

Default: null (current screen)

$context(string)

The place where the block should be shown: normal, advanced or side.

Also, you can specify a custom value; in this case, the output of the meta box (context handling) will need to be hooked to the appropriate hook. See example 6.
Default: 'advanced'

$priority(string)
The priority of the block for display above or below other blocks: high or low. You can also specify core, default.
Default: 'default'
$callback_args(array)

Arguments to pass to the callback function specified in the $callback parameter. The callback function will receive the post data (object $post) and the arguments passed in this parameter.

Default: null

Examples

More examples see in the questions.

10

#1 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 );
}
1

#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:

1

#3 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

#4 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

#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.

-11

#7 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 );
}

Notes

  • Global. Array. $wp_meta_boxes Global meta box state.

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.8.1

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,
	);
}