Meta Boxes Tutorial

Today many of us thinking about WordPress Meta Boxes like about something obsolete. Well, you know, Gutenberg, Full Site Editing and all that stuff. But I can say, that it is not true, yet.

No matter how great Gutenberg becomes, no matter how many cool plugins appear that allow you to manage custom fields, it is always useful to know how to create a metabox from scratch. At least look at WooCommerce, the Products post type works without Gutenberg right now. And probably for your specific project you would need a post type without visual editing. That’s where meta boxes come into play.

What are Meta Boxes Exactly?

Meta boxes are UI blocks in WordPress admin that allow you to manage post metadata.

Okay, what is metadata?

Every type of content in WordPress like posts, terms, users and comments has its own table in WordPress database, wp_posts, wp_terms, wp_users and wp_comments accordingly. Those tables are intended to store some default data, like post title and publish date for posts.

But what if you need to store something super-custom? Like a SEO-title for posts or an age for users or… product price for products? The answer is – metadata. WordPress also has specific database tables for that – wp_postmeta, wp_termmeta, wp_usermeta, wp_commentmeta.

Here is how it looks like in phpMyAdmin:

metadata in WordPress phpMyAdmin
wp_postmeta table

And meta boxes allow to manage this data, for example in WooCommerce. You can see that _sale_price value in the table above is the same that is in Sale price field below.

WooCommerce product metabox
Product data metabox in WooCommerce

How to Remove Default Meta Boxes

If you can not wait to create a metabox, just proceed to the next chapter of the guide.

On every edit post type page WordPress already has predefined meta boxes, if you open any post edit page with “Classic editor” plugin active, you will see them.

There are two ways to remove a default metabox – using remove_meta_box() function or with the help of remove_post_type_support().


Straight to the example. Let’s suppose that we have to remove both metaboxes – “Page Attributes” for pages and “Custom Fields” for posts. The code will be:

add_action( 'admin_menu', 'misha_remove_meta_box' );

function misha_remove_meta_box(){
	// Custom Fields
	remove_meta_box( 'postcustom', 'post', 'normal' );
	// Page Attributes
	remove_meta_box( 'pageparentdiv', 'page', 'normal' );

The first parameter of the function is a meta box ID. The easiest way to find it out is to inspect the code in your browser, but just in a case here is the list of default meta boxes IDs.

  • commentstatusdiv – Discussion,
  • slugdiv – Slug,
  • commentsdiv – Comments,
  • postexcerpt – Excerpt,
  • authordiv – Author,
  • revisionsdiv – Revisions,
  • postcustom – Custom Fields,
  • trackbacksdiv – Send Trackbacks,
  • categorydiv – Categories,
  • tagsdiv-post_tag – Tags,
  • {Taxonomy name}div – Any taxonomy,
  • postimagediv – You can not remove Featured image metabox using remove_meta_box() function, use remove_post_type_support() instead,
  • pageparentdiv – Page Attributes,
  • submitdiv – Publish.

The second parameter of remove_meta_box() function is a post type you would like to remove it from.


Let’s try to do the same what we did using remove_meta_box() function before. Technically remove_post_type_support() function doesn’t remove a meta box, it disables a specific functionality for a post type.

The cool thing about remove_post_type_support() approach is that it works both for Gutenberg and Classic Editor.

add_action( 'init', 'misha_remove_meta_box_2' );

function misha_remove_meta_box_2() {
	// Featured image
	remove_post_type_support( 'page', 'thumbnail' );
	// Page attributes
	remove_post_type_support( 'page', 'page-attributes' );

As you can see the code is similar.

I think the most correct way to remove default WordPress meta boxes is with remove_post_type_support() function, because it actually turns off a specific feature for a specific post type, and remove_meta_box() function just removes a meta box.

Features / Meta Boxes:

  • thumbnail — Featured Image metabox
  • author — Author metabox
  • excerpt — Excerpt metabox
  • trackbacks — Send trackbacks metabox
  • custom-fields — Custom Fields metabox
  • comments — Comments metabox
  • revisions — Revisions metabox
  • page-attributes — Page Attributes metabox (Gutenberg only)

You can also remove such areas as title or content editor:

  • title
  • editor


You’ve probably noticed, that in the previous chapter there was no mention of removing taxonomies meta boxes. It it because there is no way to use remove_post_type_support() function for taxonomies whether it is a custom taxonomy or not.

But there is another way:

add_action( 'init', 'misha_remove_taxonomy_meta_boxes' );

function misha_remove_taxonomy_meta_boxes() {
	// for Post tags
	unregister_taxonomy_for_object_type( 'post_tag', 'post' );
	// for Categories
	unregister_taxonomy_for_object_type( 'category', 'post' );

Please keep in mind that this approach completely disconnects a specific taxonomy from a post type. If you just wanted to remove its metabox, I highly recommend to use this method.

Add Custom Meta Box

Finally we came to the part of the tutorial where I am going to show you how to create a meta box in WordPress, here is a screenshot of what we are going to create:

WordPress meta box for Pages custom post type with multiple fields
WordPress custom meta box with multiple fields for “Pages” post type.

There are 2 ways of doing it – fully from scratch or with the help of my meta boxes plugin.

Step 1. add_meta_box()

Function add_meta_box() should be called inside an action hook, there are 3 options:

  • admin_menu
  • add_meta_boxes
  • add_meta_boxes_{post_type} – here you can specify a specific post type name, e.g. add_meta_boxes_page – so the code will only run when editing page post type.
add_action( 'admin_menu', 'misha_add_metabox' );
// or add_action( 'add_meta_boxes', 'misha_add_metabox' );
// or add_action( 'add_meta_boxes_{post_type}', 'misha_add_metabox' );

function misha_add_metabox() {

		'misha_metabox', // metabox ID
		'Meta Box', // title
		'misha_metabox_callback', // callback function
		'page', // add meta box to custom post type (or post types in array)
		'normal', // position (normal, side, advanced)
		'default' // priority (default, low, high, core)


// it is a callback function which actually displays the content of the meta box
function misha_metabox_callback( $post ) {

	echo 'hey';

Once you insert this code to functions.php of your child theme or in a custom plugin, this meta box should appear:

empty metabox example added with add_meta_box()
Simple example of using add_meta_box() function.

As you can see all we printed inside the callback function is now displayed inside the meta box.

Let’s dive into it and fill our metabox with fields.

Step 2. Callback function with meta box HTML

In the code below I populated our custom meta box with multiple fields.

function misha_metabox_callback( $post ) {

	$seo_title = get_post_meta( $post->ID, 'seo_title', true );
	$seo_robots = get_post_meta( $post->ID, 'seo_robots', true );

	// nonce, actually I think it is not necessary here
	wp_nonce_field( 'somerandomstr', '_mishanonce' );

	echo '<table class="form-table">
				<th><label for="seo_title">SEO title</label></th>
				<td><input type="text" id="seo_title" name="seo_title" value="' . esc_attr( $seo_title ) . '" class="regular-text"></td>
				<th><label for="seo_tobots">SEO robots</label></th>
					<select id="seo_robots" name="seo_robots">
						<option value="">Select...</option>
						<option value="index,follow"' . selected( 'index,follow', $seo_robots, false ) . '>Show for search engines</option>
						<option value="noindex,nofollow"' . selected( 'noindex,nofollow', $seo_robots, false ) . '>Hide for search engines</option>


In the code above:

  • I used function get_post_meta() to get the existing meta fields values and display them.
  • wp_nonce_field() is needed for additional security, when we save our meta box data, we can check the nonce value to make sure that the request come from the proper place.
  • selected() is a build-in WordPress function that simplifies the conditions and displaying selected="selected" in the <select> field.

Our meta box:

WordPress meta box for Pages custom post type with multiple fields
Now we’ve printed multiple fields in the callback function.

Step 3. Save meta box data

If you are using this metabox on the page edit screen or for a custom post type, don’t forget to redefine supported post types on line 23, otherwise metabox data won’t be saved.

Functions update_post_meta() and delete_post_meta() (lines 27-36) should be called for each input field in your meta box.

add_action( 'save_post', 'misha_save_meta', 10, 2 );
// or add_action( 'save_post_{post_type}', 'misha_save_meta', 10, 2 );

function misha_save_meta( $post_id, $post ) {

	// nonce check
	if ( ! isset( $_POST[ '_mishanonce' ] ) || ! wp_verify_nonce( $_POST[ '_mishanonce' ], 'somerandomstr' ) ) {
		return $post_id;

	// check current user permissions
	$post_type = get_post_type_object( $post->post_type );

	if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) ) {
		return $post_id;

	// Do not save the data if autosave
	if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
		return $post_id;

	// define your own post type here
	if( 'page' !== $post->post_type ) {
		return $post_id;

	if( isset( $_POST[ 'seo_title' ] ) ) {
		update_post_meta( $post_id, 'seo_title', sanitize_text_field( $_POST[ 'seo_title' ] ) );
	} else {
		delete_post_meta( $post_id, 'seo_title' );
	if( isset( $_POST[ 'seo_robots' ] ) ) {
		update_post_meta( $post_id, 'seo_robots', sanitize_text_field( $_POST[ 'seo_robots' ] ) );
	} else {
		delete_post_meta( $post_id, 'seo_robots' );

	return $post_id;


Please keep in mind, that depending on your field type you must use sanitization, so in the above example I am using sanitize_text_field() function, but there are also another ones, like sanitize_textarea() for textarea fields, sanitize_email() etc.

Also it is also not necessary to use delete_post_meta() for some cases, for example for regular text fields. But isset() or empty() condition checks are still recommended because you would get PHP notices if a field value will be empty.

$seo_title = isset( $_POST[ 'seo_title' ] ) ? sanitize_text_field( $_POST[ 'seo_title' ] ) : '';
update_post_meta( $post_id, 'seo_title', $seo_title );

Gutenberg Compatibility Flags

By default when you add a meta box, it will also appear in Gutenberg editor, sometimes it is ok, but you have to remember that it is just a part of backward compatibility, and meta boxes shouldn’t be displayed in Gutenberg – instead of them we have to use plugin sidebars or custom panels.

Otherwise you would face bloated block editor:

backward compatibility for WordPress meta boxes
And it is just five meta boxes.

But what if you just don’t know if your customers are going to use Gutenberg or Classic Editor? There are two flags (parameters) that you can pass to add_meta_box() function that could help us with that.

  • __back_compat_meta_box – set it to true and your meta box will only be loaded in the classic editor interface.
  • __block_editor_compatible_meta_box – set it to false and then in Gutenberg the metabox will display a message that it is not compatible with block editor and you should use it with classic editor instead.

Example of using __back_compat_meta_box:

	'Meta Box',
		'__back_compat_meta_box' => true,

Example of using __block_editor_compatible_meta_box:

	'Meta Box',
		'__block_editor_compatible_meta_box' => false,

That’s the message in Gutenberg:

Example of using __block_editor_compatible_meta_box flag
Flag __block_editor_compatible_meta_box is set to false.

Create the same Meta Box, the Easy Way

Above was a lot of code. And it is only for 2 input fields. What happens if your metabox should have let’s say 20 fields?

That’s why I never create meta boxes from scratch, I created a plugin for myself, which saves me so much time, you can download it here.

As simple as that:

  1. Install and activate my plugin,
  2. Paste the code below into your current theme functions.php:
add_filter( 'simple_register_metaboxes', 'misha_create_a_metabox' );

function misha_create_a_metabox( $metaboxes ) {

	$metaboxes[] = array(
 		'id'	=> 'my_metabox',
 		'name'	=> 'Meta Box',
 		'post_type' => array( 'page' ),
 		'fields' => array(
				'id' => 'seo_title',
				'label' => 'SEO title',
				'type' => 'text'
				'id' => 'seo_robots',
				'label' => 'SEO robots',
				'type' => 'select'
				'placeholder' => 'Select...',
				'options' => array(
					'index,follow' => 'Show for search engines',
					'noindex,nofollow' => 'Hide for search engines',

	return $metaboxes;


And we have the same meta box! ⚡️

WordPress meta box for Pages custom post type with multiple fields
The same meta box with multiple fields created with my plugin.
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X