Theme Mods API or Settings API?

A few days ago, Chip Bennett wrote a good tutorial on the APIs related to theme options and broke down how those APIs can/should be used depending on the context. If anyone can really go in depth with theme options, it’s Chip. I’m hoping we see more from him on theme options in the future. I highly encourage reading his post to make sure you have the foundation you need.

In this post, I want to cover a question that has been popping up, particularly about the Theme Mods vs. Settings and which to use.

Essentially, we allow three different methods of handling theme options on the repository (technically, there are other methods, but we allow three):

  • Customizer API + Theme Mods
  • Customizer API + Settings API
  • Theme options page created with the Settings API

The customizer is so much easier to use and requires a lot less code. It’s something I strongly recommend. It’s also what I’ll be focusing on as I write more about theme options. This post is going to focus on whether you should use the Theme Mods API or the Settings API for storing options.

Theme Mods

The thing many theme authors get hung up on is that theme mods are saved on a per-theme basis. So, this means when you switch child themes, any theme mod options will need to be saved again for the new child theme. Sometimes, users might even see this as “losing their settings”.

This is both a blessing and a curse. I love it because it allows my users to have seasonal or holiday child themes. Imagine you wanted to have a child theme specifically for the Christmas season with its own color scheme. It’s nice to be able to change those color options just for the Christmas child theme.

What happens when you switch back to your regular theme in January? Well, it still has all of its unique options saved for it. There’s no work involved except for switching child themes.

The other nice thing is that core WP has hooks already built in for each new theme mod you create. That’s very nice for child theme authors who want to quickly filter options in the parent theme.

Settings API

I’m less of a fan of the Settings API because it means that an option is saved based on the parent theme. The great thing about it is that users only have to save options once and not save them again if they switch child themes (technically, you can get around that if anyone wants me to explain the code).

Going back to the Christmas theme example: What happens when you switch to a Christmas child theme? In this scenario, you’d have to change your colors. Then, you’d have to change them again when switching back to your non-holiday child theme.

However, not all theme options need to be changed when a user switches child themes. Some options make more sense to stay the same, regardless of child theme.

What about a combination?

Actually, you can use a combination of both the Theme Mods API and Settings API with the customizer. It’s not an either/or thing. That’s just another example of its awesomeness. I even encourage this practice in some situations.

I imagine things like colors and fonts would generally make more sense as theme mods. But, an option to either show excerpts or content on the front page might be better served via the Settings API.

Each option should really be given this consideration rather than blindly doing one or the other.

#customizer-api, #settings-api, #theme-mods-api

Understanding the APIs Related to Theme Options

There seems to be a great deal of confusion regarding the various APIs involved with Theme options, especially regarding the interactions among those APIs. This confusion is obvious in discussions involving the Customizer. This post will attempt to explain the various APIs and how they work together.

The first thing to understand is the various interactions with options: storing options in the database, retrieving options from the database, and user configuration of options.

Storing and Retrieving Options

WordPress has two APIs for storing and retrieving Theme options: the Options API and the Theme Modification (Theme Mods) API. The Options API is the “granddaddy” API, used by core and Plugins, as well as Themes. The Theme Mods API is a Theme-specific API.

The Options API uses the add_option() and update_option() functions to store options to the database, and get_option() to retrieve options from the database. The Theme Mods API uses the set_theme_mod() to store options to the database, and get_theme_mod() and get_theme_mods() to retrieve options from the database. Neither API deals with user configuration (e.g. Settings pages) of options.

User Configuration of Options

There are essentially three ways to allow users to configure Theme options: a Settings API-generated settings page, a custom settings page, and the Customizer API. Both a settings page and the Customizer display options retrieved by either the Settings API or the Theme Mods API, then return user-configured settings to that same API for storage in the database.

(Note: where the Theme Review Guidelines formerly recommended using the Settings API, that recommendation applied to using the Settings API to generate the settings page, rather than the Theme creating a custom settings page. That recommendation has been superseded by the recommendation to use the Customizer.)

The Settings API includes a robust set of functions to generate a Settings page, but is missing some key elements such as default markup for settings fields, and standard sanitization/validation based on option data type. This void is frequently filled by the various option framework libraries available. Using such frameworks avoids the necessity of the Theme developer to roll their own code. These frameworks generally also handle the sometimes complex callback structure needed to implement the Settings API fully. (And no joke; fully implementing the Settings API is so complex that I wrote a ten-page tutorial on it a few years ago.)

Until the Customizer, the only other option was a completely custom Theme settings page. These used to be all the rage (and can still be found in many options frameworks), and were promoted as a Theme “feature”. All they really did was add a non-core UI and increase the learning curve for Theme developers and users alike. That’s why the Theme Review Guidelines used to recommend use of the Settings API to generate Theme settings pages.

Now, for Themes that opted to use the Theme Mods API, a custom Theme settings page was the only option. The Theme Mods API is, overall, probably a better and easier API to understand and to use – but it did not include an API for creating a settings page.

Customizer API

Now, a third option exists: the Customizer.

It is important to keep in mind here that the Customizer is not an API for storing or retrieving settings; it is an API for user configuration of settings. It is not a replacement for either the Settings API or the Theme Mods API for storing/retrieving options; it is a replacement for Theme settings pages. The Customizer does not store options to or retrieve options from the database. It merely displays options retrieved by either the Theme Mods API or the Settings API, and returns settings to either the Theme Mods API or the Settings API for storage in the database.

This is a very important distinction to remember, because, while the Customizer defaults to using the Theme Mods API, it can be used with either the Theme Mods API or the Settings API. This flexibility is important for existing Themes as well as for new Themes. Developers of existing Themes that use the Settings API, and who want to migrate away from settings pages and toward use of the Customizer can do so quite easily. And Developers of new Themes that want to take advantage of the Customizer from the beginning can do so just as easily.

Key Points

  1. Theme Mods API and Options API are used to store/retrieve options
  2. Settings pages and the Customizer are used to allow users to configure options
  3. The Customizer can replace a Settings page and the Settings API, but cannot replace either the Theme Mods API or the Options API
  4. The Customizer can be used on top of either the Theme Mods API or the Options API
  5. Themes that currently use the Settings API, either directly or using an options framework library, can use the Customizer without changing any of the underlying options code already in use
  6. Themes that currently use a Settings page with the Settings API can add Customizer support without impacting or removing the current Settings page
  7. New Themes are recommended to use the Customizer with the Theme Mods API

#customizer-api, #options-api, #settings-api, #theme-mods-api

Using Sane Defaults in Themes

With the release of WordPress 3.9, one of the changes to the Theme Review Guidelines is that Themes must use sane defaults. That means that Themes must not write default setting values to the database. For many Themes, this may seem like a major change; but it doesn’t have to be. This post will step through a few ways to implement sane defaults.

Portability/DRY

To make this method easier, put all of your defaults inside a function:

function themeslug_get_option_defaults() {
	$defaults = array(
		'option_1' => 'value_1',
		'option_2' => 'value_2',
		'option_3' => 'value_3'
	);
	return apply_filters( 'themeslug_option_defaults', $defaults );
}

(Note: by making the return valuable filterable, the Theme defaults can be easily overridden by a Child Theme or Plugin.)

We’ll make use of this function later.

Options API

Most Themes use the Options API, and will use get_option() to put Theme settings into a global:

$themeslug_options = get_option( 'theme_themeslug_options' );

Knowing that this get_option() caall will return FALSE if the option has not yet been saved to the database, Theme developers have taken to saving default values to the database as part of Theme initialization, like so:

if ( false == get_option( 'theme_themeslug_options' ) ) {
	update_option( 'theme_themeslug_options', themeslug_get_option_defaults() );
}
$themeslug_options = get_option( 'theme_themeslug_options' );

But this is entirely unnecessary. And everything needed to implement a better solution already exists.

As a first step, consider that get_option() includes a second parameter, which specifies the default value to return, if nothing is returned from the database:

get_option( $name, $default );

So, the simplest solution is merely to tell get_option() to return the defaults, using the function we previously defined:

$themeslug_options = get_option( 'theme_themeslug_options', themeslug_get_option_defaults() );

This works, but isn’t perfect. It will return the Theme-defined defaults if the user hasn’t saved settings to the databse. But if later versions of the Theme add, remove, or change options, this might break, since the return value is either/or: either the database-saved setting, or else the defaults. So, if the user saves settings, and then a new setting is added in a later Theme version, the new setting value won’t be included in $themeslug_options unless/until the user saves settings again.

The solution is to merge the arrays, rather than to return one or the other. WordPress has a core function specifically for this purpose: wp_parse_args(), which will use the settings array, and “fill in the blanks” with the defaults array:

wp_parse_args( $settings, $defaults );

Caveat: bearing in mind that wp_parse_args() expects both parameters to be arrays, and knowing that get_option() returns FALSE by default, be sure to specify get_option() returns an empty array by default: get_option( ‘theme_themeslug_options’, array() ); otherwise, wp_parse_args() will (might – see note below) choke if the user hasn’t saved settings to the database.

The construct will look something like this:

$themeslug_options = wp_parse_args( 
    get_option( 'theme_themeslug_options', array() ), 
    themeslug_get_option_defaults() 
);

This is perhaps the simplest, most elegant way to implement sane defaults.

(Note: according to Otto, passing an empty arrayy() as the second parameter to get_option() isn’t necessary. In his words: “The wp_parse_args() function checks for the first parameter to be an object or an array. If it’s neither, then it calls wp_parse_str on it, because it can take a GET URL-like array of parameters too. The wp_parse_str() function calls PHP’s parse_str on it, and does a deep strip_slashes if magic quotes is on, then filters the result. So, because false maps to the empty string, parse_str will return an empty array for it, so passing false to wp_parse_args should be A-OK and probably has been like that for a very long time. Doesn’t hurt to add the empty array(), but doesn’t really change anything.” YMMV.)

Theme Modification API

Using the Theme Modification API (get_theme_mod()/get_theme_mods()) is fairly similar.

An individual setting can be called via:

get_theme_mod( $name, $default );

But perhaps more useful, all settings can be called via:

$themeslug_options = get_theme_mods();

Since get_theme_mods() returns an array, you can use the same technique as with Options API settings:

$themeslug_options = wp_parse_args( 
    get_theme_mods(), 
    themeslug_get_option_defaults() 
);

Portability/DRY (Part 2)

To be able to use this method throughout the Theme, wrap the wp_parse_args() call inside a function:

function themeslug_get_options() {
    // Options API
    return wp_parse_args( 
        get_option( 'theme_themeslug_options', array() ), 
        themeslug_get_option_defaults() 
    );
    // Theme Mods API:
    /*
    return wp_parse_args( 
        get_theme_mods(), 
        themeslug_get_option_defaults() 
    );
    */
}

Then, wherever you need access to Theme options:

$themeslug_options = themeslug_get_options();

From there, you can globalize $themeslug_options, or cache/transient it, etc. When you need to add a new option, simply add the new option default to the defaults array, and the Theme will handle it automatically.

#guidelines, #sane-defaults, #settings-api, #theme-mods-api

WordPress 3.2: Fixing the edit_theme_options/manage_options bug

Theme Developers:

Many of you are aware of a minor bug in the Settings API implementation for Themes, in which add_theme_page() is passed the “edit_theme_options” capability, but the settings form submission via options.php requires the “manage_options” capability, resulting in users with the Editor role having access to the settings page, but being unable to submit the settings page form.

Fortunately, WordPress 3.2 adds a new filter that can be used to resolve this issue: option_page_capability_{option_page}, where {option_page} is the name of the option page, as defined in the fourth parameter passed to add_theme_page().

To get an idea of how to implement the fix, refer to the patch for the filter, which also includes an implemented fix for TwentyEleven. The key is to ensure that the capability passed to the hook is the same as the capability passed to add_theme_page.

Here’s one example of how to implement:

First, create a function to return the “edit_theme_options”:

function themeslug_get_options_page_cap() {
    return 'edit_theme_options';
}

Then, hook the function:

add_filter( 'option_page_capability_themeslug-options', 'themeslug_get_options_page_cap' );

Then, ensure consistency with the add_theme_page() call. Notice that I replace the hard-coded string with the previously defined function:

add_theme_page(
    'Theme Name Options',
    'Theme Name Options',
    themeslug_get_options_page_cap(),
    'themeslug-options',
    'themeslug_callback_function'
);

If you’re already testing against the 3.2 Release Candidate (and you are, aren’t you?), you should now be able to save Theme settings as a user with the Editor role.

#3-2, #best-practices, #settings-api

Themes and the WP Settings API

The Settings API was initially introduced in WordPress 2.7, and is incredibly powerful. Part of its power is its automation of settings security (nonces, etc.), as well as automation of most of the admin options-page generation. Thus, use of the Settings API helps ensure more robust data security and more consistent user experience.

Unfortunately, it doesn’t seem to have met with much widespread adoption in Theme development. Granted, very little documentation exists for proper implementation of the Settings API, and thus the learning curve is somewhat steep. To that end, Otto has an excellent tutorial, and Press Coders have a great write-up as well.

I will be adding a Theme-centric Settings API tutorial to my to-do list. Given the increasing exposure to WordPress security issues – both real and perceived – I believe that, as we go forward, ensuring the robustness of data integrity and security of repository-hosted Themes will become increasingly important. Further, I believe that adoption of the Settings API will eventually prove to be requirement toward those ends.

So, Theme Developers: have you tried to use the Settings API? What issues have you encountered? What difficulties do you envision in attempting to migrate your Themes’ current settings functionality to use of the Settings API? How long do you believe would be reasonable before the Theme Repository begins encouraging Theme developers to use the Settings API, either as a recommendation or as a requirement?

Please note: this is an extremely early-stage discussion. At best, use of the Settings API might show up as recommended when the Guidelines are revised for WordPress 3.2. But, we want as much input as possible, as early as possible, so that we can begin addressing whatever concerns may exist.

So, what are your thoughts?

#settings-api, #theme-options