How to Add a Metabox to a Custom Post Type

Update (7/1/14): I created a boilerplate plugin that has custom metaboxes. Everything in this post is still valid, but if you want to just have working code to edit and play with, check this out on GitHub.

Most custom post types in WordPress will need (or could benefit from) a unique set metaboxes for entering information.

For example, a “photography” post type might need fields for “location of photo”, “type of camera”, etc. And an “event” post type would probably need a “location” and an “event date”.

Metaboxes aren’t the easiest to set up- so I’ve written up this tutorial which shows how to add a one line field for “location” to an “event” post type.

Hopefully, you’ll be able to use this guide to add any sort of metaboxes you need.

Set Up the Post Type

If you are unfamiliar with how to set up custom post types, check out Justin Tadlock’s excellent tutorial. For this example, I am going to use a post type called “Event”, which goes in my functions.php file:

// Registers the new post type and taxonomy

function wpt_event_posttype() {
	register_post_type( 'events',
			'labels' => array(
				'name' => __( 'Events' ),
				'singular_name' => __( 'Event' ),
				'add_new' => __( 'Add New Event' ),
				'add_new_item' => __( 'Add New Event' ),
				'edit_item' => __( 'Edit Event' ),
				'new_item' => __( 'Add New Event' ),
				'view_item' => __( 'View Event' ),
				'search_items' => __( 'Search Event' ),
				'not_found' => __( 'No events found' ),
				'not_found_in_trash' => __( 'No events found in trash' )
			'public' => true,
			'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ),
			'capability_type' => 'post',
			'rewrite' => array("slug" => "events"), // Permalinks format
			'menu_position' => 5,
			'register_meta_box_cb' => 'add_events_metaboxes'

add_action( 'init', 'wpt_event_posttype' );

You may have your own custom post type set up completely different, but that’s fine. The important line of code for the metaboxes is ‘register_meta_box_cb’ => ‘add_events_metaboxes’- which calls the function to build the metaboxes.

You can rename the function to whatever you like, for instance ‘register_meta_box_cb’ => ‘add_photography_metaboxes’ might be better for a photography post type.

If the post type is being registered through a plugin or is one of the native post types, you can also use:

add_action( 'add_meta_boxes', 'add_events_metaboxes' );

Add Meta Box

The following code adds a metabox to the right side of the screen under the “Publish” box:

// Add the Events Meta Boxes

function add_events_metaboxes() {
	add_meta_box('wpt_events_location', 'Event Location', 'wpt_events_location', 'events', 'side', 'default');

You can read the full parameters for add_meta_box in the codex. I also listed them here:

<?php add_meta_box( $id, $title, $callback, $page, $context, $priority, $callback_args ); ?> 

For the example above:

  • $id is “wpt_events_location”- or the html id that will be applied to this metabox.
  • $title is “Event Location”. This appears at the top of the new metabox when displayed.
  • $callback is the function “wpt_events_location” which will load the html into the metabox.
  • $page is “events”, the name of our custom post type.
  • $context is “side”. If you wanted it to load below the content area, you could put “normal”.
  • $priority controls where the metabox will display in relation to the other metaboxes. You can put “high”, “low” or “default”.

If you wanted to have two sets of metaboxes, perhaps one on the side and one below the content area, you could do something like this (Note: Don’t use this if you’re following the tutorial step by step, this is just an example of how it would be done):

// Add the Events Meta Boxes

function add_events_metaboxes() {
	add_meta_box('wpt_events_date', 'Event Date', 'wpt_events_date', 'events', 'side', 'default');
	add_meta_box('wpt_events_location', 'Event Location', 'wpt_events_location', 'events', 'normal', 'high');

You’d then have to make sure the two function wpt_events_date and wpt_events_location were defined to call the html code to go inside the metaboxes.

Generating the HTML for the Metabox

Continuing with the first example above, we’ll now have to generate the code that goes inside our “Event Location” metabox. To keep this as simple as possible, we’re just going to make one field:

// The Event Location Metabox

function wpt_events_location() {
	global $post;
	// Noncename needed to verify where the data originated
	echo '<input type="hidden" name="eventmeta_noncename" id="eventmeta_noncename" value="' . 
	wp_create_nonce( plugin_basename(__FILE__) ) . '" />';
	// Get the location data if its already been entered
	$location = get_post_meta($post->ID, '_location', true);
	// Echo out the field
	echo '<input type="text" name="_location" value="' . $location  . '" class="widefat" />';


At this point you should have a metabox showing up in your post. If you check your “events” post type, it should load on the right side like in the screenshot I posted. This will generate any html you choose, so, you could put as many input fields in here as you like, or html descriptions.

In order to class the inputs and descriptions correctly, check out the source code for other write panels in WordPress. See how they do textareas and select boxes. You can even add icons and generated text in these spots.


If you had tried to save your metabox data before this point, it just would have disappeared on the refresh because it wasn’t being saved. Here’s the code that updates the metabox when you click “Update” (adapted from Nathan Rice in the AgentPress theme):

// Save the Metabox Data

function wpt_save_events_meta($post_id, $post) {
	// verify this came from the our screen and with proper authorization,
	// because save_post can be triggered at other times
	if ( !wp_verify_nonce( $_POST['eventmeta_noncename'], plugin_basename(__FILE__) )) {
	return $post->ID;

	// Is the user allowed to edit the post or page?
	if ( !current_user_can( 'edit_post', $post->ID ))
		return $post->ID;

	// OK, we're authenticated: we need to find and save the data
	// We'll put it into an array to make it easier to loop though.
	$events_meta['_location'] = $_POST['_location'];
	// Add values of $events_meta as custom fields
	foreach ($events_meta as $key => $value) { // Cycle through the $events_meta array!
		if( $post->post_type == 'revision' ) return; // Don't store custom data twice
		$value = implode(',', (array)$value); // If $value is an array, make it a CSV (unlikely)
		if(get_post_meta($post->ID, $key, FALSE)) { // If the custom field already has a value
			update_post_meta($post->ID, $key, $value);
		} else { // If the custom field doesn't have a value
			add_post_meta($post->ID, $key, $value);
		if(!$value) delete_post_meta($post->ID, $key); // Delete if blank


add_action('save_post', 'wpt_save_events_meta', 1, 2); // save the custom fields

This code checks to make sure the user has privileges to update the post, then saves all the input data into the array $events_meta.

An Additional Example

If we wanted to have additional fields, we’d need to output them in the metabox callback (in this case: wpt_events_location) and make sure they’re saved in the by adding them to the array in wpt_save_events_meta.

This is how you would add an additional field for “dress code”. First, update the function wpt_events_location to:

// The Event Location Metabox

function wpt_events_location() {
	global $post;
	// Noncename needed to verify where the data originated
	echo '<input type="hidden" name="eventmeta_noncename" id="eventmeta_noncename" value="' . 
	wp_create_nonce( plugin_basename(__FILE__) ) . '" />';
	// Get the location data if its already been entered
	$location = get_post_meta($post->ID, '_location', true);
        $dresscode = get_post_meta($post->ID, '_dresscode', true);
	// Echo out the field
        echo '<p>Enter the location:</p>';
	echo '<input type="text" name="_location" value="' . $location  . '" class="widefat" />';
        echo '<p>How Should People Dress?</p>';
        echo '<input type="text" name="_dresscode" value="' . $dresscode  . '" class="widefat" />';


Now add the extra array item to wpt_save_events_meta- so it looks like this:

$events_meta['_location'] = $_POST['_location'];
$events_meta['_dresscode'] = $_POST['_dresscode'];

Displaying the Metabox Information in a Template

To display the metabox information in your page templates, you’ll need to fetch the metabox data stored in the post. If you’re inside the loop, you would do it like this:

<?php echo get_post_meta($post->ID, "_location", true); ?>

More information about this is available in the codex:

Other Tutorials

There’s a few other tutorials on the web that have been extremely helpful for figuring this stuff out. One of the best is: How to Create a Better Meta Box in WordPress.

Please share the code and enjoy.

About Devin

I'm a WordPress developer based in Austin, Texas. I run a little theme shop called DevPress and work for a startup called Nano. Find me on twitter @devinsays.

120 Responses

  1. I manage to get the above working.

    Just checking, have you managed to use this method to create a image upload box?

    I’m just thinking whether it’s possible to use this method to create a box that is use solely for uploading special thumbnail images which are already not on featured image or header .

  2. this is a good article, however im unsure about something. when applying more than one metabox, the first metabox has a callback which is not valid.

    wpt_events_location is an existing function, but wpt_events_date doesnt exist. do i add another function for the first meta box? or do i give the first metabox the same callback as the second metabox?

    when applying your multi field example it should give an error about no existing call back for the first meta box. am i mistaking?

    // Add the Events Meta Boxes
    function add_events_metaboxes() {
    add_meta_box(‘wpt_events_date’, ‘Event Date’, ‘wpt_events_date’, ‘events’, ‘side’, ‘default’);
    add_meta_box(‘wpt_events_location’, ‘Event Location’, ‘wpt_events_location’, ‘events’, ‘normal’, ‘high’);

  3. Joshua

    Thanks for this tutorial Devin.

    Now that I’ve created this meta box, how do I add it to my CPT overview screen with its contents?

    So far, I haven’t been able to find a snippet that specifically works for this.

      1. Mike

        I know this is old, but for others who may find this article and want to add their custom field data to the Overview Screen, a great plugin (free) I found is: “Admin Columns” (

        There is a pro version, but the free version did what I needed, and it does it BEAUTIFULLY!

  4. Ok … I’m back and I’m certain there are no typos this time.

    Here’s my code:

    The data is being stored in the database meta for the custom post type, and I can display it on the single output, but it is not displaying in the text box area on the edit post screen.

    Would you be so kind as to have a look and let me know what I’m doing wrong?

  5. Great tutorial!

    @Marj Wayatt: Make sure you have all your variable and function names correctly. The example does work. It worked for me. But I was having trouble saving initially also and found out that I was not using my array’s name, but the one from the example (‘$events_meta), in the foor loop. Changing that fixed the problem.

    Check your code.

  6. Greg

    If I wanted to take the custom metabox values and save them to a custom taxonomy, is it as simple as replacing update_post_meta(); with update_post_terms();? I’ve done something very similar to what you’re doing here, but with taxonomies and can’t get them to save properly.

  7. Samuel

    Hi Devin,

    Great tutorial on this, worked perfect for me. I’m having one issue that I can’t quite figure out. How can I add the capability to output shortcodes in my meta field? (With your template display code I do get a result, but it’s my slider shortcode in brackets…)

    I’ve tried it like this:
    if get_post_meta($postid, '_location', true);
    echo do_shortcode(get_post_meta($postid, '_location', $single = true));

    and like this with my meta box name wpt_project_slider:
    ID, 'wpt_project_slider', true))
    echo do_shortcode(get_post_meta($post->ID, 'wpt_project_slider', $single = true));

    In both instances, I get nothing! I would greatly appreciate the help.

  8. Kent Otto

    This is working very well for me, thank you! Do you know how I might incorporate the use of a select dropdown field in a meta box instead of a text input field?

      1. Ken

        I ended up abandoning the above instructions and instead followed the guide here so I was able to implement the select drop-down, etc. Works perfectly for me. See section 7.3 specifically.

  9. pulla

    It works great but it shows “Page not found” when you click “view this page”.
    I did re-save permalinks on setting. but it doesn’t seem to be working.

    When I put this …
    ‘rewrite’ => array(‘slug’ => ‘event’,’with_front’ => FALSE),

    the title is saying “Page not found” and showing “Hello world page”. it’s wordpress default page.

    Any tips?

  10. Hello,

    I have a real estate website that I want to have a custom field in the custom post type (property) in the theme I am using.

    The field just needs to be a plain text box where I can input the property information that I want for ADMIN/USERS EYES ONLY.

    The backend field needs to be completely hidden from anyone who is not logged in, not in the source code or anything…..

    Could you tell me how to get this done? Thanks

  11. brodster

    nice post, I am having trouble removing date meta box for a specific post type..
    I want to remove the date metabox form single page..
    the code below does not work

    remove_meta_box(‘submitdiv’, ‘acmeproduct’, ‘normal’);

  12. Thomas Barnwell

    Yes!! Thank you, this worked great for me! Now I’m going to mod it slightly to use it to create relations between custom posts. Thanks again.

  13. Andrew

    This is still working as of 3.8.1! Should be part of WP. Anywho, I’m looking for a way to get private custom posts into a menu. I either need the ability to allow private custom post types to show by default in these meta boxes or implement a search function in the meta box. Anyone got any ideas / direction? I can’t find anything…

  14. Great write-up! I just have one issue, I’m getting this error:
    Undefined index: curl_noncename in /home/newcys/public_html/wp-content/themes/cys/functions.php on line 271

    When I save the post, it saves, and the error no longer appears. It’s there though every time I go to create a new post.

  15. Henri

    Thanks for the tutorial. Mine works well except that it gives me a warning due to missing 2 params.

    Instead of:
    add_action(‘save_post’, ‘wpt_save_events_meta’, 1, 2); // save the custom fields
    What I have is
    add_action(‘save_post’, ‘wpt_save_events_meta’); // save the custom fields

    I understand that ‘1’ and ‘2’ refer to post_id and post. Why are they being hardcoded there?
    I found that here the numbers are changed to ’10’ and ‘2’
    I’m very new to wordpress so thanks in advance for the enlightenment.

  16. I found this to be very useful for showing the events on the archive pages in addition to your script:

    function wpse_category_set_post_types( $query ){
    if( $query->is_category() && $query->is_main_query() ){
    $query->set( ‘post_type’, array( ‘post’, ‘events’) );
    add_action( ‘pre_get_posts’, ‘wpse_category_set_post_types’ );

  17. Thanks!
    I’ve been playing script kiddy with WordPress all night.
    I’ve setup custom post meta boxes a few times but this tutorial was very clear and keept just the info I needed.. I got it done in less than 20 minutes!

  18. zishan

    How to create custom category type meta box?
    Ii i select job type IT, then specific meta box will show.. and again when i select another job type then another meta box will shoe.

  19. Robert Zalinyan

    Hi . This is great tutorial. It works fine but there is one issue. I can not save the meta box values. After saving the post it does not save. How to solve this issue?

  20. I want to create custom content type with custom fields like Emp Name, Emp ID, Emp Salary, Emp Designation and print these posts in a table formate.

    Like header (All the Keys ie emp name, emp id etc) and repeated values while loop.

  21. Atiqur

    Hi, if you check by activating debug TRUE from wp-config file, you will get “Undefined index”… error notice while adding any new post. I think codes need to update to work better in latest wordpress version

Leave a Reply