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:
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:
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 ) |
004 |
$args = array ( 'test' , array ( 'some data' , 'in here' ), 3); |
007 |
__( 'Current Weather' , 'wr_plugin' ), |
008 |
'wr_display_meta_box' , |
016 |
function wr_display_meta_box( $post , $args ) |
Within the meta box, the $post
object would be printed and should look something like:
004 |
[post_author] => 1 |
005 |
[post_date] => 2011-07-22 01:37:29 |
006 |
[post_date_gmt] => 2011-07-22 01:37:29 |
007 |
[post_content] => Welcome to WordPress. This is your first post. Edit or delete it, then start blogging! |
008 |
[post_title] => Hello world! |
010 |
[post_status] => publish |
011 |
[comment_status] => open |
012 |
[ping_status] => open |
013 |
[post_password] => |
014 |
[post_name] => hello-world |
017 |
[post_modified] => 2011-08-06 07:30:39 |
018 |
[post_modified_gmt] => 2011-08-06 07:30:39 |
019 |
[post_content_filtered] => |
020 |
[post_parent] => 0 |
023 |
[post_type] => post |
024 |
[post_mime_type] => |
025 |
[comment_count] => 1 |
026 |
[ancestors] => Array |
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:
003 |
[id] => weather_box |
004 |
[title] => Current Weather |
005 |
[callback] => wr_display_meta_box |
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 ) |
005 |
__( 'Current Weather' , 'wr_plugin' ), |
006 |
'wr_display_meta_box' , |
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 ) |
005 |
__( 'Current Weather' , 'wr_plugin' ), |
006 |
'wr_display_meta_box' , |
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:
004 |
[post_author] => 1 |
005 |
[post_date] => 2011-07-22 01:37:29 |
006 |
[post_date_gmt] => 2011-07-22 01:37:29 |
007 |
[post_content] => Welcome to WordPress. This is your first post. Edit or delete it, then start blogging! |
008 |
[post_title] => Hello world! |
010 |
[post_status] => publish |
011 |
[comment_status] => open |
012 |
[ping_status] => open |
013 |
[post_password] => |
014 |
[post_name] => hello-world |
017 |
[post_modified] => 2011-08-06 07:30:39 |
018 |
[post_modified_gmt] => 2011-08-06 07:30:39 |
019 |
[post_content_filtered] => |
020 |
[post_parent] => 0 |
023 |
[post_type] => post |
024 |
[post_mime_type] => |
025 |
[comment_count] => 1 |
026 |
[ancestors] => Array |
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 ) |
005 |
__( 'Current Weather' , 'wr_plugin' ), |
006 |
'wr_display_meta_box' , |
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 ) |
005 |
__( 'Current Weather' , 'wr_plugin' ), |
006 |
'wr_display_meta_box' , |
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() |
004 |
'name' => _x( 'Reviews' , 'post type general name' , 'wr_plugin' ), |
005 |
'singular_name' => _x( 'Review' , 'post type singular name' , 'wr_plugin' ), |
006 |
'add_new' => _x( 'Add New' , 'review' , 'wr_plugin' ), |
007 |
'add_new_item' => __( 'Add New Review' , 'wr_plugin' ), |
008 |
'edit_item' => __( 'Edit Review' , 'wr_plugin' ), |
009 |
'new_item' => __( 'New Review' , 'wr_plugin' ), |
010 |
'all_items' => __( 'All Reviews' , 'wr_plugin' ), |
011 |
'view_item' => __( 'View Review' , 'wr_plugin' ), |
012 |
'search_items' => __( 'Search Reviews' , 'wr_plugin' ), |
013 |
'not_found' => __( 'No reviews found' , 'wr_plugin' ), |
014 |
'not_found_in_trash' => __( 'No reviews found in Trash' , 'wr_plugin' ), |
015 |
'parent_item_colon' => '' , |
016 |
'menu_name' => __( 'Reviews' , 'wr_plugin' ) |
019 |
'labels' => $labels , |
021 |
'publicly_queryable' => true, |
022 |
'show_ui' => true, |
023 |
'show_in_menu' => true, |
024 |
'query_var' => true, |
025 |
'rewrite' => true, |
026 |
'capability_type' => 'post' , |
027 |
'has_archive' => true, |
028 |
'hierarchical' => false, |
029 |
'menu_position' => null, |
030 |
'supports' => array ( 'title' , 'editor' , 'author' , 'thumbnail' , 'excerpt' , 'comments' ), |
031 |
'register_meta_box_cb' => 'wr_call_meta_box' |
033 |
register_post_type( 'review' , $args ); |
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:
- 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 ) |
003 |
wp_nonce_field(plugins_url( __FILE__ ), 'wr_plugin_noncename' ); |
006 |
<label for = "wr-temperature" ><?php _e( 'Temperature (°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> |
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 > |
007 |
< label for = "wr-temperature" >Temperature (°F): </ label > |
009 |
< em >Must be a numeric value</ em > |
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 ) |
005 |
__( 'Current Weather' , 'wr_plugin' ), |
006 |
'wr_display_meta_box' , |
012 |
add_action( 'add_meta_boxes' , 'wr_call_meta_box' , 10, 2); |
014 |
function wr_display_meta_box( $post , $args ) |
016 |
wp_nonce_field(plugins_url( __FILE__ ), 'wr_plugin_noncename' ); |
019 |
<label for = "wr-temperature" ><?php _e( 'Temperature (°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> |
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); |
003 |
function wr_save_meta_box( $post_id , $post ) |
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 ) |
003 |
if (defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE) |
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 ) |
003 |
if (defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE) |
006 |
if (!current_user_can( 'edit_post' , $post_id )) |
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 ) |
003 |
if (defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE) |
006 |
if ( 'page' == $_POST [ 'post_type' ]) |
008 |
if (!current_user_can( 'edit_page' , $post_id )) |
012 |
if (!current_user_can( 'edit_post' , $post_id )) |
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 ) |
003 |
if (defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE) |
006 |
if ( 'page' == $_POST [ 'post_type' ]) |
008 |
if (!current_user_can( 'edit_page' , $post_id )) |
012 |
if (!current_user_can( 'edit_post' , $post_id )) |
015 |
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' )) |
017 |
if ( is_numeric ( $_POST [ 'wr-temperature' ])) |
019 |
update_post_meta( $post_id , 'wr-temperature' , $_POST [ 'wr-temperature' ]); |
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 ) |
005 |
__( 'Current Weather' , 'wr_plugin' ), |
006 |
'wr_display_meta_box' , |
012 |
add_action( 'add_meta_boxes' , 'wr_call_meta_box' , 10, 2); |
014 |
function wr_display_meta_box( $post , $args ) |
016 |
wp_nonce_field(plugins_url( __FILE__ ), 'wr_plugin_noncename' ); |
019 |
<label for = "wr-temperature" ><?php _e( 'Temperature (°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> |
026 |
add_action( 'save_post' , 'wr_save_meta_box' , 10, 2); |
027 |
function wr_save_meta_box( $post_id , $post ) |
029 |
if (defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE) |
032 |
if ( 'page' == $_POST [ 'post_type' ]) |
034 |
if (!current_user_can( 'edit_page' , $post_id )) |
038 |
if (!current_user_can( 'edit_post' , $post_id )) |
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' )) |
043 |
if ( is_numeric ( $_POST [ 'wr-temperature' ])) |
045 |
update_post_meta( $post_id , 'wr-temperature' , $_POST [ 'wr-temperature' ]); |
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. Implementing these different strategies will allow you to provide provide better SEO services to clients, as well as yourself. The only thing limiting the possibilities is your creativity.
Leave A Comment