WordPress Meta Boxes: a Comprehensive Developer’s Guide

This tutorial will attempt to cover everything you could possibly want to know about using meta boxes in WordPress. Although it is possible to add meta boxes directly to a theme, best practice is to add meta boxes via a plugin. In this tutorial, we will build a plugin to add a meta box to the WordPress post editor.

The full plugin is available on github.

The add_meta_box() function was added to WordPress in version 2.5, so it’s been around a while. If you read this whole tutorial, you’ll be around a while as well. So let’s get started…

1. Create a new plugin

I’m not going to go into the reasons for placing our code in a plugin here. Best practices such as this have been discussed in detail elsewhere. For purposes of this tutorial, we will create a folder called “custom-meta-box-template”. Inside this folder, we’ll create a file called “custom-meta-box-template.php” and place the following documentation at the top:

<?php

/*
Plugin Name: Custom Meta Box Template
Plugin URI: https://themefoundation.com/
Description: Provides a starting point for creating custom meta boxes.
Author: Theme Foundation
Version: 1.0
Author URI: https://themefoundation.com/
*/

This is the standard header for any WordPress plugin. Feel free to modify it to fit your project.

2. Add a new meta box

Adding a new meta box is fairly simple. We’ll start by placing the following code into the custom-meta-box-template.php file we just created.

/**
 * Adds a meta box to the post editing screen
 */
function prfx_custom_meta() {
	add_meta_box( 'prfx_meta', __( 'Meta Box Title', 'prfx-textdomain' ), 'prfx_meta_callback', 'post' );
}
add_action( 'add_meta_boxes', 'prfx_custom_meta' );

Here we create a function called “prfx_custom_meta” and attach it to the “add_meta_boxes” action hook (If you don’t understand WordPress action hooks, I would suggest reading Nathan Rice’s excellent Introduction to WordPress Action Hooks post). This function has only one line, and it calls the add_meta_box() function. In the code above, we’ve only used the four required parameters of the seven parameters accepted by the “add_meta_box” function. Here are all seven.

Function: add_meta_box()
$id string The unique ID for this meta box.
$title string The display title of the meta box. Please note: if the meta box you are creating is intended for public release (in a theme, plugin, etc.), this string should be internationalized
$callback string The name of the function that will output the contents of the meta box.
$post_type string The post type where this meta box should be displayed.
$context string Optional. Specifies the section of the page where the meta box should be placed (normal, advanced, or side). Default: advanced.
$priority string Optional. Specifies the order within the section where the meta box will be placed (high, core, default, or low). Default: default.
$callback_args array Optional. An array of arguments that can be passed to the callback function.

Before we go edit a post to check out our new meta box, we need to create the callback function that will output the content of the meta box. Remember, the name of this next function must match the string we used as the $callback parameter above. We’ll keep it simple to start with.

/**
 * Outputs the content of the meta box
 */
function prfx_meta_callback( $post ) {
	echo 'This is a meta box';	
}

Now if we go create a new post or edit an existing post, we should see our new meta box looking something like this.

WordPress meta box example
3. Add a form field to the meta box

Now that our new meta box is working, let’s add a form field in place of the “This is a meta box” line. We can do that by completely replacing our old callback function with this updated one:

/**
 * Outputs the content of the meta box
 */
function prfx_meta_callback( $post ) {
	wp_nonce_field( basename( __FILE__ ), 'prfx_nonce' );
	$prfx_stored_meta = get_post_meta( $post->ID );
	?>

	<p>
		<label for="meta-text" class="prfx-row-title"><?php _e( 'Example Text Input', 'prfx-textdomain' )?></label>
		<input type="text" name="meta-text" id="meta-text" value="<?php if ( isset ( $prfx_stored_meta['meta-text'] ) ) echo $prfx_stored_meta['meta-text'][0]; ?>" />
	</p>

	<?php
}

First, we create a nonce for security purposes. I won’t try to explain all the ins and outs of nonces here, but if you’re interested in the subject, I would suggest reading Mark Jaquith’s post on nonces in WordPress.

Next, we retrieve all the meta information stored with this post using the get_post_meta() function. This function will be explained a little later when we use our stored meta input in the theme.

Finally, we have a label and an input field. Pretty standard for a form. Don’t mind the “prfx-row-title” class. That will come into play later when we start styling our form with CSS. Notice the value of the input is displayed using the $prfx_stored_meta variable we set using the get_post_meta() function. It won’t display anything yet, since we haven’t added the code to save the input, but eventually it will populate the text input with the previously saved value. That leads us to the next step in this tutorial.

4. Save the meta box form input

Let’s start out with the code and then look at what it does.

/**
 * Saves the custom meta input
 */
function prfx_meta_save( $post_id ) {

	// Checks save status
	$is_autosave = wp_is_post_autosave( $post_id );
	$is_revision = wp_is_post_revision( $post_id );
	$is_valid_nonce = ( isset( $_POST[ 'prfx_nonce' ] ) && wp_verify_nonce( $_POST[ 'prfx_nonce' ], basename( __FILE__ ) ) ) ? 'true' : 'false';

	// Exits script depending on save status
	if ( $is_autosave || $is_revision || !$is_valid_nonce ) {
		return;
	}

	// Checks for input and sanitizes/saves if needed
	if( isset( $_POST[ 'meta-text' ] ) ) {
		update_post_meta( $post_id, 'meta-text', sanitize_text_field( $_POST[ 'meta-text' ] ) );
	}

}
add_action( 'save_post', 'prfx_meta_save' );

The first part of the function checks to make sure we really want to save the meta input. These lines are based on Tom McFarlin’s user_can_save() function detailed in his blog post Save Custom Post Meta – Revisited, Refactored, Refined. Basically, they make sure that the nonce is valid and this isn’t an autosave.

The second part of the function checks to see if any data has been entered into the “meta-text” field we created. If there is text to save, it uses the update_post_meta() function to save the text to the database. This function has 4 parameters:

Function: update_post_meta()
$post_id integer The unique ID of the post being saved
$meta_key string The unique meta key specifying where the input should be saved.
$meta_value string The input to be saved to the database.
$prev_value string Optional. If multiple meta boxes have the same key, the previously saved value can be used to differentiate between them.

So in the code above, we the parameters we passed to this function were the post ID, the ID of the our text input, and the value of the text input. However, notice that we didn’t give it the value of the text input directly. Instead we used a sanitization function to prepare the input before placing it in the database. This isn’t a tutorial about validation and sanitization, but if you’re working with user input, you should never be placing unchecked user input into the database. More information about validation and sanitization is available in the Codex. I suggest you take a look at that since I won’t be covering input validation/sanitization any more in this tutorial.

5. Using saved meta data

Now that we’ve successfully saved our meta data, it’s time to use it in the theme. I’m not going to give you an exact location to place this code, because it will vary depending on what you’re trying to do. Anyway, here’s the code we’ll want to use in our template file:

<?php

	// Retrieves the stored value from the database
	$meta_value = get_post_meta( get_the_ID(), 'meta-text', true );

	// Checks and displays the retrieved value
	if( !empty( $meta_value ) ) {
		echo $meta_value;
	}

?>

There are a few things to notice about this code. First is the get_post_meta() function. This function can be tricky, so make sure to read the table below carefully.

Function: get_post_meta()
$post_id integer The unique ID of the post to which the meta data belongs. In most cases, we’ll want the meta data for post currently being displayed. We can find the ID for the current post using the get_the_ID() function.
$key string Optional. If only one value is needed from the database, we can specify a single value by providing the meta key we used when saving the input to the database.
$single boolean Optional. This determines whether the value is returned as a string or an array. If set to true, a string will be returned. Default: false.

Please note: by default, an array of all meta values is returned. This includes post meta data that is not part of our meta box. Using parameter #2, we’re specifying which meta value we want to retrieve from the database. However, if we leave off parameter #3, we’ll still be getting an array back, rather than a string. Then to get our data we would have to use something like $meta_value[0]. I can’t say that one way is better than the other, but I personally like to make use of parameter #3 to force the data to be returned as a string when using the function in a theme template file. However, if you remember that the prfx_meta_callback() function we used to display our meta box content used get_post_meta() with only the first parameter, which means an array of all stored meta box values will be returned. Since we’ll be adding a bunch of other meta box input fields, it’s nicer to grab all the stored meta fields at once, rather than one by one.

Finally, we want to check to make sure a value was returned before outputting it to the page. Now you may be thinking, “Couldn’t we just output it to the page without checking to make sure a value was returned?” …and that is a valid question. In fact, the answer is yes! You could most definitely do that. However, keep in mind that in most cases you would be enclosing this value in some sort of HTML element if using this in a real life situation and not a tutorial. Typically, if no value is returned, you wouldn’t want to output a blank HTML element. This is why I’ve included the if( !emtpy ) statement even though it isn’t absolutely required.

6. More meta box locations

So far we have been working on a meta box that is displayed at the bottom of the post editing screen. Now let’s move it around. Remember way back at the beginning of this tutorial when we first added the meta box to the post editing screen using the add_meta_box() function? There were a couple more parameters that we didn’t use at the time: $context and $priority. Let’s use those now, as well as the $post_type parameter that we’re already familiar with. If we’re taking the time to build a custom meta box, we probably don’t want it to be placed below all the default meta boxes on the post editing screen (Excerpt, Discussion, Comments, Revisions, etc.). If we want to place our meta box above the default boxes, we can replace our original call to the add_meta_box() function with this:

	add_meta_box( 'prfx_meta', __( 'Meta Box Title', 'prfx-textdomain' ), 'prfx_meta_callback', 'post', 'normal', 'high' );

Notice the priority set to “high” in the last parameter.

If we want to place it in the sidebar of the page editor instead of the main row of the post editor, we can use this instead:

	add_meta_box( 'prfx_meta', __( 'Meta Box Title', 'prfx-textdomain' ), 'prfx_meta_callback', 'page', 'side' );

Or we can add our meta box to a media attachment with this line:

	add_meta_box( 'prfx_meta', __( 'Meta Box Title', 'prfx-textdomain' ), 'prfx_meta_callback', 'attachment' );

Meta boxes can also be added to custom post types. We would just need to replace parameter #4 with the slug of the post type we wanted to add our meta box to. For the rest of the tutorial, I’m going to set it back to the original values:

	add_meta_box( 'prfx_meta', __( 'Meta Box Title', 'prfx-textdomain' ), 'prfx_meta_callback', 'post' );

7. Adding CSS styles to the meta box content

When we start adding more form elements to our meta box, it’s going to start looking messy real quickly. To keep everything in order, we’ll create a file called metabox.css in the plugin’s base directory and add the following styles to it:

#prfx_meta{
	overflow: hidden;
	padding-bottom: 1em;
}

#prfx_meta p{
	clear: both;
}

.prfx-row-title{
	display: block;
	float: left;
	width: 200px;
}

.prfx-row-content{
	float: left;
	padding-bottom: 1em;
}

.prfx-row-content label{
	display: block;
	line-height: 1.75em;
}

We’re not trying to create a new user interface here, just keep our form in order. We’re using the “prfx-form-title” class we mentioned earlier to float the form element titles to the left and give them a consistent width. This isn’t a tutorial on CSS though, so I’m not going to explain each style in detail. This is simply a very minimal set of rules to give the form a consistent layout.

To actually load this style sheet on the necessary pages of the WordPress admin area, we’ll go back to the custom-meta-box-template.php file and add the following function and attach it to the “admin_print_styles” action hook:

/**
 * Adds the meta box stylesheet when appropriate
 */
function prfx_admin_styles(){
	global $typenow;
	if( $typenow == 'post' ) {
		wp_enqueue_style( 'prfx_meta_box_styles', plugin_dir_url( __FILE__ ) . 'meta-box-styles.css' );
	}
}
add_action( 'admin_print_styles', 'prfx_admin_styles' );

This is a pretty simple function, but we’ll want to notice a couple things before moving on. First, we’re checking to make sure the $typenow global variable matches the $post_type parameter we used when we first called add_meta_box (in this case, ‘post’).

Please note: this method is not perfect. For example, this method will load the metabox.css file on the Posts > Categories page of the admin, even though it isn’t needed there. However, it will not load on any pages outside of the “Posts” menu section. There are other ways this can be handled, but they aren’t as simple. Here we can just match the original $post_type parameter and that’s about it.

Second, there’s the wp_enqueue_style() function that gets called if we’re loading a post page. For our purposes here, we only need to use the first two parameters of this function. The first is unique name we’re giving our style sheet, and the second is the URL to the style sheet so it can be properly loaded.

8. Standard meta box input fields

We created a standard text box already, so let’s jump straight into the rest of the standard form input types.

8.1 Checkboxes

A checkbox is a little more complicated than a text input, but not by much. There are times when we may want to use multiple checkboxes in a group, so we have to account for that in the way we format our HTML. This code will go just below the text input we created in the prfx_meta_callback() function earlier.

	<p>
		<span class="prfx-row-title"><?php _e( 'Example Checkbox Input', 'prfx-textdomain' )?></span>
		<div class="prfx-row-content">
			<label for="meta-checkbox">
				<input type="checkbox" name="meta-checkbox" id="meta-checkbox" value="yes" <?php if ( isset ( $prfx_stored_meta['meta-checkbox'] ) ) checked( $prfx_stored_meta['meta-checkbox'][0], 'yes' ); ?> />
				<?php _e( 'Checkbox label', 'prfx-textdomain' )?>
			</label>
			<label for="meta-checkbox-two">
				<input type="checkbox" name="meta-checkbox-two" id="meta-checkbox-two" value="yes" <?php if ( isset ( $prfx_stored_meta['meta-checkbox-two'] ) ) checked( $prfx_stored_meta['meta-checkbox-two'][0], 'yes' ); ?> />
				<?php _e( 'Another checkbox', 'prfx-textdomain' )?>
			</label>
		</div>
	</p>

Here we have a group of two checkboxes that share a title. Notice that the first element within the paragraph is a span, rather than a label like we used earlier when we built the text input. This title serves as a heading for multiple checkboxes, so we save the label elements for use with the individual checkboxes. Also, we used the checked() function built into WordPress to determine if each checkbox should be checked based on the values stored in the database.

We should now be able to see the new checkboxes in the post editor, but we need some more code before we can actually save the checkbox inputs. To accomplish this, we will add the following code to the bottom of the prfx_meta_save() function we created earlier:

	// Checks for input and saves
	if( isset( $_POST[ 'meta-checkbox' ] ) ) {
		update_post_meta( $post_id, 'meta-checkbox', 'yes' );
	} else {
		update_post_meta( $post_id, 'meta-checkbox', '' );
	}

	// Checks for input and saves
	if( isset( $_POST[ 'meta-checkbox-two' ] ) ) {
		update_post_meta( $post_id, 'meta-checkbox-two', 'yes' );
	} else {
		update_post_meta( $post_id, 'meta-checkbox-two', '' );
	}

Notice that the value we save to the database (‘yes’) is the same value as the checkbox value in the previous block of code. I know I said that I wouldn’t be covering data validation, but when constructing checkboxes from scratch, hard coding the input value like this (rather than using the form input value) allows you to easily ensure that the proper value gets written to the database.

8.2 Radio Buttons

Just like with the checkboxes, each radio button option has its own label. Also like the checkboxes, we use the checked() function to determine the state of each button. Just like before, we’ll add this code to the bottom of the prfx_meta_callback() function below our checkbox code.

	<p>
		<span class="prfx-row-title"><?php _e( 'Example Radio Buttons', 'prfx-textdomain' )?></span>
		<div class="prfx-row-content">
			<label for="meta-radio-one">
				<input type="radio" name="meta-radio" id="meta-radio-one" value="radio-one" <?php if ( isset ( $prfx_stored_meta['meta-radio'] ) ) checked( $prfx_stored_meta['meta-radio'][0], 'radio-one' ); ?>>
				<?php _e( 'Radio Option #1', 'prfx-textdomain' )?>
			</label>
			<label for="meta-radio-two">
				<input type="radio" name="meta-radio" id="meta-radio-two" value="radio-two" <?php if ( isset ( $prfx_stored_meta['meta-radio'] ) ) checked( $prfx_stored_meta['meta-radio'][0], 'radio-two' ); ?>>
				<?php _e( 'Radio Option #2', 'prfx-textdomain' )?>
			</label>
		</div>
	</p>

Unlike saving checkboxes, when we go to save the radio button input, we only need to save the selected value. We don’t need to check each one individually like we did for the checkboxes. The following section of code should be placed at the bottom of the prfx_meta_save() function just below the code that saves the checkbox input.

	// Checks for input and saves if needed
	if( isset( $_POST[ 'meta-radio' ] ) ) {
		update_post_meta( $post_id, 'meta-radio', $_POST[ 'meta-radio' ] );
	}

Now that we’ve done a few different elements, I’m going to start moving a little quicker. Just remember, code for displaying form elements goes in the prfx_meta_callback() function and code for saving meta box input goes in the prfx_meta_save() function.

8.3 Select list

A select list is a single element, so we can start out with a label tag, rather than a span tag like we used for the last couple elements. However, within that select element, there are a number of different options, so we’ll still need to determine the state of each option as we display it. We do this using the selected() function, which works exactly like the checked() function we used with checkboxes and radio buttons, except that its output is specific to select lists.

	<p>
		<label for="meta-select" class="prfx-row-title"><?php _e( 'Example Select Input', 'prfx-textdomain' )?></label>
		<select name="meta-select" id="meta-select">
			<option value="select-one" <?php if ( isset ( $prfx_stored_meta['meta-select'] ) ) selected( $prfx_stored_meta['meta-select'][0], 'select-one' ); ?>><?php _e( 'One', 'prfx-textdomain' )?></option>';
			<option value="select-two" <?php if ( isset ( $prfx_stored_meta['meta-select'] ) ) selected( $prfx_stored_meta['meta-select'][0], 'select-two' ); ?>><?php _e( 'Two', 'prfx-textdomain' )?></option>';
		</select>
	</p>

The code used to save the select list input value looks just like the code used to save the radio button input. We just have to change “meta-radio” to “meta-select” and we’re good to go.

	// Checks for input and saves if needed
	if( isset( $_POST[ 'meta-select' ] ) ) {
		update_post_meta( $post_id, 'meta-select', $_POST[ 'meta-select' ] );
	}

8.4 Textarea

Our textarea code looks almost like the text box code we used at the very beginning. Here’s the code we’ll use to create a textarea.

	<p>
		<label for="meta-textarea" class="prfx-row-title"><?php _e( 'Example Textarea Input', 'prfx-textdomain' )?></label>
		<textarea name="meta-textarea" id="meta-textarea"><?php if ( isset ( $prfx_stored_meta['meta-textarea'] ) ) echo $prfx_stored_meta['meta-textarea'][0]; ?></textarea>
	</p>

The code for saving the textarea input looks almost the same as the last few.

	// Checks for input and saves if needed
	if( isset( $_POST[ 'meta-textarea' ] ) ) {
		update_post_meta( $post_id, 'meta-textarea', $_POST[ 'meta-textarea' ] );
	}

9. Additional meta box input fields

Now that we’ve covered the standard input types, let’s add some specialized inputs. Most of these inputs will require the use of javascript. For the sake of simplicity (and for those who only need to use one of these custom input types), I will add the javascript related to each input type separately. If you’re using a number of these input types, feel free to combine the javascript into a single file.

9.1 Color Picker

First we’ll create a standard text input, just like we did at the very beginning of this tutorial. Later, we’ll add some javascript to transform it using the color picker built into WordPress itself.

	<p>
		<label for="meta-color" class="prfx-row-title"><?php _e( 'Color Picker', 'prfx-textdomain' )?></label>
		<input name="meta-color" type="text" value="<?php if ( isset ( $prfx_stored_meta['meta-color'] ) ) echo $prfx_stored_meta['meta-color'][0]; ?>" class="meta-color" />
	</p>

Next, we’ll create a file in the plugin’s base directory named “meta-box-color.js” and add this code to it:

jQuery(document).ready(function($){
	$('.meta-color').wpColorPicker();
});

This specifies which element to transform into an actual color picker. Notice that “meta-color” matches the class we gave to our text input in the previous step.

Now that our javascript file has been created. we need to load it whenever our metabox is loaded. We can do that by going back to the custom-meta-box-template.php file and adding the following code block:

/**
 * Loads the color picker javascript
 */
function prfx_color_enqueue() {
	global $typenow;
	if( $typenow == 'post' ) {
		wp_enqueue_style( 'wp-color-picker' );
		wp_enqueue_script( 'meta-box-color-js', plugin_dir_url( __FILE__ ) . 'meta-box-color.js', array( 'wp-color-picker' ) );
	}
}
add_action( 'admin_enqueue_scripts', 'prfx_color_enqueue' );

This function begins by checking if this is a post screen, since our meta box is only being loaded for posts. If your meta box is located somewhere else, change “post” in the if() statement. Other locations for meta boxes are discussed in section 5, “More meta box locations.” The limitation of using the $typenow variable are discussed in section 6, “Adding CSS styles to the meta box content.”

If $typenow is equal to “post” then we start loading files. First we enqueue the “wp-color-picker” stylesheet built into WordPress. It will handle all the CSS styling of the color picker. Next, we use the wp_enqueue_script() function to load the custom javascript file we just created. Notice that is lists “wp-color-picker” as a dependency. This is required for the color picker to work properly. In the last line, we attach the function to the “admin_enqueue_scripts” action hook so it gets loaded at the proper time.

Finally, we need to add to our prfx_save_meta() function to save the input from our color picker. It looks just like the code we used to save our other inputs earlier, except that we’ve changed the name of the field being saved.

	// Checks for input and saves if needed
	if( isset( $_POST[ 'meta-color' ] ) ) {
		update_post_meta( $post_id, 'meta-color', $_POST[ 'meta-color' ] );
	}

…and that should transform the lowly text input into a working color picker.

9.2 Image uploader

Here’s where things start to get more complicated. First let’s add another input to our metabox. The text input will hold the URL of our image file. We’ll also include a button for opening the WordPress media manager to allow a seamless WordPress experience when choosing and uploading images through our meta box.

	<p>
		<label for="meta-image" class="prfx-row-title"><?php _e( 'Example File Upload', 'prfx-textdomain' )?></label>
		<input type="text" name="meta-image" id="meta-image" value="<?php if ( isset ( $prfx_stored_meta['meta-image'] ) ) echo $prfx_stored_meta['meta-image'][0]; ?>" />
		<input type="button" id="meta-image-button" class="button" value="<?php _e( 'Choose or Upload an Image', 'prfx-textdomain' )?>" />
	</p>

Next, we’ll create a file in the plugin directory named “meta-box-image.js” and add this section of javascript to it:

/*
 * Attaches the image uploader to the input field
 */
jQuery(document).ready(function($){

	// Instantiates the variable that holds the media library frame.
	var meta_image_frame;

	// Runs when the image button is clicked.
	$('#meta-image-button').click(function(e){

		// Prevents the default action from occuring.
		e.preventDefault();

		// If the frame already exists, re-open it.
		if ( meta_image_frame ) {
			meta_image_frame.open();
			return;
		}

		// Sets up the media library frame
		meta_image_frame = wp.media.frames.meta_image_frame = wp.media({
			title: meta_image.title,
			button: { text:  meta_image.button },
			library: { type: 'image' }
		});

		// Runs when an image is selected.
		meta_image_frame.on('select', function(){

			// Grabs the attachment selection and creates a JSON representation of the model.
			var media_attachment = meta_image_frame.state().get('selection').first().toJSON();

			// Sends the attachment URL to our custom image input field.
			$('#meta-image').val(media_attachment.url);
		});

		// Opens the media library frame.
		meta_image_frame.open();
	});
});

There are a few thing in particular that should be noted about this file. First, “#meta-image-button” must match the ID of the button we placed in our meta box. Second, the line “library: { type: ‘image’ }” instructs the media manager to display only images. Finally, “#meta-image” must match the ID of the text input field we placed our meta box to hold the image URL.

Now that our javascript is ready, we’ll want add another function to the custom-meta-box-template.php file that will load it, along with anything else required by the media manager. We can do that by adding the following code to the custom-meta-box-template.php file:

/**
 * Loads the image management javascript
 */
function prfx_image_enqueue() {
	global $typenow;
	if( $typenow == 'post' ) {
		wp_enqueue_media();

		// Registers and enqueues the required javascript.
		wp_register_script( 'meta-box-image', plugin_dir_url( __FILE__ ) . 'meta-box-image.js', array( 'jquery' ) );
		wp_localize_script( 'meta-box-image', 'meta_image',
			array(
				'title' => __( 'Choose or Upload an Image', 'prfx-textdomain' ),
				'button' => __( 'Use this image', 'prfx-textdomain' ),
			)
		);
		wp_enqueue_script( 'meta-box-image' );
	}
}
add_action( 'admin_enqueue_scripts', 'prfx_image_enqueue' );

Just like we did with the color picker, this function checks to make sure the global $typenow variable is set to “post” before continuing. Next, it calls the wp_enqueue_media() function built into WordPress, which loads all the resources required by the media manager. Then it registers our meta-box-image.js file. However, unlike the color picker, we want to pass our javascript file a few lines of text, one to be used in the title area of the media manager, and the other to be placed on the button used to confirm the selected image. We do that by using the wp_localize_script() function.

Function: wp_localize_script( $handle, $object_name, $l10n )
$handle string The unique ID of the script to which this data belongs.
$object_name string The name of the object that will hold the data.
$l10n array The array of localization data.

Please note: although we’re passing our data in directly as strings, the purpose of this function is to allow the localization functions built into WordPress to be used with javascript strings. If you’re creating a theme for public release, please use the available localization functions.

After the data has been passed to the localization function, the script is enqueued. Finally, the function is attached to the admin_enqueue_scripts hook.

Almost there! We just have to add the last little section of code that saves the image URL to the database. This code gets added to the prfx_save_meta() function just like our previous inputs.

	// Checks for input and saves if needed
	if( isset( $_POST[ 'meta-image' ] ) ) {
		update_post_meta( $post_id, 'meta-image', $_POST[ 'meta-image' ] );
	}

…and that’s how to integrate the WordPress media manager into a custom meta box.

9.3 File uploader

The code for the image uploader is actually just a file uploader with a filter to display only images in the media manager. Creating a file uploader instead of an image uploader only takes a couple small modifications. First, we want to remove the following line from the meta-image.js file we created for the image uploader:

			library: { type: 'image' }

Now all file types in the media manager will be visible.

Second, we’ll want to modify the title and button text in the localization script:

				'title' => 'Choose or Upload a File',
				'button' => 'Use this file',

You may be thinking, “But what if I want to use both an image uploader and a file uploader in the same meta box (or even multiple image uploaders in the same meta box)?” That’s a good question, but I’m not going to spell it out step by step here. Let’s just say that one way you could do it would be to duplicate all the code used to create the image uploader and just change little pieces, such as the form field IDs, localization data, and the name of the javascript file. I’m not saying that’s the only way, but it is one way.

If there’s anything else that you’d like to see covered in this tutorial, please let me know via Twitter.

I’ve had one request for repeatable fields, but that is a complicated topic and I haven’t had time to address it yet.

10. Wrap up and additional reading

I hope you found this guide helpful. If you have any questions, comments, or suggestions, please let me know. If you’d like to do more reading on the subject, I would suggest checking out the following resources:

Other sources I used while creating this tutorial:

And finally, I want to say a special thanks to Ulrich Pogson for kindly fixing an issue with my original code and adding translation support.