Creating
meta boxes is a crucial part of
WordPress
theme/plugin development. It's a way to add an appealing editor to the
post screen and avoids forcing users to rely on custom fields. If you've
ever created a custom post type in WordPress, you've probably wanted to
add some sort of additional data to it. Sure, you could use
custom fields, but that's ugly. Getting started with custom meta boxes is easy, so let's dive in!
What Are Custom Meta Boxes?
A custom meta (or write) box is
incredibly simple in theory. It allows you to add a custom piece of
data to a post or page in WordPress.
Imagine that you're working on a theme for a client that wants to
catalog his extensive collection of concert posters. You immediately
start looking to the core WordPress functionality to see how you might
organize the theme: Every
post will represent a poster, which is perfect for adding an image, title and description. We can also use the
categories and
tags system
inside of WordPress to organize the posters. But what if we wanted to
add a new type of meta data for the "artist" of each poster? Hmph.
WordPress doesn't quite have anything for that right out of the box...
which brings us to custom meta boxes.
A custom meta (or write) box is incredibly simple in theory. It
allows you to add a custom piece of data to a post or page in WordPress -
what's better is that it can fit directly into most of the default
pages inside WP, so you can easily place it inside the Post-Editor for
easy use by non-technical types. As I said in the intro, you can add
this same kind of "meta data" to your post using the built in
custom fields for a post or page. There's nothing wrong with this persay, but it's not a very graceful or user-friendly.
Instead, you want to create a custom meta box that contains fields
for all of your data and saves all of that stuff right when the post is
published. That's what we're covering here. This is broken down into
three big steps:
- Adding the meta box
- Rendering the meta box
- Saving the data (the right way - yes, there is a wrong way)
It's worth noting that a lot of this information can also be used
inside the custom post types API (we'll get to that point later!), but
for the sake of keeping things focused today, we're going to add this
directly to the default post editor.
For the advanced readers in the audience: Yes, custom post types is
where we'll be going with this eventually, but it's important to setup
some fundamentals first. Plus, as you can use the custom meta boxes in
all sorts of places, it's good for anyone to know.
Anything in this tutorial will work in a theme's
functions.php
file. That is not the correct place for it, however. If you're adding
data to a post, chances are you want it there regardless of your front
end design. As such, you should place this code some place that isn't
dependent on your design: a plugin file.
Step 1 Adding the Meta Box
Conveniently, WordPress provides a function for adding meta boxes to a given admin screen:
add_meta_box.
The codex entry is well done for this function, but here's a brief overview. Its prototype:
1
|
<?php add_meta_box( $id, $title, $callback, $page, $context, $priority, $callback_args ); ?>
|
$id is the html ID attribute of the box. This is useful
if you're loading custom CSS or Javascript on the editing page to
handle the options. Otherwise, it doesn't really matter all that much.
$title is displayed at the top of the meta box.
$callback is the function that actually renders the meta box. We'll outline this in step 2.
$page is where you want the meta box to be displayed. This should be a string with 'post' or 'page' or 'some_custom_post_type'.
$context is where you want the meta box displayed.
'normal' puts it below the post editor. 'side' moves the meta box to
editing screen's right sidebar (by the categories and tags, etc).
'advanced' also put the box in the same column as the post editor, but
further down.
$priority tells wordpress where to place the meta box in
the context. 'high', 'default' or 'low' puts the box closer to the
top, in its normal position, or towards the bottom respectively. Since
all meta boxes are drag-able,
$priority is not a huge deal.
Finally
$callback_args lets you pass data to your
$callback
function in the form of an array. We're not going to use this here,
but it could be useful for passing some data to the meta box. Say, if
your plugin had several options that influenced what was displayed in
the meta box. You could pass those options values through the
$callback_args array.
So, our
add_meta_box call will look like this:
1
|
<?php add_meta_box( 'my-meta-box-id', 'My First Meta Box', 'cd_meta_box_cb', 'post', 'normal', 'high' ); ?>
|
We can't just pop this into our plugin file alone. Doing so will
result in the white screen of death, and PHP fatal error: call to
undefined function. Why? Because we called
add_meta_box function before WordPress was loaded. So we need to use a WordPress hook, which is part of the
plugin api.
Basically, functions get hooked into a given WordPress action or
filter hook, then those functions are fired when that hook loads. By
wrapping our add_meta_box call in a function, then hooking that function
into the
add_meta_boxes action hook, we avoid the fatal error.
Our code to add the meta box to our post screen would look like this:
1
2
3
4
5
6
7
|
<?php
add_action( 'add_meta_boxes', 'cd_meta_box_add' );
function cd_meta_box_add()
{
add_meta_box( 'my-meta-box-id', 'My First Meta Box', 'cd_meta_box_cb', 'post', 'normal', 'high' );
}
?>
|
Step 2 Rendering the Meta Box
The above code is enough to add the meta box, but now we have to
render the thing and actually add fields. This is just an HTML form
code mixed in with a bit of PHP to display the saved data. We don't need
to include the form tags as WordPress does that for us.
Remember the string we passed as the
$callback in
add_meta_box? We're now going to create a function with the same name. This function will take care of all the display inside the meta box.
1
2
3
4
5
6
|
<?php
function cd_meta_box_cb()
{
echo 'What you put here, show\'s up in the meta box';
}
?>
|
We're going to add several fields to our meta box: a text input, a
drop down menu, and a check-box. Let's start with the text input.
Adding the Text Input
1
2
3
4
5
6
7
8
9
|
<?php
function cd_meta_box_cb()
{
?>
<label for="my_meta_box_text">Text Label</label>
<input type="text" name="my_meta_box_text" id="my_meta_box_text" />
<?php
}
?>
|
But what about actually displaying the data? Well, as you'll see in
step 3, we'll store this data in the wp_postmeta table using the
update_post_meta function. That function has two sister functions
called
get_post_meta and
get_post_custom, which grab data from wp_postmeta.
get_post_meta only grabs data from one key, while
get_post_custom grabs all of it. Because we're only really using one field at this point, let's use
get_post_meta.
Also note that the
add_meta_box function passes one variable to our callback: $post, which is a post object.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
<?php
function cd_meta_box_cb( $post )
{
$values = get_post_custom( $post->ID );
$text = isset( $values['my_meta_box_text'] ) ? esc_attr( $values['my_meta_box_text'][0] ) : ”;
$selected = isset( $values['my_meta_box_select'] ) ? esc_attr( $values['my_meta_box_select'][0] ) : ”;
$check = isset( $values['my_meta_box_check'] ) ? esc_attr( $values['my_meta_box_check'][0] ) : ”;
?>
<p>
<label for="my_meta_box_text">Text Label</label>
<input type="text" name="my_meta_box_text" id="my_meta_box_text" value="<?php echo $text; ?>" />
</p>
<?php
}
?>
|
Adding The Drop Down
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?php
function cd_meta_box_cb( $post )
{
$values = get_post_custom( $post->ID );
$text = isset( $values['my_meta_box_text'] ) ? esc_attr( $values['my_meta_box_text'][0] ) : ”;
$selected = isset( $values['my_meta_box_select'] ) ? esc_attr( $values['my_meta_box_select'][0] ) : ”;
$check = isset( $values['my_meta_box_check'] ) ? esc_attr( $values['my_meta_box_check'][0] ) : ”;
?>
<p>
<label for="my_meta_box_text">Text Label</label>
<input type="text" name="my_meta_box_text" id="my_meta_box_text" value="<?php echo $text; ?>" />
</p>
<p>
<label for="my_meta_box_select">Color</label>
<select name="my_meta_box_select" id="my_meta_box_select">
<option value="red" <?php selected( $selected, 'red' ); ?>>Red</option>
<option value="blue" <?php selected( $selected, 'blue' ); ?>>Blue</option>
</select>
</p>
<?php
}
?>
|
With the addition of a second field, we changed or
get_post_meta call to
get_post_custom,
which returns an associative array of all the post's custom keys and
values. We then just access our fields via their names. The ternary
statements keep our code from throwing PHP warnings (undefined indices
and such). We'll cover the esc_attr function in step three.
In the drop down, we're going to use one of WordPress's most handy functions:
selected. This compares the first value, the data we saved, with the second, the
<option>'s value attribute. If they're the same, the function will echo
selected="selected",
which makes that value display on the drop down. Pretty sweet, and it
saves us from writing a bunch of if or ternary statements. You can also
use the
selected() function with radio buttons.
Adding the Check-box
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<?php
function cd_meta_box_cb()
{
global $post;
$values = get_post_custom( $post->ID );
$text = isset( $values['my_meta_box_text'] ) ? $values['my_meta_box_text'] : '';
$selected = isset( $values['my_meta_box_select'] ) ? esc_attr( $values['my_meta_box_select'] ) : '';
$check = isset( $values['my_meta_box_check'] ) ? esc_attr( $values['my_meta_box_check'] ) : '';
wp_nonce_field( 'my_meta_box_nonce', 'meta_box_nonce' );
?>
<p>
<label for="my_meta_box_text">Text Label</label>
<input type="text" name="my_meta_box_text" id="my_meta_box_text" value="<?php echo $text; ?>" />
</p>
<p>
<label for="my_meta_box_select">Color</label>
<select name="my_meta_box_select" id="my_meta_box_select">
<option value="red" <?php selected( $selected, 'red' ); ?>>Red</option>
<option value="blue" <?php selected( $selected, 'blue' ); ?>>Blue</option>
</select>
</p>
<p>
<input type="checkbox" id="my_meta_box_check" name="my_meta_box_check" <?php checked( $check, 'on' ); ?> />
<label for="my_meta_box_check">Do not check this</label>
</p>
<?php
}
?>
|
Again WordPress provides the handy function
checked(). It works just like
selected() comparing the first value (our saved data) to the second and echoing out
checked="checked" if they're the same.
wp_nonce_field adds two hidden fields to our meta box. One of them is a
nonce.
These are random strings of numbers that are valid on per user per
blog basis for 24 hours. Nonces are a way of verifying intention, and
they make sure that WordPress doesn't do anything unless the request
came from a very specific place. In other words, we don't want to
accidentally update our data by somehow running our save function (see
step 3) in another location other than the save_post hook, so we check
to make sure the nonce is valid before doing anything.
Step 3 Saving the Data
The number one rule when putting anything into your database or on your site is don't trust the user. Even if that user is you.
To save our data, we're going to rely on another WordPress hook:
save_post. This works just like our action hook above:
1
|
<?php add_action( 'save_post', 'cd_meta_box_save' ); ?>
|
The
cd_meta_box_save function will receive one argument, the post id, and take care of cleaning and saving all of our data. The
save_post hook fires after the update or save draft button has been hit. So we have access to all the
$_POST
data, which includes our meta box fields, within our saving function.
Before we can do anything, however, we have to do three things: check if
the post is auto saving, verify the nonce value we created earlier, and
check to make sure the current user can actually edit the post.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<?php
add_action( 'save_post', 'cd_meta_box_save' );
function cd_meta_box_save( $post_id )
{
if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if( !isset( $_POST['meta_box_nonce'] ) || !wp_verify_nonce( $_POST['meta_box_nonce'], 'my_meta_box_nonce' ) ) return;
if( !current_user_can( 'edit_post' ) ) return;
}
?>
|
Now the fun stuff: actually saving our data. The number one rule when putting anything into your database or on your site is
don't trust the user.
Even if that user is you. To that end, before we save any data, we
want to make sure there's nothing malicious in there. Fortunately
WordPress provides a bunch of functions for
data validation.
You already saw
esc_attr() above (step 2). This one
encodes ' " and < > into HTML entities. Why use this? So users
couldn't type a <script> into your meta box. If you'd like to
allow certain HTML tags in, but strip others,
wp_kses can
do that. It takes two arguments, the first of which is the string you'd
like to check and the second is an associative array of allowed tags.
WordPress provides many more data validation tools, just don't be afraid
to use them.
We're going to use the
update_post_meta function to date care of saving our data. It takes three arguments: a post ID, the meta key, and the value.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<?php
add_action( 'save_post', 'cd_meta_box_save' );
function cd_meta_box_save( $post_id )
{
if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if( !isset( $_POST['meta_box_nonce'] ) || !wp_verify_nonce( $_POST['meta_box_nonce'], 'my_meta_box_nonce' ) ) return;
if( !current_user_can( 'edit_post' ) ) return;
$allowed = array(
'a' => array(
'href' => array()
)
);
if( isset( $_POST['my_meta_box_text'] ) )
update_post_meta( $post_id, 'my_meta_box_text', wp_kses( $_POST['my_meta_box_text'], $allowed ) );
if( isset( $_POST['my_meta_box_select'] ) )
update_post_meta( $post_id, 'my_meta_box_select', esc_attr( $_POST['my_meta_box_select'] ) );
$chk = isset( $_POST['my_meta_box_check'] ) && $_POST['my_meta_box_select'] ? 'on' : 'off';
update_post_meta( $post_id, 'my_meta_box_check', $chk );
}
?>
|
Wrap Up
That's it! You should have a fully working meta box. Other examples
you may find around the web loop through a bunch of fields without
really cleaning the data. This is the wrong approach. Always use the
built in data validation functions; different fields/values may require
different data validation.
To use these custom fields on the front end of your site, use the
get_post_meta or
get_post_custom functions (see step 2).