Home / Advanced / Complex Meta Boxes in WordPress

Complex Meta Boxes in WordPress

Introduction

The present article will discuss the finer details of adding meta boxes to WordPress write screens. It will cover the WordPress functions involved in the process, as well as give in depth information about the necessary hooks involved in the process. You will learn how to display and save meta boxes. After working through this article, you will have the capability of adding your own meta boxes to your projects!

Displaying Meta Boxes with the add_meta_box Function

The     add_meta_box


function is the primary function that allows for information to be added to the add/edit screens for WordPress post types. This powerful function is easy to leverage and can lead to customized content being added to WordPress write screens.

The  add_meta_box


function takes seven arguments. The arguments with each argument’s data type, required/optional status, and default value are listed below.

Argument Data Type Required Optional Default
$id string X No default
$title string X No default
$callback string X No default
$page string X No default
$context string X advanced
$priority string X required
$callback_args array X null

To understand the purpose and influence of each argument, each will be discussed separately.

$id


The $id argument defines the internal name for the meta box. WordPress core determines order to display meta boxes and screens to display meta boxes on based on this argument. Internally, this reference to the meta box is utilized in numerous arrays to reference the meta box. Obviously, this is a crucial piece of information to send to the add_meta_box function. Externally, the value of the argument is used for the id attribute in the div that encompasses the meta box. Changing this value has no effect on the display of the meta box unless CSS rules have been applied to the meta box using the id attribute. While the add_meta_box will execute with any string, it is advisable that you only use characters that you would normally use for HTML id and class attributes: 0-9, a-z, ‘-’, and ‘_’. Spaces should also be avoided. It is also recommended that the $id argument is prefaced with a unique identifier. For example, in building a plugin called “Weather Report” that adds a meta box for attaching the current temperature to a post, it would be sensible to add “wr” to the beginning of the $id value. As such, an appropriate $id value would be “wr_weather_box”. By prefixing the $id value, clashes with other plugin utilizing similar values will be reduced and many headaches will be avoided.

Usage Tips:

  • Use only the following characters: 0-9, a-z, ‘-’, and ‘_’
  • Prefix with a unique string that represents your plugin or theme (e.g., “wb” for “Weather Box”

$title

The $title argument is only slightly more interesting than the $id argument. The $title argument controls the value that will be printed for the meta box label. This argument will find itself nestled nicely inside a span and h3 tag within the main meta box div. For instance, if the $title argument is set as “Current Weather”, the following HTML would be generated:

 

</code>
<div id="highlighter_512919" class="syntaxhighlighter  ">
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>001</code></td>
<td class="content"><code class="plain"><</code><code class="keyword">h3</code> <code class="color1">class</code><code class="plain">=</code><code class="string">'hndle'</code><code class="plain">><</code><code class="keyword">span</code><code class="plain">>Current Weather</</code><code class="keyword">span</code><code class="plain">></</code><code class="keyword">h3</code><code class="plain">></code></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<code>


Adding the text for the h3 element is all that the $title argument does. It is utilized only for labeling the meta box. As such, the argument value must be a string. It is recommended that the value of the $title argument is descriptive enough that the user has a good understanding of the purpose of the meta box. For instance, something like “Current Weather” is much easy to understand than “Awesome Weather Meta Box”. Additionally, parsimonious use of words is recommended to avoid the uglification of a user’s write screen. The following image shows the difference between using a simple two word title versus a nine word title.

 

The final recommendation for this argument is to make it localizable. A discussion of localization in WordPress is well beyond the scope of this article. Please refer to the WordPress Codex for an excellent discussion of localization in WordPress. For the $title argument, the __ function should be utilized with the desired string and the text domain for the plugin or theme.

001 __('Current Weather', 'wr_weather_box')

Usage Tips:

  • Use a descriptive title
  • Avoid a lengthy title as it will cause display problems
  • Use the l18n WordPress functions __ or _x with an appropriate text domain

$callback

In setting up a meta box, the $callback argument is one of the most essential arguments. It controls the function that is called to generate the HTML for the meta box. The function that is specified is responsible for generating the content that the meta box displays. Without it, the meta box would display a label with no content. The HTML generated by the callback function will be displayed in a div with class “inside”, which is inside the overall meta box div wrapper. The HTML will look like:

001 <div class="inside">
002     The HMTL
003 </div>

It is important that the $callback argument only contains the following characters: 0-9, a-z, and ‘_’. Because this argument is referencing a function defined in PHP, it must follow the rules of naming PHP functions. Additionally, it is advisable to prefix the function with the a string representative of the plugin or function in which this code is added. Not doing so will open the project up to possible errors if another plugin or theme contains a function of the same name. In the case of the weather report meta box example, one might use the string “wr_display_meta_box” that corresponds with a function of the same name as the value for the $callback argument.

Usage Tips:

  • Use only the following characters: 0-9, a-z, and ‘_’
  • Prefix with a unique string that represents your plugin or theme (e.g., “wb” for “Weather Box”

$page

The last of the required arguments is $page. The $page argument specifies the write screens in which the meta box will be displayed. By default, WordPress has two post types (“post”, “page”) write screens, as well as the links write screen. The meta box can be displayed on any of these three content types’ write screens. Use “post” for the post write screen, “page” for the page write screen and “link” for the link write screen. Unfortunately, the $page argument cannot accept an array of values. To display the meta box on all three write pages, the add_meta_box function would need to be called three times, once for each of the three default $page argument values (Note: there is another way to add the meta box on every write screen by using the parameter passed to the callback function that is associated with the “add_meta_boxes” hook. As this is a more advanced topic, it is discussed in the “Using add_meta_box” of this article.

In addition to utilizing the three default write screen values for the $page argument, a custom post type’s slug can be used as the value for the $page argument. For instance, imagine that a custom post type was defined using the following:

001 $args = array(
002     // Your args here
003 );
004 register_post_type('review', $args);

The meta box can be added to the “review” custom post type’s write screen by using the “review” slug as the $page argument value.

Usage Tips:

  • Can only be “post”, “page”, “link”, or any custom post type slug
  • Cannot be an array of values

$context and $priority

Using only the four previously discussed arguments, a meta box can be displayed; however, to gain greater control of the positioning, it is essential to understand the $context and $priority arguments. Both of these arguments are optional, but are necessary for precise positioning of meta boxes. I will discuss these two arguments together as the arguments are so closely related. Loosely speaking, the $context argument defines where the meta box will be displayed. It takes three possible values: “side”, “normal”, or “advanced” with “advanced” being the default value. The $context argument has a dramatic effect on where the meta box is positioned on the write screen. The “side” value will place the meta box in the right hand column of the screen where the “Publish”, “Format”, and “Featured Image” boxes (amongst others) are displayed (this assumes that you are using a two column layout in your write screens). The “normal” and “advanced” values will place the meta box in the middle column somewhere below the main content textarea. While the $context argument will place the meta box in a general area, the $priority argument will allow for greater control of where the meta box displays within the area of the write screen that it is being displayed. The $priority argument takes four values: “high”, “core”, “low”, and “default”, with “default” cleverly being the default value.

Placement of the meta boxes can be a tricky especially as the position of meta boxes may be altered by meta boxes defined by other themes or plugins. It is not the case that other meta boxes will change the definition of the meta box; rather, the other meta boxes may be competing for a similar position and, depending on a number of factors, it is uncertain which meta box will get the desired position. To help understand where the meta boxes will be displayed, please refer to the following table. The table shows all 12 combinations of the $context and $priority arguments. The “Display Position” column refers to where the meta box will be shown assuming that only the default meta boxes are being displayed and that all of the default meta boxes are selected to “Show on screen” in the “Screen Options” panel.

$context $priority Display Position
advanced default Below “Revisions”
high Below “Revisions”
low Below “Revisions”
core Below “Revisions”
normal default Below “Revisions”
high Above “Excerpt”
low Below “Revisions”
core Below “Revisions”
side default Below “Featured Image”
high Above “Publish”
low Below “Featured Image”
core Below “Featured Image”

One can see that much of this seems redundant. For instance, if $context is set to “advanced”, it will always display below the “Revisions” meta box. An astute reader may wonder what the purpose of having twelve argument combinations if it only results in four different positions. These arguments become important once you introduce other meta boxes into the write screen. Within a context, one meta box may be displayed higher or lower than another meta box depending on the $priority argument. For instance, a meta box with $context set to “side” and $priority set to “high” will display higher than a meta box with $context set to “side” and $priority set to “low”.

While the “advanced” and “normal” $context values appear to display meta boxes in the same area, the “normal” $context value will always place meta boxes above meta boxes with “advanced” as the $context value, regardless of the $priority argument. In the event that two meta boxes are in the same context, the location of meta boxes will be determined by the $priority argument as previously discussed. The order of the $priority argument, for highest to lowest is: “high”, “core”, “default”, “low”.

Finally, if two meta boxes have identical $context and $priority, the meta boxes will be arranged alphanumerically according to the $id argument, with “a” and “0″ being highest and “z” and “∞” being the lowest.

Note that you can put as much time and effort as you want into placing a meta box, yet, a user can take advantage of the drag and drop interface to move it wherever the user desires. Additionally, if you would like to have the meta box in the side area, a user can elect to switch to the single column mode and your meta box will be moved.

Usage Tips:

  • $priority values (highest to lowest): “high”, “core”, “default”, “low”
  • $context values: “advanced”, “normal”, “side”
  • “normal” is higher than “advanced”
  • Alphanumeric sort of $id will disambiguate two meta boxes with identical $context and $priority values
  • Users can always move the meta box wherever they want so do not assume your meta box will be where you want it to be

$callback_args

The final argument to discuss is the $callback_args argument. The value of this argument can take either a string or an array. It represents data that can be passed to the callback function that is defined by the $callback argument. In order to access this data, two arguments must be specified for the callback function. The first argument contains the standard WordPress post object and the second argument will contain the an array of data which includes the data explicitly passed to the callback function via the $callback_args parameter.

By default, whether data is passed to the callback function using $callback_args or not, the second argument of the callback function will be an array containing the following keys:

  • id: $id argument specified for the add_meta_box function
  • title: $title argument specified for the add_meta_box function
  • callback: $callback argument specified for the add_meta_box function
  • args: $callback_args argument specified for the add_meta_box function

The data passed to the $callback_args argument can be accessed using the “args” key of the second argument specified in the callback function. This key and value is merged with the existing array that contains the “id”, “title”, and “callback” values for the current meta box. Even if no $callback_args value is specified, the default data can be accessed via the second argument of the callback function. To understand how this data is accessed, consider the following code snippet.

001 add_action('add_meta_boxes', 'wr_call_meta_box', 10, 1);
002 function wr_call_meta_box($post_type, $post)
003 {
004     $args = array('test', array('some data', 'in here'), 3);
005     add_meta_box(
006         'weather_box',
007         __('Current Weather', 'wr_plugin'),
008         'wr_display_meta_box',
009         'post',
010         'advanced',
011         'default',
012         $args
013     );
014 }
015
016 function wr_display_meta_box($post, $args)
017 {
018     echo '<pre>';
019     print_r($post);
020     print_r($args);
021     echo '</pre>';
022 }

Within the meta box, the $post object would be printed and should look something like:

001 stdClass Object
002 (
003     [ID] =&gt; 1
004     [post_author] =&gt; 1
005     [post_date] =&gt; 2011-07-22 01:37:29
006     [post_date_gmt] =&gt; 2011-07-22 01:37:29
007     [post_content] =&gt; Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!
008     [post_title] =&gt; Hello world!
009     [post_excerpt] =&gt;
010     [post_status] =&gt; publish
011     [comment_status] =&gt; open
012     [ping_status] =&gt; open
013     [post_password] =&gt;
014     [post_name] =&gt; hello-world
015     [to_ping] =&gt;
016     [pinged] =&gt;
017     [post_modified] =&gt; 2011-08-06 07:30:39
018     [post_modified_gmt] =&gt; 2011-08-06 07:30:39
019     [post_content_filtered] =&gt;
020     [post_parent] =&gt; 0
021     [guid] =&gt; http://localhost:8888/wordpress-3.2/?p=1
022     [menu_order] =&gt; 0
023     [post_type] =&gt; post
024     [post_mime_type] =&gt;
025     [comment_count] =&gt; 1
026     [ancestors] =&gt; Array
027         (
028         )
029
030     [filter] =&gt; edit
031 )

Regardless if this information is needed or not, the $post object is always passed to the callback function. The output above shows everything that is contained in the object that can be utilized in the meta box. Usually, it is necessary to have the current post’s ID value to access meta data to manipulate. That datum, among other data, can be accessed through the $post object. Just remember, the $post object will always be the first argument sent to the callback function. If you want to access your $callback_args values only, remember to access the second argument, not the first.

In addition to the $post object output displayed above, the output for the $args value is also displayed. It should look like the following:

001 Array
002 (
003     [id] =&gt; weather_box
004     [title] =&gt; Current Weather
005     [callback] =&gt; wr_display_meta_box
006     [args] =&gt; Array
007         (
008             [0] =&gt; test
009             [1] =&gt; Array
010                 (
011                     [0] =&gt; some data
012                     [1] =&gt; in here
013                 )
014
015             [2] =&gt; 3
016         )
017
018 )

As one can see, the $callback_args value is appended to the end of the $args array and is accessible via the “args” keys. The data can then be used as desired in the meta box.

Usage Tips:

  • The $callback_args is only accessible via the second argument of the callback function
  • The $callback_args is stored in the “args” key
  • $callback_args can be a string or array
  • Do not forget that the first argument is the $post object
    • It is not necessary to send any information contained in this object via the $callback_args argument
    • Do not try to access the $callback_args argument via the first argument of the callback function

Using add_meta_box

With a proper introduction to the add_meta_box function’s argument, it is time to bring it all together to create a meta box. While I will be using the code that I previously used to demonstrate the $callback_args argument, I will go through it piece by piece to explain what is happening. In this example, I will be creating a very simple weather meta box that allows users to attach weather data to posts.

Initiating the Call to add_meta_box

To begin with, the call to add_meta_box is made inside a function that will be used to hook into the “add_meta_boxes” function.

001 function wr_call_meta_box($post_type, $post)
002 {
003     add_meta_box(
004         'weather_box',
005         __('Current Weather', 'wr_plugin'),
006         'wr_display_meta_box',
007         'post',
008         'advanced',
009         'default'
010     );
011 }
012 add_action('add_meta_boxes', 'wr_call_meta_box', 10, 2);

Inside the wr_call_meta_box, the call to add_meta_box is made. Note the naming of the function that calls the add_meta_box function. First of all, it is being added to the global namespace, so it is prudent that it is prefixed. Since I am creating a weather report meta box, “wr” seems fitting as a prefix. Next, I add “call” to the name. Since you need a total of three functions (the save function will be discussed later in the article) to make meta box function, it is recommended that you set a naming convention that helps keep all of the functions organized. After adding the prefix, I tend to use a word that explains the purpose that the function serves in the overall meta box creation process. As such, I use “call” for this function because it is the function that “calls” the add_meta_box function. This convention is obviously my preference. Whether you like it or not, it would be wise to have some convention that works for you, especially when you find yourself adding multiple meta boxes within a plugin. Finally, I use “meta_box” to finish the function name. I do this to show that it is involved in the meta box creation process.

Within the wr_call_meta_box function, the call to add_meta_box is made. As one can see, this creates a meta box shown on the post screen with a title of “Current Weather” that is displayed in the bottom most area under the “Revisions” meta box. The wr_display_meta_box function will be responsible for building the HTML that will comprise the meta box.

The last function performed in this snippet is hooking the function to the appropriate WordPress action. For the purpose of adding meta boxes, the “add_meta_boxes” hook should be utilized. As such, it is the first parameter of the add_action function used to start the process. The next parameter for the the “add_action” function defines the callback function that adds the meta box. “10″ is the value of the next parameter that defines the priority of the call to the function. “10″ is the default value for this parameter and since there is no reason to have this function run earlier or later that the default, it is left as “10″. The final parameter of the add_action function specifies how many arguments are sent to the callback function. WordPress sends two arguments to the callback function for the “add_meta_boxes” hook and this final parameter allows those two parameters to be sent.

Regarding the parameters sent to the callback function, the first parameter is the name of the post type. For instance, when the code displayed above is executed on the post write screen, the value of the $post_type would be “post”, on the page write screen it would be “page”, and on a “Review” custom post type write screen, it would be “review”. The attentive reader will notice that since the $post_type is sent to the callback function and a “post-type” variable is used for the $page argument of the add_meta_boxes function, the $post_type parameter can be used for the $page argument. Yes, this can be done; however, this will add the meta box to every write screen, including the built in write screens (pages, posts, and links) and all custom post types. This method is a simple way to add a meta box to all write screens without having to make multiple calls to the add_meta_box function. The following code would add the weather meta box to all write screens.

001 function wr_call_meta_box($post_type, $post)
002 {
003     add_meta_box(
004         'weather_box',
005         __('Current Weather', 'wr_plugin'),
006         'wr_display_meta_box',
007         $post_type,
008         'advanced',
009         'default'
010     );
011 }
012 add_action('add_meta_boxes', 'wr_call_meta_box', 10, 2);

The second parameter sent to the callback function is $post. It contains the current post object. Printing this value to the screen reveals that this object contains the following information:

001 stdClass Object
002 (
003     [ID] =&gt; 1
004     [post_author] =&gt; 1
005     [post_date] =&gt; 2011-07-22 01:37:29
006     [post_date_gmt] =&gt; 2011-07-22 01:37:29
007     [post_content] =&gt; Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!
008     [post_title] =&gt; Hello world!
009     [post_excerpt] =&gt;
010     [post_status] =&gt; publish
011     [comment_status] =&gt; open
012     [ping_status] =&gt; open
013     [post_password] =&gt;
014     [post_name] =&gt; hello-world
015     [to_ping] =&gt;
016     [pinged] =&gt;
017     [post_modified] =&gt; 2011-08-06 07:30:39
018     [post_modified_gmt] =&gt; 2011-08-06 07:30:39
019     [post_content_filtered] =&gt;
020     [post_parent] =&gt; 0
021     [guid] =&gt; http://localhost:8888/wordpress-3.2/?p=1
022     [menu_order] =&gt; 0
023     [post_type] =&gt; post
024     [post_mime_type] =&gt;
025     [comment_count] =&gt; 1
026     [ancestors] =&gt; Array
027         (
028         )
029
030     [filter] =&gt; edit
031 )

The only change made in this snippet was to utilize the $post_type parameter as the $page argument for the add_meta_box function. While this may be tempting to utilize as a quick and easy way to add meta boxes, I would caution you to be more specific about which pages you add the meta boxes to. Not all meta boxes make sense in all write screens. Furthermore, if a user defines a custom post type and you use this method to add meta boxes to all write screens, that newly defined custom post type will also get the meta box whether or not it is meant to be there. Use this method with caution!

Before moving on to the function that produces the HTML for the meta box, it is important to discuss the “add_meta_boxes” hook. While other hooks can be used to add the meta boxes, it important that the “add_meta_boxes” hook be used for this purpose, not other hooks, which are, unfortunately, widely used for this purpose. Most prominently, “admin_init” is commonly used to execute the function that calls the add_meta_box function. It will work; however, the purpose of the “add_meta_boxes” hook is specifically for these functions that add the meta boxes. There are two reasons to use the “add_meta_boxes” hook, as opposed to other hooks. The first reason is that if you use another hook, the callback function attached to the hook may be executed when it is unnecessary. This causes unnecessary code to be executed and can lead to unexpected (and very difficult to debug) errors. The second reason is that the “add_meta_boxes” hook can be extended to be used for a specific custom post type. There is a variable hook version of the “add_meta_boxes” function that takes the name of the custom post type. For instance, if you refer back to the example post type “review” defined above, you can specifically target the “add_meta_boxes” hook for the “review” write screen only, by modifying the hook to be “add_meta_boxes_review”. The hook is generated in WordPress core using: 'add_meta_boxes_' . $post_type. Note that the post-type-name would be identical to that used above as the $page argument. Ideally, with a custom post type, the function that calls add_meta_box would be attached to the variable version of the “add_meta_boxes” hook. As an example, in order to add a meta box to only the “review” post type, a very solid way of executing this code only when absolutely necessary would to use the following snippet:

001 function wr_call_meta_box($post)
002 {
003     add_meta_box(
004         'weather_box',
005         __('Current Weather', 'wr_plugin'),
006         'wr_display_meta_box',
007         'review',
008         'advanced',
009         'default'
010     );
011 }
012 add_action('add_meta_boxes_review', 'wr_call_meta_box', 10, 1);

Take note that there is only one parameter being passed to the callback function when the variable hook is used. Instead of sending both the post type and the post object, only the post object is sent to the callback function when the variable “add_meta_boxes” hook is used. Now, to further reduce redundancy in the code, instead of hard coding “review” as the $page parameter, it can be added using the “post_type” parameter within the post object that is passed to the callback function. The resulting code would look like:

001 function wr_call_meta_box($post)
002 {
003     add_meta_box(
004         'weather_box',
005         __('Current Weather', 'wr_plugin'),
006         'wr_display_meta_box',
007         $post-&gt;post_type,
008         'advanced',
009         'default'
010     );
011 }
012 add_action('add_meta_boxes_review', 'wr_call_meta_box', 10, 1);

Instead of using the variable “add_meta_boxes” hook to call the function that produces the HTML for the meta box, this process can be started when a new post type is registered. Within the arguments array that the register_post_type accepts is an argument called “register_meta_box_cb”. The “register_meta_box_cb” argument works identically to the “add_meta_boxes” variable hook. In fact, it ultimately attaches the value set for “register_meta_box_cb” to the “add_meta_boxes” variable hook. As such, the callback function defined by the “register_meta_box_cb” argument will only receive the single post object argument that the variable “add_meta_boxes” hook sends. To initiate the adding of a meta box via register_post_type, you would do something like the following:

001 function wr_register_post_type()
002 {
003     $labels = array(
004         'name' =&gt; _x('Reviews', 'post type general name', 'wr_plugin'),
005         'singular_name' =&gt; _x('Review', 'post type singular name', 'wr_plugin'),
006         'add_new' =&gt; _x('Add New', 'review', 'wr_plugin'),
007         'add_new_item' =&gt; __('Add New Review', 'wr_plugin'),
008         'edit_item' =&gt; __('Edit Review', 'wr_plugin'),
009         'new_item' =&gt; __('New Review', 'wr_plugin'),
010         'all_items' =&gt; __('All Reviews', 'wr_plugin'),
011         'view_item' =&gt; __('View Review', 'wr_plugin'),
012         'search_items' =&gt; __('Search Reviews', 'wr_plugin'),
013         'not_found' =&gt;  __('No reviews found', 'wr_plugin'),
014         'not_found_in_trash' =&gt; __('No reviews found in Trash', 'wr_plugin'),
015         'parent_item_colon' =&gt; '',
016         'menu_name' =&gt; __('Reviews', 'wr_plugin')
017     );
018     $args = array(
019         'labels' =&gt; $labels,
020         'public' =&gt; true,
021         'publicly_queryable' =&gt; true,
022         'show_ui' =&gt; true,
023         'show_in_menu' =&gt; true,
024         'query_var' =&gt; true,
025         'rewrite' =&gt; true,
026         'capability_type' =&gt; 'post',
027         'has_archive' =&gt; true,
028         'hierarchical' =&gt; false,
029         'menu_position' =&gt; null,
030         'supports' =&gt; array('title','editor','author','thumbnail','excerpt','comments'),
031         'register_meta_box_cb' =&gt; 'wr_call_meta_box'
032     );
033     register_post_type('review', $args);
034 }
035
036 add_action('init', 'wr_register_post_type');

I will not cover registering custom post types here as it has been covered extensively elsewhere. The important part of the above code is to notice how the “register_meta_box_cb” argument points to the previously defined wr_call_meta_box function. When this post type is registered, it also sets into motion events that will create the meta box in the appropriate write screen. It saves the trouble of having to use a separate call to add_action to set up the meta box. It also keeps all of the code for the individual post type in one place. Functionally, it does the exact same thing as the “add_meta_boxes” variable hook. It is just a different way to accomplish the same task.

Usage Tips:

  • Know the arguments sent to the callback function
    • “add_meta_boxes” sends:
      • $post_type: the name of the post type
      • $post: the post object
    • “add_meta_boxes_{$post_type}” sends:
      • $post: the post object
  • Use the arguments sent to the callback function
  • If possible, register the callback function for custom post types when registering the custom post type

Defining the Callback Function

Now that you know a few different ways to call the function that initiates the creation of the meta box, it is time to write the code that builds the actual meta box. Note that in all of the above code examples, the value for the $callback parameter has been “wr_display_meta_box”. This value refers to the function that generates the HTML for the meta box. Keeping with the previously described naming convention, I prefix the function with “wr” as it is being added to the global namespace. I then add “display” as its the function that “displays”” the HTML for the meta box. Finally, I stick with the “meta_box” term to finish the function name so I know that it belongs to the family of functions that create the meta box.

Without further ado, the following code snippet generates the HTML for the meta box.

001 function wr_display_meta_box($post, $args)
002 {
003    wp_nonce_field(plugins_url(__FILE__), 'wr_plugin_noncename');
004 ?>
005    <p>
006        <label for="wr-temperature"><?php _e('Temperature (&deg;F)', 'wr_plugin'); ?>: </label>
007        <input type="text" name="wr-temperature" value="<?php echo get_post_meta($post->ID, 'wr-temperature', true); ?>" />
008        <em><?php _e('Must be a numeric value', 'wr_plugin'); ?></em>
009    </p>
010 <?php
011 }
012 ?>

In this example, a simple text input field with a label and two hidden fields are generated. The resulting HTML for the meta box should look something like:

001 <div class="postbox ">
002     <div class="handlediv"><br></div>
003     <h3 class="hndle"><span>Current Weather</span></h3>
004     <div class="inside">
005
006         <p>
007             <label for="wr-temperature">Temperature (&deg;F): </label>
008
009             <em>Must be a numeric value</em>
010         </p>
011     </div>
012 </div>

Notice that the HTML defined in the callback function shows up inside the div with the aptly name class of “inside”. The rest of the HTML, including the div structure is automatically generated. An h3 element is created based on the desired title for the meta box and the meta box wrapper div is given an id equivalent to the $id argument of the add_meta_box function.

The first line of the wr_display_meta_box calls the wp_nonce_field function, which generates two hidden fields. These hidden fields help protect the form against cross-site scripting. It is recommended that when using forms within WordPress that you use the nonce functions for security purposes. The first argument of the wp_nonce_field function defines the “action” for the nonce field. When the function creates the “hidden” input, it will use this value to create the “value” attribute for the input. Later, when validating the form, this value will be checked against to make sure that the form coming from the correct place. Note that it is typical to use the something like the plugins_url function for this argument as it is an easy way to stay consistent with the action name when it is created and when it is validated. The next argument specifies the “name” and “id” attributes for the “hidden” input. In this case, we will call the “hidden” input “wr_plugin_noncename”. These two arguments create the following HTML:

001 <input type="hidden" id="wr_plugin_noncename" name="wr_plugin_noncename" value="24062433f6" />

The next argument in the wp_nonce_field function determines whether a “hidden” input field with the referrer information will be added or not. The value “true” is sent for this argument (which is the default), which will create the referrer “hidden” input. Later, when saving the meta box information, this value will be checked. It generates the following HTML.

001 <input type="hidden" name="_wp_http_referer" value="/wordpress-3.2/wp-admin/post-new.php" />

The final argument in the wp_nonce_field function determines whether the HTML generated will be returned by the function or echoed. The “true” value echoes the HTML. If you are unfamiliar with the built-in WordPress nonce functions, I would recommend reading about them in order to harden and secure your WordPress work.

You will recall that the callback function defined in the add_meta_box function will take two arguments: the post object and the custom arguments sent via add_meta_box. The ID variable in the $post object is utilized within the get_post_meta function to find and display the previous value of “wr-temperature” if it exists as post meta. The get_post_meta function finds a value in the database given a post ID and a meta key name. At this point, no values have been saved to the database, so this value will not be found and nothing will be printed. In the next section of this article, this value will be saved and the current function is being written in such a way that it will be prepared to handle the existing value when it is in the database.

To bring everything together at this point, the following code summarizes adding a meta box to the post write screen in the “advanced” area.

001 function wr_call_meta_box($post_type, $post)
002 {
003    add_meta_box(
004        'weather_box',
005        __('Current Weather', 'wr_plugin'),
006        'wr_display_meta_box',
007        'post',
008        'advanced',
009        'default'
010    );
011 }
012 add_action('add_meta_boxes', 'wr_call_meta_box', 10, 2);
013
014 function wr_display_meta_box($post, $args)
015 {
016    wp_nonce_field(plugins_url(__FILE__), 'wr_plugin_noncename');
017 ?>
018    <p>
019        <label for="wr-temperature"><?php _e('Temperature (&deg;F)', 'wr_plugin'); ?>: </label>
020        <input type="text" name="wr-temperature" value="<?php echo get_post_meta($post->ID, 'wr-temperature', true); ?>" />
021        <em>Must be a numeric value</em>
022    </p>
023 <?php
024 }
025 ?>

Saving Meta Boxes

The data gathered by the meta box needs to be added to the database. Typically, meta boxes will collect data that are associated with the post. As such, it is appropriate to add this data to the post as post meta data. To do so, a new function will be defined that will save the meta data using the update_post_meta function when the post is saved.

Hooking the Save Function

To begin, an appropriate hook needs to be identified that runs whenever the a post is saved. Fortunately, there is a hook called “save_post” that can be used for this purpose. The following code snippet sets up a function to be executed when the “save_post” hook is called.

001 add_action('save_post', 'wr_save_meta_box', 10, 2);

Examining the call to the add_action function reveals that the function wr_save_meta_box will be executed on the “save_post” action. Note that I have again followed the naming structure that uses “wr” as the prefix, followed by a word (“save”) that explains what the function is doing, and ends with “meta_box”, which associates it with the family of functions that handle the meta box. The remaining two arguments for the add_action function specify that it will run in the default order (“10″) and that two (“2″) arguments are passed to the wr_save_meta_box function. The callback function for the “save_post” action is sent the ID for the post and the post object itself. This data can be used to help process data collected by the meta box.

The Save Function

While “save_post” is the hook to use in this scenario, it is important to note a few details about this hook before you use it. There are some counterintuitive aspects regarding when this hook is run. First of all, you must understand that when you visit the “post-new.php” page (i.e., the “Add New” link under the “Posts”, “Pages”, or any custom post type menu item), functions associated with the “save_post” hook are called. Indeed, even though you are merely only visiting the write screen and have not yet saved any data, the “save_post” hook is called. When you visit the “Add New” write screen, a post with status of “auto-draft” is created. It is a completely blank post that is a placeholder for the post you are about to create. To illustrate this point, add the following code to your “functions.php” of your current theme or within a new plugin.

001 add_action('save_post', 'wr_save_meta_box', 10, 2);
002
003 function wr_save_meta_box($post_id, $post)
004 {
005     var_dump($post_id);
006 }

Simply put, this function will output the ID of the current post to the screen each time that the “save_post” took is executed. In certain situations, this will cause errors, but the point is that the functions associated with the “save_post” hook are executed at times that you might not intuitively expect. As such, it is prudent to add some code to check for specific context when utilizing the “save_post” hook. For instance, there is no need to try to save the post meta for the weather meta box when a user first accesses the “Add New” write screen. As an aside, to help catch these types of errors in the code, I recommend developing with the WP_DEBUG constant set to “true”. You can find this variable in the “wp-config.php” file.

It is necessary then that the routine within the wr_save_meta_box function is not executed if not in the right context. How is this accomplished? Three things need to be checked when this function is executed:

  • Is the current context an “auto save”
  • Does the user have the capabilities to edit posts
  • Has the appropriate nonce field been set and is its value correct

Generally speaking, it is not necessary to save meta data during auto save routines. Meta data is not maintained in the revision history and thus, it is not important that it is saved during the auto save for the post. To accomplish the goal of exiting the function on auto save, we begin the wp_save_meta_box function by adding a check for the auto save condition

001 function wr_save_meta_box($post_id, $post)
002 {
003     if(defined('DOING_AUTOSAVE') &amp;&amp; DOING_AUTOSAVE)
004       return;
005 }

When an auto save is initiated, WordPress core sets the constant DOING_AUTOSAVE to true. This variable is set during a function that is executed when the auto save routine is initiated by an AJAX action. The purpose of this variable is to make the rest of the scripts aware that an auto save is being performed and the routines should be thusly altered. In the previous function, AUTO_SAVE is detected and if it is set, the function returns nothing. Therefore, none of the actual save routines within the function will be executed.

The next step is to check the capabilities of the current user to determine if the current user is able to perform the action. Adding this section to the function looks like:

001 function wr_save_meta_box($post_id, $post)
002 {
003     if(defined('DOING_AUTOSAVE') &amp;&amp; DOING_AUTOSAVE)
004         return;
005
006     if(!current_user_can('edit_post', $post_id))
007         return;
008 }

Simply put, this part of the function utilizes the current_user_can function to determine if the current user has the capability to perform the intended action (i.e., “edit_post”) for the current post (i.e., $post_id. This snippet shows the general form of this check, as well as demonstrates the basic concept; however, a few more situations need to be considered before this is completed. Note that this snippet checks for the “edit_post” capability. What if the current context is saving a page or a custom post type? Interestingly, if the user can “edit_post”, s/he will pass this check even if it is called in the context of a page or a custom post type. If your intent is to have every user who has the “edit_post” capability update this meta data in any context, the code above is sufficient; however, should you wish to have the ability to save post meta data more closely tied to post type, you can check for the context prior to making the call to current_user_can. To check specifically for posts and pages, the following snippet would be what you need.

001 function wr_save_meta_box($post_id, $post)
002 {
003     if(defined('DOING_AUTOSAVE') &amp;&amp; DOING_AUTOSAVE)
004         return;
005
006     if('page' == $_POST['post_type'])
007     {
008         if(!current_user_can('edit_page', $post_id))
009             return;
010     }
011     else
012         if(!current_user_can('edit_post', $post_id))
013             return;
014
015 }

This function checks if the current context is a page. If it is, then it tests if the current user can “edit_page”. If it is not a page, it instead checks if the current user can “edit_post”. Note that there is no condition to check for a custom post type. In fact, if the current context of this function is a custom post type, the function would test if the current user can “edit_post”. In the sample code for defining a custom post type that I provided earlier in the article, I set the “capability_type” to “post”. What this means is that all of the capabilities that are associated with posts are associated with the “review” custom post type. As such, the wr_save_meta_box function would, assuming that the current user has the “edit_post” capability, determine that the user has the proper permissions to edit the “review” custom post type. Should you want to define a set of capabilities specifically for a custom post type, you would need to make sure that you test specifically for those capabilities in this function to ensure that it runs properly for the right users.

Finally, we need to check that we are in the right context with regard to the meta box and its form fields being present. We can guarantee the right context if the nonce field that was generated previously is checked for before executing the code. When discussing the nonce field earlier in the article, I discussed its security benefit; however, it has the added benefit of controlling the context in which the routines within the wr_save_meta_box function are executed. With that said, the following code represents an updated wr_save_meta_box function that checks for its context and adds updates the post meta value.

001 function wr_save_meta_box($post_id, $post)
002 {
003     if(defined('DOING_AUTOSAVE') &amp;&amp; DOING_AUTOSAVE)
004         return;
005
006     if('page' == $_POST['post_type'])
007     {
008         if(!current_user_can('edit_page', $post_id))
009             return;
010     }
011     else
012         if(!current_user_can('edit_post', $post_id))
013             return;
014
015     if(isset($_POST['wr_plugin_noncename']) &amp;&amp; wp_verify_nonce($_POST['wr_plugin_noncename'], plugins_url(__FILE__)) &amp;&amp; check_admin_referer(plugins_url(__FILE__), 'wr_plugin_noncename'))
016     {
017         if(is_numeric($_POST['wr-temperature']))
018         {
019             update_post_meta($post_id, 'wr-temperature', $_POST['wr-temperature']);
020         }
021     }
022     return;
023 }

The function first checks to see if the “wr_plugin_noncename” key within the $_POST array is set. If it is not set, this means that the “Add New” or the “Edit” write screen has not been submitted. This check is not enough, however, as it could be set as part of an cross-site scripting attack. As such, the value of the nonce field needs to be checked. The wp_verify_nonce function checks to make sure that the value of the nonce field is as expected. It takes the value of the nonce field as its first argument and the “action” value that was sent to the wp_nonce_field as the second argument. If the actual value (i.e., $_POST['wr_plugin_noncename']) of the nonce field is equivalent to the expect value for the action (i.e., plugins_url(__FILE__)), the function returns true.

It is also important to check that the referrer is the expected value. In other words, we need to check that the form was submitted from where we expected it to be submitted. The check_admin_referer (sic) function is used for this purpose (Note: while the proper spelling of the term is “referrer”, the HTTP standard that defines the term spells the word “referer”. As such, WordPress has used the latter spelling convention. Do not forget to misspell the word!). This function takes the “action” as the first argument and the nonce field name as the second argument. It tests to ensure that the referring page is the expected page. If any of these checks fail, the update_post_meta function is not executed. If both checks pass, the update_post_meta function is executed.

(Note: The check_admin_referer function will run the wp_verify_nonce function within it. As such, it is redundant to use both wp_verify_nonce and check_admin_referer together as demonstrated above; however, I am including both in this article to thoroughly cover what each will do to help inform readers about the purpose of each of these functions. Ideally, I would only use check_admin_referer in this case.

Once the origin of the request has been verified, the data is validated and submitted to the database. Because the input field states that it will only accept numeric values, we will simply test to make sure that the values are numeric using the PHP function is_numeric before updating the value. If the value is numeric, the value will be added using the update_post_meta function. The update_post_meta function accepts four arguments: $post_id, $meta_key, $meta_value, and $prev_value. The $post_id argument takes the ID of the post that the meta data will be associated with. The $meta_key argument specifies the key that will be used to identify the piece of data. The piece of data itself is specified by the $meta_value argument. Finally, the $prev_value (not used in the function above), is an optional argument that contains the previous data value. If it is set, it will change the table row where the “meta_key” column is the same as the $meta_key argument and the “meta_value” column is equivalent to the $meta_value argument.

Having completed the wr_save_meta_box function, the meta value can now be saved! To summarize, the following code will define a meta box and save it when a post is updated.

001 function wr_call_meta_box($post_type, $post)
002 {
003    add_meta_bo
004        'weather_box',
005        __('Current Weather', 'wr_plugin'),
006        'wr_display_meta_box',
007        'post',
008        'advanced',
009        'default'
010    );
011 }
012 add_action('add_meta_boxes', 'wr_call_meta_box', 10, 2);
013
014 function wr_display_meta_box($post, $args)
015 {
016    wp_nonce_field(plugins_url(__FILE__), 'wr_plugin_noncename');
017 ?>
018    <p>
019        <label for="wr-temperature"><?php _e('Temperature (&deg;F)', 'wr_plugin'); ?>: </label>
020        <input type="text" name="wr-temperature" value="<?php echo get_post_meta($post->ID, 'wr-temperature', true); ?>" />
021        <em>Must be a numeric value</em>
022    </p>
023 <?php
024 }
025
026 add_action('save_post', 'wr_save_meta_box', 10, 2);
027 function wr_save_meta_box($post_id, $post)
028 {
029    if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
030        return;
031
032    if('page' == $_POST['post_type'])
033    {
034        if(!current_user_can('edit_page', $post_id))
035            return;
036    }
037    else
038        if(!current_user_can('edit_post', $post_id))
039            return;
040
041    if(isset($_POST['wr_plugin_noncename']) && wp_verify_nonce($_POST['wr_plugin_noncename'], plugins_url(__FILE__)) && check_admin_referer(plugins_url(__FILE__), 'wr_plugin_noncename'))
042    {
043        if(is_numeric($_POST['wr-temperature']))
044        {
045            update_post_meta($post_id, 'wr-temperature', $_POST['wr-temperature']);
046        }
047    }
048    return;
049 }
050 ?>

With that we have added a meta box and saved its data to the post meta table.

Usage Tips:

  • Within a function called by the “save_post” hook:
    • Check for DOING_AUTOSAVE
    • Check user capabilities
    • Use the nonce field to check for context and for security
  • Remember that “save_post” is executed at times you might not expect; check for the context!

Conclusion

The present tutorial has thoroughly outlined how to add a meta box to any write screen within WordPress. All of the necessary functions have been discussed and usage recommendations and strategies have been outlined. Using these basic concepts, you can now create any meta box that you desire, from simple weather meta boxes to more complex meta boxes that handle significant amounts of data. The only thing limiting the possibilities is your creativity.

About admin

Check Also

shortcode-generator

Shortcode generator for WordPress

Shortcodes Everybody has seen them – those bracketed tags you put in your post or ...

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>