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:

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.

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()
.
remove_meta_box()
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,
– You can not remove Featured image metabox usingpostimagedivremove_meta_box()
function, useremove_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.
remove_post_type_support()
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 metaboxauthor
— Author metaboxexcerpt
— Excerpt metaboxtrackbacks
— Send trackbacks metaboxcustom-fields
— Custom Fields metaboxcomments
— Comments metaboxrevisions
— Revisions metaboxpage-attributes
— Page Attributes metabox (Gutenberg only)
You can also remove such areas as title or content editor:
title
editor
unregister_taxonomy_for_object_type()
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:

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 editingpage
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() {
add_meta_box(
'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:

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">
<tbody>
<tr>
<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>
</tr>
<tr>
<th><label for="seo_tobots">SEO robots</label></th>
<td>
<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>
</select>
</td>
</tr>
</tbody>
</table>';
}
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 displayingselected="selected"
in the<select>
field.
Our meta box:

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:

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 totrue
and your meta box will only be loaded in the classic editor interface.__block_editor_compatible_meta_box
– set it tofalse
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
:
add_meta_box(
'misha_metabox',
'Meta Box',
'misha_metabox_callback',
'page',
'normal',
'default',
array(
'__back_compat_meta_box' => true,
)
);
Example of using __block_editor_compatible_meta_box
:
add_meta_box(
'misha_metabox',
'Meta Box',
'misha_metabox_callback',
'page',
'normal',
'default',
array(
'__block_editor_compatible_meta_box' => false,
)
);
That’s the message in Gutenberg:

__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:
- Install and activate my plugin,
- 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(
array(
'id' => 'seo_title',
'label' => 'SEO title',
'type' => 'text'
),
array(
'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! ⚡️


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
I have a WordPress site and this article is very helpful. Great example code and very easy to understand. Really is very nice.
Thanks,
That’s a nice post. Can you tell me how to remove custom meta-box? I have a plugin where I’ve created a custom meta-box. Now I want to remove it on button click. I’ve created that custom meta-box in main plugin while, whereas I want to delete it via another settings file. Thanks!
Just check the options with
get_option()
and if the metabox is turned off, do not add it.So finally something that actually works. But I am having difficult times figuring out how to display the content of text field.
Also not sure if check box actually blocks robots for real or it is just for demonstration purposes?
So I use two languages on my website and I need a meta box to manually add language: lang=”en”. I want echo this in my html head. Would you be able to help me with this one?
I tried and but with no results.
Appreciate any help.
Thank you, very helpful tutorial :)
What PHP version? 5 or 7.? For me is everything ok. I have version PHP 7.3 on the server.
Thanks Misha! …but why no ability to remove category or tag features with remove_post_type_support?!
Thank you for a good question! Just added the info about it
remove_meta_box(), does not work with WP 5.9, remove_post_type_support(), does the work
Not exactly.
I assume you either tried using
remove_meta_box()
for block editor or maybe tried to hide Featured image metabox with it.I have added some clarifications in the post.