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.

Add Custom Meta Box

Let me show you how to create a meta box in WordPress, here is a screenshot of what we are going to create:

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', 'atdev_add_metabox' );
// or add_action( 'add_meta_boxes', 'atdev_add_metabox' );
// or add_action( 'add_meta_boxes_{post_type}', 'atdev_add_metabox' );

function atdev_add_metabox() {

		'atdev_metabox', // metabox ID
		'Meta Box', // title
		'wpdev_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 atdev_metabox_callback( $post ) {

	echo 'hey';

Insert above code in your theme functions.php or in a custom plugin.

Step 2. Callback function with meta box HTML

Now populate the meta box with custom fields.

function atdev_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( 'atdev_security', '_atdevnonce' );

	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_robots">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.

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, otherwise metabox data won’t be saved.

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

function atdev_save_meta( $post_id, $post ) {

	// nonce check
	if ( ! isset( $_POST[ '_atdevnonce' ] ) || ! wp_verify_nonce( $_POST[ '_atdevnonce' ], 'atdev_security' ) ) {
		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 );

Have fun coding!

Categorized in:


Tagged in: