Blog

Creating Custom Meta Boxes in WordPress

In Using the WordPress Media Uploader With Custom Fields, I demonstrated how to integrate the WordPress media uploader to get a file url using the native media uploader in WordPress. In this post, I’ll show you how to create a custom meta box to house your plugin/theme specific meta data in its own meta box on the edit page/post page. The meta box we’ll be creating is shown below.

Wordpress Edit Post Screen Showing Custom Meta Box

There are 3 steps in creating a custom meta box in wordpress:

  1. Register a hook on the admin_menu action and call add_meta_box() in your function to register a new meta box.
  2. Create a function that outputs the HTML for your meta box. You passed the name of this function to add_meta_box in step 1.
  3. Register a hook on the save_post action and create a function that will save the data in your meta box to the database.
  4. If you are integrating the media uploader, as we are, you will also need to register a hook on the admin_head action and embed the needed JavaScript to obtain URLs from the uploader. For more info on integrating the media uploader, see Using the WordPress Media Uploader With Custom Fields.
  5. The example code below shows how to create a custom meta box that saves an image as meta data with the page. The user can upload and choose image using the native WordPress media uploader so there is no need to hunt down URLs.

    Note that before saving the URL, we are converting it to an attachment id. This makes the field much more useful in the rest of your theme and potentially saves space in your database.

    <?php
    add_action('admin_menu', 'addMetaBox');
    add_action('save_post', 'saveMetaData', 10, 2);
    add_action('admin_head', 'embedUploaderCode');
     
    //Define the metabox attributes.
    $metaBox = array(
      'id'     => 'my-meta-box',
      'title'    => 'My Meta Box',
      'page'     => 'page',
      'context'  => 'side',
      'priority'   => 'low',
      'fields' => array(
        array(
          'name'   => 'My Custom Image',
          'desc'   => 'A Custom Image Displayed On Your Site Somewhere.',
          'id'  => 'myCustomImage',  //value is stored with this as key.
          'class' => 'image_upload_field',
          'type'   => 'media'
        )
      )
    );
     
    function addMetaBox() {
      global $metaBox;
      add_meta_box($metaBox['id'], $metaBox['title'], 'createMetaBox', 
        $metaBox['page'], $metaBox['context'], $metaBox['priority']);
     
    }
     
    /**
    * Create Metabox HTML.
    */
    function createMetaBox($post) {
      global $metaBox;
      if (function_exists('wp_nonce_field')) {
        wp_nonce_field('awd_nonce_action','awd_nonce_field');
      }
     
      foreach ($metaBox['fields'] as $field) {
        echo '<div class="awdMetaBox">';
        //get attachment id if it exists.
        $meta = get_post_meta($post->ID, $field['id'], true);
        switch ($field['type']) {
          case 'media':
    ?>
            <p><?php echo $field['desc']; ?></p>
            <div class="awdMetaImage">
    <?php 
            if ($meta) {
              echo wp_get_attachment_image( $meta, 'thumbnail', true);
              $attachUrl = wp_get_attachment_url($meta);
              echo 
              '<p>URL: <a target="_blank" href="'.$attachUrl.'">'.$attachUrl.'</a></p>';
            }
    ?>    
            </div><!-- end .awdMetaImage -->
            <p>
              <input type="hidden" 
                class="metaValueField" 
                id="<?php echo $field['id']; ?>" 
                name="<?php echo $field['id']; ?>"
                value="<?php echo $meta; ?>" 
              /> 
              <input class="image_upload_button"  type="button" value="Choose File" /> 
              <input class="removeImageBtn" type="button" value="Remove File" />
            </p>
     
    <?php
          break;
        }
        echo '</div> <!-- end .awdMetaBox -->';
      } //end foreach
    }//end function createMetaBox
     
     
    function saveMetaData($post_id, $post) {
      //make sure we're saving at the right time.
      //DOING_AJAX is set when saving a quick edit on the page that displays all posts/pages  
      //Not checking for this will cause our meta data to be overwritten with blank data.
      if ( empty($_POST)
        || !wp_verify_nonce($_POST['awd_nonce_field'],'awd_nonce_action')
        || $post->post_type == 'revision'
        || defined('DOING_AJAX' )) {
        return;
      }
     
      global $metaBox;
      global $wpdb;
     
      foreach ($metaBox['fields'] as $field) {
        $value = $_POST[$field['id']];
     
        if ($field['type'] == 'media' && !is_numeric($value) ) {
          //Convert URL to Attachment ID.
          $value = $wpdb->get_var(
            "SELECT ID FROM $wpdb->posts 
             WHERE guid = '$value' 
             AND post_type='attachment' LIMIT 1");
        }
        update_post_meta($post_id, $field['id'], $value);
      }//end foreach
    }//end function saveMetaData
     
    /**
     * Add JavaScript to get URL from media uploader.
     */
    function embedUploaderCode() {
      ?>
      <script type="text/javascript">
      jQuery(document).ready(function() {
     
        jQuery('.removeImageBtn').click(function() {
          jQuery(this).closest('p').prev('.awdMetaImage').html('');   
          jQuery(this).prev().prev().val('');
          return false;
        });
     
        jQuery('.image_upload_button').click(function() {
          inputField = jQuery(this).prev('.metaValueField');
          tb_show('', 'media-upload.php?TB_iframe=true');
          window.send_to_editor = function(html) {
            url = jQuery(html).attr('href');
            inputField.val(url);
            inputField.closest('p').prev('.awdMetaImage').html('<p>URL: '+ url + '</p>');  
            tb_remove();
          };
          return false;
        });
      });
     
      </script>
      <?php
    }//end function embedUploaderCode()

    At first glance, it looks like a lot of work, and it is. But you only need to do it once. You’ll probably want to make this into a generic code library that you can include in each of your themes or plugins that need it. For a more in depth tutorial showing how to create such a library, check out the excellent tutorial, How To Create A Better Meta Box In WordPress Post Editing Page.

    Let me know if this was helpful or if you had any problems implementing the custom meta box by leaving a comment below.

23 Responses

  1. 1

    Great post, super-helpful. Thanks!

  2. 2
    Gary

    Testing against 3.1 trunk, and getting an error, as $_REQUEST['post_id'] is not present (it is when clicking Set Featured Image) when adding this for a custom post type.

    Stuck for ideas :(

  3. 3
    Melinda

    After choosing an image using the uploader there is no link to add to post or use as a featured image. The image never shows up in my meta box and neither does the url. Any ideas?

    • After you upload a new image, you have to go to the “Media Library” tab, click show next to the image you just uploaded. Here you should see the “Insert into Post” button. Let me know if this doesn’t work for you.

  4. 4

    Just used this again on another WP site, and I had an error that every time I hit “Insert into Post”, I’d get an “undefined” message back in the box. After some digging, I found that the javascript was capturing the wrong value. CODE: [ url = jQuery(html).attr('href'); ] should be [ url = jQuery(html).attr('src'); ]. Thanks again for this!

  5. 5

    The javascript binds window.send_to_editor , but does not surrender it back. You’ll have an issue if you want to add an image to the post or the Featured Image.

  6. 6

    Great post!

    What is the license for this code? Can I edit the code and turn this into a plugin and upload it to wordpress.org for example?

  7. 7
    Darcy

    Thanks for the tutorial!

    I discovered a bug and worked out a fix. If you use the attachment post URL instead of the media file URL, it fails to get the post_id on subsequent updates.

    function saveMetaData($post_id, $post) {
    //make sure we’re saving at the right time.
    //DOING_AJAX is set when saving a quick edit on the page that displays all posts/pages
    //Not checking for this will cause our meta data to be overwritten with blank data.

    if ( empty($_POST)
    || !wp_verify_nonce($_POST['awd_nonce_field'],’awd_nonce_action’)
    || $post->post_type == ‘revision’
    || defined(‘DOING_AJAX’)
    ){

    return;
    }

    global $metaBox;
    global $wpdb;

    foreach ($metaBox['fields'] as $field) {
    $value = $_POST[$field['id']];

    //Convert URL to Attachment ID.
    if ($field['type'] == ‘media’ && !is_numeric($value) ) {

    // do we have Attachment post URL
    if(preg_match(‘/attachment_id=(\d*)$/’,$value, $matches)) {
    $value = $matches[1];
    }
    // Or media file URL
    else {
    $value = $wpdb->get_var(
    “SELECT ID FROM $wpdb->posts
    WHERE guid = ‘$value’
    AND post_type=’attachment’”);
    }

    }

    update_post_meta($post_id, $field['id'], $value);
    }//end foreach
    }//end function saveMetaData

  8. 8

    Excellent explanation to create a Metabox but if you don’t recover the old send_to_editor function you can’t upload pictures in the editor until you refresh the page,
    and for this particular feature I recommend use
    add_theme_support(‘post-thumbnails’, array(‘page’));

    you have a great site,
    Greetings.

  9. 9

    Hi! Thanks for this awesome script!

    I have trouble implementing though. I’m using the script with another which limits the meta box to a single page ID. The custom field takes the attachment ID as its value instead of the URL.

    Any thoughts?

  10. 10

    Ah! Figured it out, nevermind. Thanks again!

  11. 11
    Mj

    Great article. i will update my code to this approach.
    thanks

  12. Pingback: XPro Creative » Best Blog Tools

  13. 12
    Chris

    Hey . Great tut.

    But how do i get the attachement in my custom Page?

    ID, $field['id'], true);
    $attachUrl = wp_get_attachment_url($meta);
    echo $attachUrl;

    ?>

    This wont work.

    Regards
    Chris

  14. 13
    Chris

    Never mind. forgot the foreach

    foreach ($metaBox['fields'] as $field) {
    $meta = get_post_meta($post->ID, $field['id'], true);
    $attachUrl = wp_get_attachment_url($meta);
    echo $attachUrl;

    }

  15. 14
    dan

    Great Article! I ended up changing the code a little to write to a input box which was already being saved to a custom post type

  16. 15
    Adam

    Hi Mitchell, great article, really helpful! It’s all up and running, however like Pedro pointed out, you can’t then add an image into the post without refreshing. He hinted at a solution to this but I’m not sure how to implement it, I don’t suppose you’re able to offer a starting point?

    • Adam

      Also, the media gallery that opens up is different from the gallery that is used for the ‘Add Media’ button, is there a way to use the ‘Add Media’ one, which feels a bit faster and hides a lot of the information?

      Finally, you may have deliberately wanted inputs, but I changed them in the createMetaBox() function to:

      Choose File
      Remove File

      So they are more aesthetically inline with the admin interface!

      • Adam

        They were supposed to be anchor tags with a class of button as well as the input classes, but the comments section obviously eats HTML!