How To Create Custom Post Meta Boxes In WordPress


What seems like one of the most complicated bits of functionality in WordPress is adding meta boxes to the post editing screen. This complexity only grows as more and more tutorials are written on the process with weird loops and arrays. Even meta box “frameworks” have been developed. I’ll let you in on a little secret though: it’s not that complicated.


Creating custom meta boxes is extremely simple, at least it is once you’ve created your first one using the tools baked into WordPress’ core code. In this tutorial, I’ll walk you through everything you need to know about meta boxes:

  • Creating meta boxes.
  • Using meta boxes with any post type.
  • Handling data validation.
  • Saving custom meta data.
  • Retrieving custom meta data on the front end.

Note: When I use the term “post” throughout this tutorial, I’m referring to a post of any post type, not just the default blog post type bundled with WordPress.

What is a post meta box?

A post meta box is a draggable box shown on the post editing screen. Its purpose is to allow the user to select or enter information in addition to the main post content. This information should be related to the post in some way.

Generally, two types of data is entered into meta boxes:

  • Metadata (i.e. custom fields),
  • Taxonomy terms.

Of course, there are other possible uses, but those two are the most common. For the purposes of this tutorial, you’ll be learning how to develop meta boxes that handle custom post metadata.

What is post metadata?

Post metadata is data that’s saved in the wp_postmeta table in the database. Each entry is saved as four fields in this table:

  • meta_id: A unique ID for this specific metadata.
  • post_id: The post ID this metadata is attached to.
  • meta_key: A key used to identify the data (you’ll work with this often).
  • meta_value: The value of the metadata.

In the following screenshot, you can see how this looks in the database.

When you get right down to it, metadata is just key/value pairs saved for a specific post. This allows you to add all sorts of custom data to your posts. It is especially useful when you’re developing custom post types.

The only limit is your imagination.

Note: One thing to keep in mind is that a single meta key can have multiple meta values. This isn’t a common use, but it can be extremely powerful.

Working with post metadata

By now, you’re probably itching to build some custom meta boxes. However, to understand how custom meta boxes are useful, you must understand how to add, update, delete, and get post metadata.

I could write a book on the various ways to use metadata, but that’s not the main purpose of this tutorial. You can use the following links to learn how the post meta functions work in WordPress if you’re unfamiliar with them.

The remainder of this tutorial assumes that you’re at least familiar with how these functions work.

The setup

Before building meta boxes, you must have some ideas about what type of metadata you want to use. This tutorial will focus on building a meta box that saves a custom post CSS class, which can be used to style posts.

I’ll start you off by teaching you to develop custom code that does a few extremely simple things:

  • Adds an input box for you to add a custom post class (the meta box).
  • Saves the post class for the smashing_post_class meta key.
  • Filters the post_class hook to add your custom post class.

You can do much more complex things with meta boxes, but you need to learn the basics first.

All of the PHP code in the following sections goes into either your custom plugin file or your theme’s functions.php file.

Building a custom post meta box

Now that you know what you’re building, it’s time to start diving into some code. The first two code snippets in this section of the tutorial are mostly about setting everything up for the meta box functionality.

Since you only want your post meta box to appear on the post editor screen in the admin, you’ll use the load-post.php and load-post-new.php hooks to initialize your meta box code.

/* Fire our meta box setup function on the post editor screen. */
add_action( 'load-post.php', 'smashing_post_meta_boxes_setup' );
add_action( 'load-post-new.php', 'smashing_post_meta_boxes_setup' );

Most WordPress developers should be familiar with how hooks work, so this should not be anything new to you. The above code tells WordPress that you want to fire the smashing_post_meta_boxes_setup function on the post editor screen. The next step is to create this function.

The following code snippet will add your meta box creation function to the add_meta_boxes hook. WordPress provides this hook to add meta boxes.

/* Meta box setup function. */
function smashing_post_meta_boxes_setup() {

  /* Add meta boxes on the 'add_meta_boxes' hook. */
  add_action( 'add_meta_boxes', 'smashing_add_post_meta_boxes' );

Now, you can get into the fun stuff.

In the above code snippet, you added the smashing_add_post_meta_boxes() function to the add_meta_boxes hook. This function’s purpose should be to add post meta boxes.

In the next example, you’ll create a single meta box using the add_meta_box()5 WordPress function. However, you can add as many meta boxes as you like at this point when developing your own projects.

Before proceeding, let’s look at the add_meta_box() function:

add_meta_box( $id, $title, $callback, $page, $context = 'advanced', $priority = 'default', $callback_args = null );
  • $id: This is a unique ID assigned to your meta box. It should have a unique prefix and be valid HTML.
  • $title: The title of the meta box. Remember to internationalize this for translators.
  • $callback: The callback function that displays the output of your meta box.
  • $page: The admin page to display the meta box on. In our case, this would be the name of the post type (post, page, or a custom post type).
  • $context: Where on the page the meta box should be shown. The available options are normal, advanced, and side.
  • $priority: How high/low the meta box should be prioritized. The available options are default, core, high, and low.
  • $callback_args: An array of custom arguments you can pass to your $callback function as the second parameter.

The following code will add the post class meta box to the post editor screen.

/* Create one or more meta boxes to be displayed on the post editor screen. */
function smashing_add_post_meta_boxes() {

    'smashing-post-class',      // Unique ID
    esc_html__( 'Post Class', 'example' ),    // Title
    'smashing_post_class_meta_box',   // Callback function
    'post',         // Admin page (or post type)
    'side',         // Context
    'default'         // Priority

You still need to display the meta box’s HTML though. That’s where the smashing_post_class_meta_box() function comes in ($callback parameter from above).

/* Display the post meta box. */
function smashing_post_class_meta_box( $object, $box ) { ?>

  <?php wp_nonce_field( basename( __FILE__ ), 'smashing_post_class_nonce' ); ?>

    <label for="smashing-post-class"><?php _e( "Add a custom CSS class, which will be applied to WordPress' post class.", 'example' ); ?></label>
    <br />
    <input class="widefat" type="text" name="smashing-post-class" id="smashing-post-class" value="<?php echo esc_attr( get_post_meta( $object->ID, 'smashing_post_class', true ) ); ?>" size="30" />
<?php }

What the above function does is display the HTML output for your meta box. It displays a hidden nonce input (you can read more about nonces6 on the WordPress Codex). It then displays an input element for adding a custom post class as well as output the custom class if one has been input.

At this point, you should have a nice-looking meta box on your post editing screen. It should look like the following screenshot.

The meta box doesn’t actually do anything yet though. For example, it won’t save your custom post class. That’s what the next section of this tutorial is about.

Saving the meta box data

Now that you’ve learned how to create a meta box, it’s time to learn how to save post metadata.

Remember that smashing_post_meta_boxes_setup() function you created earlier? You need to modify that a bit. You’ll want to add the following code to it.

/* Save post meta on the 'save_post' hook. */
add_action( 'save_post', 'smashing_save_post_class_meta', 10, 2 );

So, that function will actually look like this:

/* Meta box setup function. */
function smashing_post_meta_boxes_setup() {

  /* Add meta boxes on the 'add_meta_boxes' hook. */
  add_action( 'add_meta_boxes', 'smashing_add_post_meta_boxes' );

  /* Save post meta on the 'save_post' hook. */
  add_action( 'save_post', 'smashing_save_post_class_meta', 10, 2 );

The new code you’re adding tells WordPress that you want to run a custom function on the save_post hook. This function will save, update, or delete your custom post meta.

When saving post meta, your function needs to run through a number of processes:

  • Verify the nonce set in the meta box function.
  • Check that the current user has permission to edit the post.
  • Grab the posted input value from $_POST.
  • Decide whether the meta should be added, updated, or deleted based on the posted value and the old value.

I’ve left the following function somewhat generic so that you’ll have a little flexibility when developing your own meta boxes. It is the final snippet of code that you’ll need to save the metadata for your custom post class meta box.

/* Save the meta box's post metadata. */
function smashing_save_post_class_meta( $post_id, $post ) {

  /* Verify the nonce before proceeding. */
  if ( !isset( $_POST['smashing_post_class_nonce'] ) || !wp_verify_nonce( $_POST['smashing_post_class_nonce'], basename( __FILE__ ) ) )
    return $post_id;

  /* Get the post type object. */
  $post_type = get_post_type_object( $post->post_type );

  /* Check if the current user has permission to edit the post. */
  if ( !current_user_can( $post_type->cap->edit_post, $post_id ) )
    return $post_id;

  /* Get the posted data and sanitize it for use as an HTML class. */
  $new_meta_value = ( isset( $_POST['smashing-post-class'] ) ? sanitize_html_class( $_POST['smashing-post-class'] ) : '' );

  /* Get the meta key. */
  $meta_key = 'smashing_post_class';

  /* Get the meta value of the custom field key. */
  $meta_value = get_post_meta( $post_id, $meta_key, true );

  /* If a new meta value was added and there was no previous value, add it. */
  if ( $new_meta_value && '' == $meta_value )
    add_post_meta( $post_id, $meta_key, $new_meta_value, true );

  /* If the new meta value does not match the old value, update it. */
  elseif ( $new_meta_value && $new_meta_value != $meta_value )
    update_post_meta( $post_id, $meta_key, $new_meta_value );

  /* If there is no new meta value but an old value exists, delete it. */
  elseif ( '' == $new_meta_value && $meta_value )
    delete_post_meta( $post_id, $meta_key, $meta_value );

At this point, you can save, update, or delete the data in the “Post Class” meta box you created from the post editor screen.

Using the metadata from meta boxes

So you have a custom post meta box that works, but you still need to do something with the metadata that it saves. That’s the point of creating meta boxes. What to do with your metadata will change from project to project, so this is not something I can answer for you. However, you will learn how to use the metadata from the meta box you’ve created.

Since you’ve been building a meta box that allows a user to input a custom post class, you’ll need to filter WordPress’ post_class hook so that the custom class appears alongside the other post classes.

Remember that get_post_meta() function from much earlier in the tutorial? You’ll need that too.

The following code adds the custom post class (if one is given) from your custom meta box.

/* Filter the post class hook with our custom post class function. */
add_filter( 'post_class', 'smashing_post_class' );

function smashing_post_class( $classes ) {

  /* Get the current post ID. */
  $post_id = get_the_ID();

  /* If we have a post ID, proceed. */
  if ( !empty( $post_id ) ) {

    /* Get the custom post class. */
    $post_class = get_post_meta( $post_id, 'smashing_post_class', true );

    /* If a post class was input, sanitize it and add it to the post class array. */
    if ( !empty( $post_class ) )
      $classes[] = sanitize_html_class( $post_class );

  return $classes;

If you look at the source code of the page where this post is shown on the front end of the site, you’ll see something like the following screenshot.


Pretty cool, right? You can use this custom class to style posts however you want in your theme’s stylesheet.


One thing you should keep in mind when saving data is security. Security is a lengthy topic and is outside the scope of this article. However, I thought it best to at least remind you to keep security in mind.

You’ve already been given a link explaining nonces earlier in this tutorial. The other resource I want to provide you with is the WordPress Codex guide on data validation7. This documentation will be your best friend when learning how to save post metadata and will provide you with the tools you’ll need for keeping your plugins/themes secure.

Bonus points to anyone who can name all of the security measures used throughout this tutorial.

Create a custom meta box

Once you’ve copied, pasted, and tested the bits of pieces of code from this tutorial, I encourage you to try out something even more complex. If you really want to see how powerful meta boxes and post metadata can be, try doing something with a single meta key and multiple meta values for that key (it’s challenging).

I hope you’ve enjoyed the tutorial. Feel free to post questions about creating meta boxes in the comments.



  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11

↑ Back to top Tweet itShare on Facebook

  1. 1

    Great tutorial!

    Justin, or anyone reading, can you please direct me to where I can find info on how to sanitize other data types? For example my meta values are text based.

    Also, is it true that if you use an underscore prefix that you’re custom post meta wont show up in the Custom Fields box? I’m using my custom meta for a custom post type so this would be great.

  2. 52

    In attempting to add multiple meta boxes I have gotten the error “Cannot modify header information – headers already sent” I believe i am duplicating something I shouldnt be and was curious if you could include a brief explanation of what does/doesnt need to be included for adding multiple meta boxes? Thank you and great article :)

  3. 103

    Oh my God!

    Really cool article! I am so happy it finally worked. I tried different tutorials, with different results – some of them not working, some of them even crashing my apache server :))

    But this one is so nice and straight forward. Thanks!

    I need to find a way to translate all the metabox text in other languages for qTranslate now :)

  4. 154

    I even made an improvement to the save post data function – check it out!

    In the foreach loop, all you have to do is put in the array all the names of the post variables you want to assign to the post. Of course, each variable is a field in the custom metabox :)

    Then, you don’t have to copy-paste the update metadata functions over and over again for each metadata field – joy to the world!

    function ghf_save_postdata($post_id, $post)
    /* Verify the nonce before proceeding. */
    if ( !isset( $_POST[‘ghf_product_meta_nonce’] ) || !wp_verify_nonce( $_POST[‘ghf_product_meta_nonce’], basename( __FILE__ ) ) )
    return $post_id;

    /* Get the post type object. */
    $post_type = get_post_type_object( $post->post_type );

    /* Check if the current user has permission to edit the post. */
    if ( !current_user_can( $post_type->cap->edit_post, $post_id ) )
    return $post_id;
    //_l(‘Save post data started for ‘.$post_id.”: “.var_export($post->post_type));
    /* Get the posted data and sanitize it for use as an HTML class. */
    foreach (array(‘ghf_product_price’,’ghf_discount_price’,’ghf_product_warranty’) as $meta_key)
    $new_meta_value = ( isset( $_POST[$meta_key] ) ? sanitize_html_class( $_POST[$meta_key] ) : ” );
    /* Get the meta value of the custom field key. */
    $meta_value = get_post_meta( $post_id, $meta_key, true );
    /* If a new meta value was added and there was no previous value, add it. */
    //_l(‘registering ‘.$meta_key.” as $new_meta_value old is $meta_value for id $post_id”);
    if ( $new_meta_value != ” && ” == $meta_value )
    add_post_meta( $post_id, $meta_key, $new_meta_value, true );
    /* If the new meta value does not match the old value, update it. */
    elseif ( $new_meta_value != ” && $new_meta_value != $meta_value )
    update_post_meta( $post_id, $meta_key, $new_meta_value );
    /* If there is no new meta value but an old value exists, delete it. */
    elseif ( ” == $new_meta_value && $meta_value != ” )
    delete_post_meta( $post_id, $meta_key, $meta_value );

    • 205

      Octav, great solution. I’m trying to implement that, I have a pesky metabox field that’s not saving when i input. Would you (or anyone) be willing to take a quick look at my code? i have event dates that work great, but i tried to add a field for location and it’s not retaining the input information.


    • 256

      While it seems like a good idea, it really isn’t. You have different types of data and are sanitizing/validating them in the same way and with a function that makes no sense for the data you’re trying to save.

  5. 307

    Is it possible to set a date (actually a time) format a custom meta? I need to be able to query posts based off of this value, but when passing ‘meta_key’ and ‘meta_value’ into my query args, something like ‘1:00pm’ will show up before ‘9:00am’. As far as I can tell, there’s no way to save the value in any format other than a string, correct? Any thoughts as to how to format the value of the meta_value prior to throwing it into the query_posts args?

  6. 358

    Steven Gliebe

    May 9, 2012 1:56 pm

    Thank you for this tutorial. I originally scoffed at bothering to do it from scratch when there are plugins/classes to make things quicker but with time both that I had used proved only to limit what I wanted to do. Now I feel better having more flexibility and not including third party code with my next theme. You never know which PHP freebie will end up being the next TimThumb…

  7. 409

    Hey. I wanted to save some code for each post, and show them on the home page only. When i use this yo save my code (a video code) for a post, everything else but the special characters gets removed. any solution?

  8. 460

    in your code :

    ‘smashing-post-class’, // Unique ID
    esc_html__( ‘Post Class’, ‘example’ ), // Title
    ‘smashing_post_class_meta_box’, // Callback function
    ‘post’, // Admin page (or post type)
    ‘side’, // Context
    ‘default’ // Priority

    Cant figure out why you are html-escaping the hardcoded title string. How can this be exploited?

  9. 511

    Thanks for a great tut on custom meta boxes! I’ve used your code as a base for minor modifications due to my need of having more than one field in the MetaBox. However, unless I specifically create two Custom Fields, and name them the same as I have for the MetaBox fields, the specific information is not shown in the MetaBox? As well, if I make a change to the info in the MetaBox fields it is not saved, however if I change the data in the custom fields value both fields are updated? NB. I won’t want to have the custom fields when the MetaBox works! My Code is below:


    /* Fire our meta box setup function on the page editor screen. */
    add_action( 'load-page.php', 'stock_info_meta_boxes_setup' );
    add_action( 'load-page-new.php', 'stock_info_meta_boxes_setup' );

    /* Meta box setup function. */
    function stock_info_meta_boxes_setup() {

    /* Add meta boxes on the 'add_meta_boxes' hook. */
    add_action( 'add_meta_boxes', 'sda_add_stock_info_meta_boxes' );

    /* Save post meta on the 'save_post' hook. */
    add_action( 'save_post', 'sda_save_stock_info_meta');

    /* Create one or more meta boxes to be displayed on the post editor screen. */
    function sda_add_stock_info_meta_boxes() {

    'stock-info-meta-box', // Unique ID
    esc_html__( 'Stock Info', 'example' ), // Title
    'show_stock_info_meta_box', // Callback function
    'page', // Admin page (or post type)
    'side', // Context
    'core' // Priority


    // custom_meta_field array
    $custom_meta_fields = array(
    'title' => 'Stock ID',
    'label' => 'Edit the Stock ID for this item',
    'desc' => 'Here you can manage the identification number for this stock item.',
    'id' => 'stock_id',
    'type' => 'text',
    'placeholder' => 'Stock ID'
    'title' => 'Price',
    'label' => 'Edit the Price for this item',
    'desc' => 'Here you can manage the cost of this item.',
    'id' => 'unit_price',
    'type' => 'text',
    'placeholder' => 'Price'

    /* Display the stock meta box. */
    function show_stock_info_meta_box() {
    global $custom_meta_fields, $post;
    // Use nonce for verification
    wp_nonce_field( basename( __FILE__ ), 'manage_stock_info_nonce' );

    // Begin the field loop
    foreach ($custom_meta_fields as $field) {
    // get value of this field if it exists for this post
    $meta = get_post_meta($post->ID, $field['id'], true);

    // create the new fields for each entry needed
    echo '<h4 title="'.$field['desc'].'">'.$field['title'].'</h4>';
    echo '<label class="invisible" title="'.$field['desc'].'" for="'.$field['id'].'">'.$field['label'].'</label>';
    echo '<input title="'.$field['desc'].'" class="widefat" type="text" placeholder="'.$field['placeholder'].'" name="'.$field['id'].'" id="'.$field['id'].'" value="'.$meta.'" size="30" />';

    /* Save the meta box's post metadata. */
    function sda_save_stock_info_class_meta( $post_id, $post ) {
    global $custom_meta_fields;

    /* Verify the nonce before proceeding. */
    if ( !isset( $_POST['manage_stock_info_nonce'] ) || !wp_verify_nonce( $_POST['manage_stock_info_nonce'], basename( __FILE__ ) ) )
    return $post_id;

    /* Get the post type object. */
    $post_type = get_post_type_object( $post->post_type );

    /* Check if the current user has permission to edit the post. */
    if ( !current_user_can( $post_type->cap->edit_post, $post_id ) )
    return $post_id;

    // loop through fields and save the data
    foreach ($custom_meta_fields as $field) {
    /* Get the posted data and sanitize it for use. */
    $new_meta_value = ( isset($_POST[$field['id']])? sanitize_text_field($_POST[$field['id']]) : '' );

    /* Get the original meta value of the custom field key. */
    $old_meta_value = get_post_meta($post_id, $field['id'], true);

    /* If a new meta value was added and there was no previous value, add it. */
    if ( $new_meta_value && '' == $old_meta_value ){
    add_post_meta($post_id, $field['id'], $new_meta_value, true );
    /* If the new meta value does not match the old value, update it. */
    elseif ($new_meta_value && $new_meta_value != $old_meta_value) {
    update_post_meta($post_id, $field['id'], $new_meta_value);
    /* If there is no new meta value but an old value exists, delete it. */
    elseif ('' == $new_meta_value && $old_meta_value) {
    delete_post_meta($post_id, $field['id'], $old_meta_value);
    } // end foreach



  10. 562

    GREAT post!
    I have one question, is it posible to hide this data from the custom fields section.

  11. 613

    Thank you!! Works fine!

  12. 664

    Thanks, very helpful post! Keep simplicity this way :)

  13. 715

    Nuruzzaman Sheikh

    April 19, 2013 9:52 pm

    Very detailed and to the point post. It was really helpful for me. Thanks a lot for this awesome tutorial!

  14. 766

    Hello Justin,

    I created a plugin leveraging custom meta boxes. I love it, works great and really unleashes the power of WP.

    However :) there’s one aspect of it I’m not comfortable with. I tend to see a lot of empty custom meta stored into the post_meta table. Even successful and popular plugins like Yoast’s SEO plugin tend to create empty records. This could be taxing the database over time.

    Would you know a way around this ?


  15. 817

    You have covered everything in detail here. Thanks a lot!

  16. 868

    This article was useful because I had no idea that it was necessary to hook the ‘save_post’ action in order to save the metadata. I assume the reason is that the actual post does not exist until someone clicks “publish”.

    Is there anyway to post metadata that is not in the HTML? I have a metabox that reads in a file in order to get the data that I want to use as custom meta, so unlike the example above, the save_post hook can not just read it out of the HTML.

    I can’t be the first person to run into this problem, but I have run out of ideas at this point.

  17. 919

    Looks good, but adding combobox is quite difficult, Hope this article may helpful for adding combo box in meta boxes area.

  18. 970

    hi , thanks for this tutorial
    i have 3 meta box in my custome post
    i want to callback the first meta box in the special location of my theme
    and other meta box too

    please help me!! thanks

  19. 1021

    I realize this post is practically ancient now, but it was a big help building my first meta box without having a nervous breakdown. Thanks!

  20. 1072

    This is exactly the kind of information I’ve been trying to find :). Using for customising the post preview type on the home page, just had to change from a text input to a select box… Perfect.. Thank you very much!

  21. 1123

    Very nice post. I like how you really broke down what’s happening behind the scenes and what each element means. I’ve managed to make great use of Custom Meta Boxes in a theme I’m developing now.

    However, I have a problem that I thought would be so simple, but it just won’t work. I cannot get the meta box out of the side and into the main column. Here is the code I’m using.

    // add in box set
    function AMS_custom_post_meta() {
    add_meta_box( ‘AMS_piece_meta’, __( ‘Art Piece Info’, ‘ams-custom-functions’ ), ‘AMS_post_meta_callback’, ‘post’, ‘normal’, ‘high’ );
    add_action( ‘add_meta_boxes’, ‘AMS_custom_post_meta’ );

    Can anybody clue me in, or point me to something that I can study to get a better sense of how things work overall?

    • 1174

      Adrien, have you tried leaving context and priority on defaults? Check what Justin wrote in “Building a custom post meta box” section, there is every parameter of add_meta_box() explained. Try changing context and priority to other values. That should help, if it doesn’t.. problem is somewhere else. Maybe (if you didnt) you should set boxes_setup action on load-post.php.

  22. 1225

    Hello Justin!

    I have a milion request, maybe you could help me with this.

    I need a meta box named “source” with the option to add a link. Exemple

    So when I’m writing a post and put the link in the meta box, it should be automaticaly added to the last row of the post with name “source” and linked to the

    Is it possible? Can you help me?

    Here is an example.





    Thank you very much,

  23. 1276

    Thank youuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu :)

  24. 1327

    Hey Justin!

    I copied this line for line into my functions.php file and it gives me a “blank” white screen when I test it.

    I am running the newest version of WordPress locally on the newest version of MAMP and MyPHPAdmin so there “shouldn’t” be issues with that.

    Is this a common error? It is maddening! I am also using the “bones” theme and building around basic framework with no other plugins installed yet.

    Any ideas? Is this tutorial needing a refresh from the new structures in WP?


↑ Back to top