Guide to WordPress’s Custom Write Panels

With the release of WordPress 3.0, a whole slew of features were introduced, including the merging of WordPress MU into the main codebase (allowing multiple sites to be managed from one WordPress installation), and the addition of custom post types. A feature that has been around for a while, but which is now extra-interesting as a result of the new custom post types, is Custom Write Panels.

A Custom Write Panel is a box of form fields (inputs, checkboxes, radio buttons, textareas, and so on) that’s shown when you’re writing or editing a post. The fields link up to custom fields that you’ve added to posts.

Although the Custom Field Panel displays in the Edit Post page by default, it can be tricky to use, especially when you want to enter a fair amount of data. That’s where Custom Write Panels come in: they let you create slick-looking panels that allow users to enter all the additional information you want for a post.

For example, let’s create a custom post type called Books. Books will have a title and description (the title and main blog content panels), but they also need some extra information in order to display properly: an author and an ISBN (a unique numeric commercial book identifier). So, in your theme’s functions.php, you need to add this code:

add_action( 'init', 'create_book_type' );
function create_book_type() {
  register_post_type( 'books',
    array(
      'labels' => array(
        'name' => __( 'Books' ),
        'singular_name' => __( 'Book' )
      ),
      'public' => true,
    )
  );
}

What does this do?

add_action( 'init', 'create_book_type' ); tells WordPress to run our code on the init action (as soon as WordPress loads, in other words), and the register_post_type() method adds the Book post type. Now, if you view your admin dashboard, you’ll see a new Books panel in the left-hand navigation. We want to add the Custom Write Panel to this post type so, again in functions.php, we add the following code:

//We set up a few things, like where your
//theme folder is, etc.
define(
  'MY_WORDPRESS_FOLDER',
  $_SERVER['DOCUMENT_ROOT']
);
define(
  'MY_THEME_FOLDER',
  str_replace("\",'/',dirname(__FILE__))
);
define(
  'MY_THEME_PATH',
  '/' . substr(
    MY_THEME_FOLDER,
    stripos(MY_THEME_FOLDER,'wp-content')
  )
);

//This initializes the write panel.
add_action('admin_init','book_meta_init');

function book_meta_init() {
  //This adds our CSS file,
  //so our write panels look pretty.
  wp_enqueue_style(
    'my_meta_css',
    MY_THEME_PATH . '/custom/book_panel.css'
  );

  //This method is the one that actually adds the
  //write panel, named 'Book Information' to the
  //post type 'books'
  add_meta_box(
    'book_meta',
    'Book Information',
    'book_meta',
    'books',
    'advanced',
    'high'
  );
}

// The function below links the panel
// to the custom fields
// ---------------------------------
function book_meta() {
  global $post;
  //The two variables.
  $author = get_post_meta($post->ID,'author',TRUE);
  $isbn = get_post_meta($post->ID,'isbn',TRUE);

  //Call the write panel HTML
  include(MY_THEME_FOLDER .
      '/custom/book_information.php');

  // create a custom nonce for submit
  // verification later
  echo '';
}

//The function below checks the
//authentication via the nonce, and saves
//it to the database.
function my_meta_save($post_id) {
  // authentication checks
  // make sure data came from our meta box
  if(!wp_verify_nonce(
      $_POST['my_meta_noncename',__FILE__)
  ) return $post_id;
  if (!current_user_can('edit_post', $post_id)) {
    return $post_id;
  }
  // The array of accepted fields for Books
  $accepted_fields['books'] = array(
    'author',
    'isbn'
  );
  $post_type_id = $_POST['post_type'];

  //We loop through the list of fields,
  //and save 'em!
  foreach($accepted_fields[$post_type_id] as $key){
    // Set it to a variable, so it's
    // easier to deal with.
    $custom_field = $_POST[$key];

    //If no data is entered
    if(is_null($custom_field)) {

      //delete the field. No point saving it.
      delete_post_meta($post_id, $key);

      // If it is set (there was already data),
      // and the new data isn't empty, update it.
    }
    elseif(isset($custom_field)
&& !is_null($custom_field))
    {
      // update
     update_post_meta($post_id,$key,$custom_field);

      //Just add the data.
    } else {
      // Add?
      add_post_meta($post_id, $key,
        $custom_field, TRUE);
    }
  }
  return $post_id;
}

The code comments should make the workings of this snippet fairly evident, so onto the next step: adding the HTML for the Custom Write Panel. Inside your theme folder, create a new directory called custom and add two files: a CSS file for styling the panel’s looks, and the HTML so it displays the fields properly. The CSS file should be named book_panel.css and contain this code :

.book_panel .description {
  display: none;
}

.book_panel label {
  display: block;
  font-weight: bold;
  margin: 6px;
  margin-bottom: 0;
  margin-top: 12px;
}

.book_panel label span {
  display: inline;
  font-weight: normal;
}

.book_panel span {
  color: #999;
  display: block;
}

.book_panel textarea,
.book_panel input[type='text'] {
  margin-bottom: 3px;
  width: 100%;
}

.book_panel h4 {
  color: #999;
  font-size: 1em;
  margin: 15px 6px;
  text-transform:uppercase;
}

The HTML file should be named
book_information.php, and it should contain this code:

<div class="book_panel">

This panel contains information
for displaying the book

  <label>Author</label>
  <!-- The php checks if there is existing
       data to pre-fill the input fields -->

  <input type="text" name="author" value="<?php
    if(!empty($author)) echo $author;
  ?>"/>

  <label>ISBN</label>
  <!-- The php checks if there is
    existing data to pre-fill the input fields -->
  <input type="text" name="isbn" value="<?php
    if(!empty($isbn)) echo $isbn;
  ?>"/>
</div>

If you’ve followed the steps, when you add a Book in the Books panel, a new box of fields (containing Author and the ISBN) will display. To display these fields in your theme, call the posts via the loop and grab the custom field information:

$loop = new WP_Query( array(
  'post_type' => 'books',
  'posts_per_page' => 10 )
);
while ($loop->have_posts()) : $loop->the_post();
  the_title();
  the_content();
  echo 'Author: ' .
get_post_meta($post->ID, "author", true);
  echo 'ISBN: ' .
get_post_meta($post->ID, "isbn", true);
endwhile;

That’s it: your posts will display with the custom information. If you want to learn more about WordPress, check out our latest release Build Your Own Wicked WordPress Themes. Enjoy!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • DAvid

    Great article, thanks! Been wondering for a while now how best to go about creating something like this, so thank you!

  • Scott Petrovic

    This looks really useful. I need to learn more advanced wordpress customizations like this, so this will be great. Thanks for the post

  • Steve

    What a helpful way to let us know what your new book contains. Very valuable!

  • astrotim

    This looks really useful. It’s like a mini plugin inside your own theme – how cool! I noticed the plug for the new Sitepoint book at the end – does the book cover topics such as WP3 custom post types and this kind of additional customisation?

  • guest

    thanks

  • MarkRidgway

    I’m just getting parse error after parse error.

    • Dan

      I haven’t tried this code yet myself, but it looks like the start ‘code’ and close ‘code’ tags are reversed in line 23 of the first example above, at the end of the book_meta function declaration.

      Hope this is helpful.

      @Mark

      Cool post! This looks like the cleanest way of doing this I have seen so far, especially separating the panel html and css files from the functions file. Keep up the good work!

  • Martin

    There’s a typo in line 61 of the second codebox, must say:

    if ( !wp_verify_nonce($_POST['my_meta_noncename'], __FILE__) ) return $post_id;

    You missed the apostrophe and a closing bracket.

  • Dimas

    I would like to introduce a PHP Helper Class/plugin for simplify the creation of write panels … WPAlchemy MetaBox (wpalchemy.com) … I’ve written all the code above before, and its sucks to do it over and over, so I created the above … hope its useful to folks …

  • Joeke

    You are defining constants that are totally unneccesary and wrong. WordPress has it’s own constants for MY_WORDPRESS_FOLDER, MY_THEME_FOLDER and MY_THEME_PATH.
    Check out this article at yoast.com for the correct way to do this:
    http://yoast.com/paths-urls-wordpress/

  • afnet

    Great to see how to use these new features of WordPress. Thanks!

    However, I was concerned to see code like this:
    //We set up a few things, like where your
    //theme folder is, etc.
    define(
    ‘MY_WORDPRESS_FOLDER’,
    $_SERVER['DOCUMENT_ROOT']
    );

    because my WordPress folder is not in the root but in a subdirectory.

    I was very relieved to see Joeke had already noticed this and posted a correction:-
    http://yoast.com/paths-urls-wordpress/

    Can I suggest you please correct some of these misleading definitions and make this a really great article!

    Thanks.

  • Thad

    I’ve followed all the steps and even corrected the error on line 61 of the second code block however whenever I add content to my write panel it is not saving. Anybody have any ideas why not?

    • Mark Cipolla

      Check that the names of variables are all correct… This caught me out a lot.

      In the book_meta() function:
      $author = get_post_meta($post->ID,’author’,TRUE);
      $isbn = get_post_meta($post->ID,’isbn’,TRUE);

      These pass the variables to the custom post panel, which uses them:
      Author

      <input type="text" name="author" value="”/>

      ISBN

      <input type="text" name="isbn" value="”/>

      That displays the content (if available), and passes it back on submit to the my_meta_save() function

      $accepted_fields['books'] = array(
      ‘author’,
      ‘isbn’
      );

      which loops through the list and saves them.

      If the variable names don’t all match up, the meta gets saved… but not what you want them saved as. Hope this helps.

      • Thad

        Thanks for the response, Mark!

        Interestingly enough my variable names are 100% correct and even copying and pasting the exact code in the article gives me the same problem. I’m wondering if this has something to do with the server using? I’m running on a linux server with PHP 4.3.9 and MySQL 4.1.20.

        Thanks again for trying to help

        • Yassen

          Terribly late but for the sake of other people that may stumble upon this:

          The reason it dos not save the data is a missing call that should look something like:

          add_action( ‘save_post’, ‘my_meta_save’, 3, 1 );

          Also, I had to comment the line checking the nonce in my_meta_save():

          // if ( ! wp_verify_nonce($_POST['my_meta_noncename'], __FILE__) ) return $post_id;

          Now it works, indeed.

          • Chanon Srithongsook

            It’s good to keep wp_verify_nonce as another ‘save_post’ action could trig this function.

            You may need to add

            wp_nonce_field(‘my_meta_noncename’, __FILE__);

            in function book_meta() to make it work without delete the verification part in the save function

  • upcoming movie trailers

    Top blog, I hadn’t come across http://www.boldfish.co.uk previously in my searches!
    Carry on the wonderful work!