User-friendly custom fields with Meta Boxes in WordPress

User-friendly custom fields with Meta Boxes in WordPress

  • Knowledge needed: PHP, experience with WordPress
  • Requires: WordPress
  • Project time: 30 minutes

Ryan Taylor explains how to add metadata to a WordPress post by creating and adding Meta Boxes and improving the usability of custom fields

In a previous tutorial I talked about Post Formats, a new feature introduced in WordPress 3.1. A Post Format is a piece of meta information that can be assigned to individual posts and used by a theme to customise the presentation of those posts. I also discussed the current limitations of Post Formats in WordPress when compared to services like Tumblr. With Tumblr, whichever Post Format you select determines the form fields youre presented with. In Wordpress, by default, you only have the title and post editor fields.

We can of course add additional metadata to a post by using Custom Fields:

The problem with Custom Fields is they’re not very user-friendly. If you’re designing for a client you need to explain what they are, what needs to be entered and also hope they don’t forget to do it.

This is where Meta Boxes come in handy!

What are Meta Boxes?

A Meta Box is a custom section that can be added to a post/page screen. It can be used to group Custom Fields together and give them context. Here’s a Meta Box that I use in conjunction with Post Formats for my blog (ryanhavoctaylor.com):

You can have as many form fields as you need in a Meta Box as well as use other input types such as checkboxes and radio buttons. Under the hud, they’re still Custom Fields and you call the values in your theme the exact same way:

  1. get_post_meta($post_id, $key, $single);

We’re just making the admin UI more usable with labels, instructions and better formatting.

Create a Meta Box

When I build WordPress themes I use Meta Boxes with my posts all the time and often I have Custom Post Types (codex.wordpress.org/Post_Types) registered that I also want to have Meta Boxes applied to.

The code I’m now going to talk you through will enable you to quickly add Meta Boxes to any and all of your themes Post Types.

We start off by adding the following code to our functions.php template file (if this file doesn’t exist in your theme then create it):

  1. //We create an array called $meta_box and set the array key to the relevant post type
  2. $meta_box['post'] = array(
  3.    
  4.     //This is the id applied to the meta box
  5.     'id' => 'post-format-meta',  
  6.    
  7.     //This is the title that appears on the meta box container
  8.     'title' => 'Additional Post Format Meta',    
  9.    
  10.     //This defines the part of the page where the edit screen section should be shown
  11.     'context' => 'normal',    
  12.    
  13.     //This sets the priority within the context where the boxes should show
  14.     'priority' => 'high',
  15.    
  16.     //Here we define all the fields we want in the meta box
  17.     'fields' => array(
  18.         array(
  19.             'name' => 'Link - URL',
  20.             'desc' => 'URL for the link',
  21.             'id' => 'pf_link_url',
  22.             'type' => 'text',
  23.             'default' => ''
  24.         ),
  25.         array(
  26.             'name' => 'Quote - Source',
  27.             'desc' => '(Optional) URL to the quote source',
  28.             'id' => 'pf_quote_source',
  29.             'type' => 'text',
  30.             'default' => ''
  31.         )
  32.     )
  33. );

This is simply an array declaration that we will pass to a series of functions to create our Meta Box. We’re going to look at those functions next, but once they’re written, all you’ll need to do is add array entries like the one above to your function.php file to quickly add Meta Boxes to your themes.

The above entry will add the Additional Post Format Meta box that I use on my blog to the post screen.

If you’re using Custom Post Types you can add a Meta Box to them as well by adding another array entry. Here’s an example for a Post Type called Books:

  1. $meta_box['books'] = array(
  2.     'id' => 'book-meta-details',
  3.     'title' => 'Book Meta Details',
  4.     'context' => 'normal',
  5.     'priority' => 'high',
  6.     'fields' => array(
  7.         array(
  8.             'name' => 'Summary',
  9.             'desc' => '(Max 45 words) Note: Leave all other fields blank if the book is not on sale yet.',
  10.             'id' => 'book_summary',
  11.             'type' => 'textarea',
  12.             'default' => ''
  13.         ),
  14.         array(
  15.             'name' => 'Buy Now URL:',
  16.             'desc' => '',
  17.             'id' => 'book_ buy_now_link',
  18.             'type' => 'text',
  19.             'default' => ''
  20.         ),
  21.         array(
  22.             'name' => 'Price:',
  23.             'desc' => 'e.g. £9.95',
  24.             'id' => 'book_price',
  25.             'type' => 'text',
  26.             'default' => ''
  27.         ),
  28.         array(
  29.                 'name' => 'Book is on sale?',
  30.             'desc' => '',
  31.             'id' => 'book_on_sale',
  32.             'type' => 'checkbox',
  33.             'default' => ''
  34.           ),
  35.         array(
  36.             'name' => 'Sample PDF Url',
  37.             'desc' => '(Optional) Link to a sample PDF.',
  38.             'id' => 'book_sample_url',
  39.             'type' => 'text',
  40.             'default' => ''
  41.         )
  42.     )
  43. );

Notice that we change the array key to reflect the post type that the entry applies to. Once you’ve created all the array entries that you want you need to add the following line of code to run the functions that will actually create the Meta Box(es).

  1. add_action('admin_menu', 'plib_add_box');

Note that this line only needs to be run once.

Keeping everything tidy

This is an optional step, but to keep everything tidy I’ve created a file called preset-library.php, which I add to my themes and include at the top of function.php like so:

  1. include('preset-library.php');

I find separating functions that never change from the code that calls them easier to work with than having everything clumped together.

If you want to do this too, add the following functions to preset-library.php, otherwise add them to the top of functions.php.

Adding the Meta Box

The first function is a relatively simple one.

  1. //Add meta boxes to post types
  2. function plib_add_box() {
  3.     global $meta_box;
  4.    
  5.     foreach($meta_box as $post_type => $value) {
  6.         add_meta_box($value['id'], $value['title'], 'plib_format_box', $post_type, $value['context'], $value['priority']);
  7.     }
  8. }

We loop through the $meta_box array we defined earlier and pass each entry into the WordPress function add_meta_box (codex.wordpress.org/Function_Reference/add_meta_box). You’ll see that one of the parameters for this function is a callback to another function called plib_format_box. We’ll cover this one next.

Formatting the Meta Box

This function applies the HTML formatting within the Meta Box for each input field.

  1. //Format meta boxes
  2. function plib_format_box() {
  3.   global $meta_box, $post;
  4.  
  5.   // Use nonce for verification
  6.   echo '<input type="hidden" name="plib_meta_box_nonce" value="', wp_create_nonce(basename(__FILE__)), '" />';
  7.  
  8.   echo '<table class="form-table">';
  9.  
  10.   foreach ($meta_box[$post->post_type]['fields'] as $field) {
  11.       // get current post meta data
  12.       $meta = get_post_meta($post->ID, $field['id'], true);
  13.  
  14.       echo '<tr>'.
  15.               '<th style="width:20%"><label for="'. $field['id'] .'">'. $field['name']. '</label></th>'.
  16.               '<td>';
  17.       switch ($field['type']) {
  18.           case 'text':
  19.               echo '<input type="text" name="'. $field['id']. '" id="'. $field['id'] .'" value="'. ($meta ? $meta : $field['default']) . '" size="30" style="width:97%" />'. '<br />'. $field['desc'];
  20.               break;
  21.           case 'textarea':
  22.               echo '<textarea name="'. $field['id']. '" id="'. $field['id']. '" cols="60" rows="4" style="width:97%">'. ($meta ? $meta : $field['default']) . '</textarea>'. '<br />'. $field['desc'];
  23.               break;
  24.           case 'select':
  25.               echo '<select name="'. $field['id'] . '" id="'. $field['id'] . '">';
  26.               foreach ($field['options'] as $option) {
  27.                   echo '<option '. ( $meta == $option ? ' selected="selected"' : '' ) . '>'. $option . '</option>';
  28.               }
  29.               echo '</select>';
  30.               break;
  31.           case 'radio':
  32.               foreach ($field['options'] as $option) {
  33.                   echo '<input type="radio" name="' . $field['id'] . '" value="' . $option['value'] . '"' . ( $meta == $option['value'] ? ' checked="checked"' : '' ) . ' />' . $option['name'];
  34.               }
  35.               break;
  36.           case 'checkbox':
  37.               echo '<input type="checkbox" name="' . $field['id'] . '" id="' . $field['id'] . '"' . ( $meta ? ' checked="checked"' : '' ) . ' />';
  38.               break;
  39.       }
  40.       echo     '<td>'.'</tr>';
  41.   }
  42.  
  43.   echo '</table>';
  44.  
  45. }

The function loops through the fields section of each entry of the $meta_box array and prints out the supplied values.

Saving data from the Meta Box

Finally we need to tell WordPress that the fields exist and how to save them with the Post.

  1. // Save data from meta box
  2. function plib_save_data($post_id) {
  3.     global $meta_box,  $post;
  4.    
  5.     //Verify nonce
  6.     if (!wp_verify_nonce($_POST['plib_meta_box_nonce'], basename(__FILE__))) {
  7.         return $post_id;
  8.     }
  9.  
  10.     //Check autosave
  11.     if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
  12.         return $post_id;
  13.     }
  14.  
  15.     //Check permissions
  16.     if ('page' == $_POST['post_type']) {
  17.         if (!current_user_can('edit_page', $post_id)) {
  18.             return $post_id;
  19.         }
  20.     } elseif (!current_user_can('edit_post', $post_id)) {
  21.         return $post_id;
  22.     }
  23.    
  24.     foreach ($meta_box[$post->post_type]['fields'] as $field) {
  25.         $old = get_post_meta($post_id, $field['id'], true);
  26.         $new = $_POST[$field['id']];
  27.        
  28.         if ($new && $new != $old) {
  29.             update_post_meta($post_id, $field['id'], $new);
  30.         } elseif ('' == $new && $old) {
  31.             delete_post_meta($post_id, $field['id'], $old);
  32.         }
  33.     }
  34. }
  35.  
  36. add_action('save_post', 'plib_save_data');

Conclusion

And that’s it! If your themes need more information than the basic title and post editor fields then Meta Boxes are a great way to improve the usability of Custom Fields for the user, whether they’re technical or not.

11 comments

Comment: 2

Thanks Ryan,
Just what I was looking for.
I'm getting an 'unexpected $end' error and wondering if the Formatting the Meta Box function is missing a line at the end?

Comment: 4

Thank you, Ryan. This was great!

I'm a php novice and I wanted to know if you could add a dropdown box instead of one of the text fields. I have a 'country' field and would like a dropdown menu.

Thanks.

Comment: 5

Hi
Really hope you can help with this, I am using WP3.2 and trying to implement this excellent solution for adding Custom Field Write Panels. I have implemented everything as above (in fact I have implemented this solution on a different site with success) with the one difference that I am adding this to a custom_post of "course" but php_error log is giving me;

[29-Jul-2011 19:41:44] PHP Parse error: syntax error, unexpected T_STRING in /Applications/MAMP/htdocs/wordpress/wp-content/themes/theme_name/custom_fields/preset-library.php on line 5

Line 5 is as above;
    global $meta_box;

Please help! - even if you could point me in the right direction, I have tweaked and tweaked to no effect..

Thanks for a fantastic resource and magazine!
Adam

Comment: 6

how to remove/disable drag & drop feature of the metabox?

Comment: 7

I'm having trouble implementing the custom meta values onto my template. Can't seem to find the correct "key/array value" combination.

Comment: 8

This is a great post.

I've got it all working in the back-end except how would I create a loop to retrieve each value inside a single post?

Thanks

Comment: 9

Waiting for OOP version for automatically creating this metabox :).

Comment: 10

Hi,
How can i save custom field with Ajax??

Thanks

Comment: 11

HI , I dunt understand how to display the content form the fields. You wrote that I could use get_post_meta($post_id, $key, $single); but it dosnt works for me for the fields in the box..Could you give me an exampel how to output if the array looks like :

//We create an array called $meta_box and set the array key to the relevant post type
$meta_box['post'] = array(

//This is the id applied to the meta box
'id' => 'post-format-meta',

//This is the title that appears on the meta box container
'title' => 'Additional Post Format Meta',

//This defines the part of the page where the edit screen section should be shown
'context' => 'normal',

//This sets the priority within the context where the boxes should show
'priority' => 'high',

//Here we define all the fields we want in the meta box
'fields' => array(
array(
'name' => 'Link - URL',
'desc' => 'URL for the link',
'id' => 'pf_link_url',
'type' => 'text',
'default' => ''
),

thanks! / Emma
August issue on sale now!

The Week in Web Design

Sign up to our 'Week in Web Design' newsletter!

Hosting Directory
.net digital edition
Treat yourself to our geeky merchandise!
site stat collection