Post Options API: An Alternative to Custom Fields

Some of the code and concepts in this post are outdated, please view the following post: Post Options API is Almost Ready for Action.

Remember Custom Fields? Yeah, the ones with a not so friendly user interface, designed to assign some meta data to posts, pages and other post types, that would hopefully appear somewhere in the post content through a function called the_meta. The most known example is the “mood” one, but theme and plugin developers nowadays use custom fields (i.e. post meta) for entirely different purposes, like title rewrites, meta description, per post/page custom CSS, AdSense banner codes, social bookmarking links display, layout configuration and much more.

The problem with custom fields is that while being okay for developers, they’re somewhat scary and sometimes frustrating for the less savvy people and it has been like that for years. To avoid that problem, developers are using custom meta boxes with their own custom form fields which are generally wrappers around the original post meta. That way the end-user can set their mood to Happy in a simple drop-down select box or perhaps a radio button group.

The trouble however is that there is no unified way of doing this, so each developer has to come up with their own, meaning redundant code, new UI, new learning curve for the end user and less flexibility. There’s a similar situation with custom admin screens and settings pages, hopefully being solved by the Settings API and today we’ll take a look at a similar concept for post meta data — the Post Options API.

The Concept

The concept is fairly simple, especially if you’ve been able to take advantage of the Settings API. You’ll find functions with similar names and somewhat similar function arguments. When I speak about posts, I generally refer to the WordPress posts table, meaning posts, pages and of course custom post types.

So the general idea is that post options are grouped into sections, just like settings are grouped into settings sections (Settings API). Post options sections are then applied to (or registered for) different post types, just like settings sections are printed in custom admin pages. Post options consist of an ID, a title, a section name and a callback function that renders the control to be used. Again, this is very similar to how the Settings API works.

After registering a few post options sections, a couple of options to each section and after adding those sections to post types, the well-known edit post screen will (magically) display a new meta box that contains all the post options nicely divided into sections. Here’s a screenshot:

Post Options Preview

Of course that’s only a rough sketch, just like the whole current implementation, but the idea is quite clear. Let’s move over to some code.

The Functions

For post options registration and options retrieval we came up with several different functions, trying to keep the concept close to the Settings API but with a few alterations of course:

  • register_post_options_section — registers a new post options section, given an ID for the section (used when registering post options to the section) and a section title which is displayed.
  • register_post_option — registers a new post options section, has an ID, a title, a section ID and a callback argument that renders the form control.
  • add_section_to_post_type — adds the provided section to the provided post type, makes it possible to reuse sections across different post types.
  • get_post_option — retrieves an option by ID from a post by ID,  this is simply a wrapper around the get_post_meta function.

So the scenario is this, register a section, register a post option to the section, add the section to a post type and finally use the get_post_option function in your template files (or wherever you like) to retrieve the registered values.

The Callback Function

I’m sure you’re familiar with callback functions and how they’re passed in as arguments to function calls. Again, this is similar to what Settings API does, but with a different approach at sanitizing input, which is also available.

Callbacks are provided in the register_post_option function, can be given additional arguments if needed and a sanitize_callback function. Here are a few examples of how callback functions are passed in:

register_post_option( array(
	'id' => 'an-option-id',
	'title' => 'The Option Title',
	'section' => 'the-section-id',
	'callback' => 'my_function'
) );

And a more advanced example with some additional arguments and a sanitize_callback function:

register_post_option( array(
	'id' => 'some-option',
	'title' => 'Some Option',
	'section' => 'real-world',
	'callback' => array(
		'function' => 'my_callback',
		'sanitize_callback' => 'sanitize_title',
		'args' => array( 1, 2, 3 )
	)
) );

Note that all this is experimental, it’s a draft and it’s subject to change, so don’t get too familiar with the syntax, arguments and array keys, not just yet ;) In both examples, the callback function my_callback is the one called to render the control on screen. It’s given an array of arguments, including the name attribute of the field, the current value, and whatever extra is passed in (an array of 1, 2 and 3 in the second example).

All this may sound a little difficult at first, but in reality it isn’t, especially after you’ve read our next section.

Predefined Callback Functions or Helpers

When dealing with forms, most of the time we use the same form controls over and over, text fields, checkboxes, etc. So we went ahead and created predefined callback functions or how we like to call them — Helpers. These cover many of the existing form field elements and more of them will of course be added in the future.

Helpers are currently accessed via static methods in a class we called Post_Options_Fields and each of the methods accepts some arguments to customize the form field to some extent. Here’s a brief example of how a checkbox field can be rendered without writing your own callback function:

register_post_option( array(
	'id' => 'an-option-id',
	'title' => 'A checkbox',
	'section' => 'the-section-id',
	'callback' => Post_Options_Fields::checkbox( array(
		'label' => 'Check me to win $500',
		'description' => 'A description will go here!'
	) )
) );

So the helper functions will render the checkbox for you, giving it a nice label around the control and a description below the checkbox.

Post Options API Checkbox

Some fields don’t have labels, can have additional data (like a radio-group), or contain sizes (like the rows argument of a textarea). All these nuances can be solved via the arguments to the callback creator function (in the above case it’s checkbox()). And then again, if you really need to take customization forward, you can go ahead and write your own set of helpers, but make sure it’s worth the effort before you do.

Alright, enough talk, let’s get to some exciting examples.

Examples

I have to warn you once again that this is an experimental project, one you shouldn’t yet use in your production environment, but one you can play around with and give us some feedback ;)

We have also wrapped all our methods into a Post_Options class and a $post_options global object which is why all our function calls come with that prefix, here’s how we initialized the object and got ourselves ready to write some examples:

// The Post_Options and Post_Options_Fields are defined above.
add_action( 'init', create_function( '', 'global $post_options; $post_options = new Post_Options();' ) );
add_action( 'init', 'post_options_test' );

function post_options_test() {
	global $post_options;

	// Register two sections and add them both to the 'post' post type
	$post_options->register_post_options_section( 'showing-off', 'Showing off various post options' );
	$post_options->register_post_options_section( 'real-world', 'Some real world examples' );
	$post_options->add_section_to_post_type( 'showing-off', 'post' );
	$post_options->add_section_to_post_type( 'real-world', 'post' );

	// Our examples will go here!
}

// Some custom callback functions will be defined somewhere here.

I hope that’s quite easy to follow. The first call to add_action defines the $post_options global and initializes it, the second call makes our test function run during WordPress init, that’s where we’ll register all our post options.

The function itself initializes two sections and adds them to the “post” post type. We’ll keep some dummy examples in the first section and then some real-world in the second one. Ready?

A Checkbox

We’ve seen something similar above, now let’s try a real one in a real section (but not with the real money of course ;)

// A simple checkbox
$post_options->register_post_option( array(
	'id' => 'a-checkbox',
	'title' => 'A checkbox',
	'section' => 'showing-off',
	'callback' => Post_Options_Fields::checkbox( array(
		'label' => 'Check me to win $500',
		'description' => 'Any sort of description can go below this checkbox and <a href="#">HTML markup</a> too!'
	) )
) );

A Sanitized Text Input

One more of the previous examples, note that the value will be sanitized using the sanitize_title function provided as the sanitize_callback argument.

// A text input with a sanitize callback
$post_options->register_post_option( array(
	'id' => 'an-input',
	'title' => 'A text input',
	'section' => 'showing-off',
	'callback' => Post_Options_Fields::text( array(
		'description' => 'The text in this input is saved and sanitized using the <code>sanitize_title</code> sanitize callback, so try and input some caps, numbers, symbols and spaces.',
		'sanitize_callback' => 'sanitize_title'
	) )
) );

A Radio Group

This is a little bit more interesting, as you can see the radio group creator function accepts a radio_data key where you can pass in the values and the labels for the radio buttons. Simple eh?

// A radio group
$post_options->register_post_option( array(
	'id' => 'a-radio-group',
	'title' => 'A radio group',
	'section' => 'showing-off',
	'callback' => Post_Options_Fields::radio( array(
		'description' => 'Radio groups accept a <code>$radio_data</code> argument where we pass in an array with values and captions for each item in the group.',
		'radio_data' => array(
			'option-1' => 'The first option',
			'option-2' => 'Another option',
			'option-3' => 'One more option'
		)
	) )
) );

A Dropdown Select Box

Very similar to the radio group shown above, just a different key and function name. This produced a nice-looking drop-down select box with the given options.

// A drop-down select box
$post_options->register_post_option( array(
	'id' => 'a-select-input',
	'title' => 'A drop-down select box',
	'section' => 'showing-off',
	'callback' => Post_Options_Fields::select( array(
		'description' => 'Select boxes are similar to radio when it comes to data input, so just provide an array of values and captions.',
		'select_data' => array(
			'option-1' => 'This is the first option',
			'option-2' => 'Hurray for the second one',
			'option-3' => 'There is room for a third'
		)
	) )
) );

Next up, some (almost) real-world examples.

Hide Sidebar Checkbox

Remember the full-width page templates that come with most free and premium themes these days? Here’s an easier way of doing it.

// Hide sidebar
$post_options->register_post_option( array(
	'id' => 'hide-sidebar',
	'title' => 'Hide sidebar',
	'section' => 'real-world',
	'callback' => Post_Options_Fields::checkbox( array(
		'label' => 'Hide sidebar on this post',
		'description' => 'Check this to hide the right sidebar on this post.'
	) )
) );

Feature This Post Checkbox

Tired of asking your end-user to “tag” their posts with a special tag to get them in the featured block on the home page, or worse — create a custom fields antry? Why not use a simple checkbox?

// Feature this post
$post_options->register_post_option( array(
	'id' => 'featured-post',
	'title' => 'Featured post',
	'section' => 'real-world',
	'callback' => Post_Options_Fields::checkbox( array(
		'label' => 'This is a featured post',
		'description' => 'Check this to feature the post in the highlights section on the homepage.'
	) )
) );

A Background Image URL Field

Creating a landing page for a Facebook campaign? Customize the look of the page with a new background image that will make it stand out from the rest ;)

// Background image
$post_options->register_post_option( array(
	'id' => 'background-image',
	'title' => 'Background image URL',
	'section' => 'real-world',
	'callback' => Post_Options_Fields::text( array(
		'description' => 'Provide the background image URL to override on this post, useful to create outstanding landing pages without the use of templates.'
	) )
) );

All the above (and more) post options are illustrated in the following screenshot.

Post Options API Examples

If you were wondering about the page layout option in the screenshot, that’s been easily implemented via a custom callback function. The full code example shows exactly how it’s been done.

Oh, and finally …

The New Mood Example

Let’s try a complete mood example with our new post options. We’ll register the post option and then add a filter to the_content that will call the my_mood_filter function that should be defined somewhere (outside of post_options_test of course).

// Mood Example
$post_options->register_post_option( array(
	'id' => 'mood',
	'title' => 'Mood',
	'section' => 'real-world',
	'callback' => Post_Options_Fields::select( array(
		'description' => 'How did you feel when writing this post?',
		'select_data' => array(
			'Happy' => 'Happy',
			'Sad' => 'Sad',
			'Disappointed' => 'Disappointed',
			'Awful' => 'Awful'
		)
	) )
) );

add_filter( 'the_content', 'my_mood_filter' );

And here’s our my_mood_filter function that grabs the mood of the post and appends it to the content that’s passed through the filter:

// A filter to the_content to show off the current mood
function my_mood_filter( $content ) {
	global $post_options, $post;
	$mood = $post_options->get_post_option( $post->ID, 'mood' );
	if ( ! empty( $mood ) )
		$content .= "<p><strong>Mood</strong>: {$mood}</p>";
	return $content;
}

The result will look something like this:

Twenty Eleven Mood Example

Download & Try it Out Yourself

Let’s go over this one more time. The Post Options API is an experimental project, you shouldn’t use or get used to this implementation in your projects, but you’re free to play around, test things and give us some valuable feedback to continue (or discontinue) working on it.

It comes as a plugin for WordPress so drop it into your plugins folder, activate it and dig into the code, watching what’s happening on the edit post page side. Some of the code is well-commented, some isn’t, but we’re working on that. Most of it should be self-explanatory, especially after reading this article.

The Post Options API project is currently hosted as an open-source project on Github and at the time of writing I tagged it as 0.1, so go ahead and grab your copy, fork it, follow it and stay tuned with what’s happening on the master branch. If you’ve got some cool ideas and would like to contribute, we would really love and appreciate that, so let us know.

Post Options API on Github

That’s it for today my friends! Do share your thoughts, comments and suggestions with us. Have you found an easier approach at custom fields? How are you currently dealing with post meta and how do your end-users and customers find it? We’re all ears so say it out loud! Thank you for reading and hope you enjoyed it. Keep up with our RSS feed for the latest WordPress tips and tricks, tutorials, experiments and giveaways!

Update: Some of the code and concepts in this post are outdated, please view the following post: Post Options API is Almost Ready for Action.