Making an Admin Options Page With the WordPress Settings API

fractal | the wordpress settings api

This article covers the benefits of the Settings API, how to use it to create admin options pages, and how to keep your sanity despite its oddities.

My last article walked through making a WordPress admin options page: one of the numerous pages in wp-admin that allow you, the site administrator, to change anything from your site’s color scheme to which Twitter feed a widget pulls from, and which you’re likely to find yourself making in the process of building either a plugin or theme.

In that article, I mentioned that I’m not a big fan of the default way you create these pages: the WordPress Settings API. My main complaints are as follows:

  1. It’s made up of lots of functions which take huge numbers of parameters.
  2. The order of those parameters often feels counterintuitive and easy to forget.
  3. All this complexity is being introduced to solve real, but not giant, problems with the other possible ways you can make an admin options page in WordPress.

However, if I’m honest, I’m probably exaggerating the pain and undervaluing the benefits of the Settings API. There’s a lot that’s good about it, especially if you’re willing to put in the time to make sense of it.

Here, we’ll cover its benefits, how to use it, and how to keep your sanity despite its oddities. I think you’ll get the most out of this if you’ve read through my earlier article on making WordPress admin pages, but I will recap the most important points before moving into the heart of the Settings API.

Benefits of the Settings API

What’s great about the Settings API is that once you fulfill all of its requirements, you’re good to go: your settings will save properly the “WordPress-y” way, they’ll be kept valid (if that’s necessary), and they’ll output onto a prettily formatted page that also takes care of some security issues and a few other things for you.

Relative to creating options pages yourself—and, if you’re foolish, risking things like failing to validate your inputs—the Settings API packages everything together neatly and completely. It’s the Right Way to do admin options in WordPress.

Some Foundational Concepts to Help with the Settings API

Using the Settings API buys you into some complexity, as well, as we’ll see. This section is here to provide you some early key concepts that will help you through the rest of this walkthrough.

Field, Section, Group, Page

To understand the Settings API, you need to understand four related terms: “field”, “section”, “group”, and “page.”

The Settings API documentation refers heavily to four related, possibly-confusing terms: “field”, “section”, “group”, and “page.” Until you understand each of these terms, the whole API seems like reciting magical incantations. (Trust me, until I sat down to write this some parts of it still felt that way to me.)

So let’s tackle them first. Here’s my best attempt at getting them straightened out:

  • Fields are the individual settings that you want to be controllable on your page. One or more fields are needed to make up a section, so there’s always at least one field in every section on a page.
  • Sections are collections of individual fields. One or more sections make up a page. If you’ve got a short page, it may make a lot of sense just to have one section, making the need to create sections just an inconvenience; but for more complicated pages, it can be a real boon to be able to break your settings up into “sections.” (I almost said “groups” there… I hope you now get why I didn’t.)
  • Groups are the most obscure of the four terms: they’re a collection of one or more settings. That is, when you create an individual setting, you add it to a group of settings. The documentation doesn’t make the relationship between groups and pages at all clear — I honestly haven’t figured out why. To keep you sane, here’s the rule: treat groups the same as pages, and call your settings group and your page slug by the same name.
  • Pages are what you’d kind of think they are: a collection of one or more sections, and the unit to which all of an option page’s settings belong. Again, unless you’re writing something quite complex — like, literally, the next WooCommerce (and even then I’m not 100% sure) — you should think of your “settings group” and “page slug” as the same thing.

The Overall Process of Registering a Settings Page

You’ll also benefit from knowing the basic steps of what the Settings API is used for: registering an admin options page, also called a settings page. At its simplest, the process includes:

  1. Registering one or more settings.
  2. Creating the navigation to your settings page.
  3. Creating your settings page itself.

That’s the basic flow we’ll be following below.

Two Additional Resources

We’ll be using the real code from my plugin Require Featured Image (RFI), so browse the source code there if you want to see how everything fits together.

Also, I’ve written out key takeaways at the bottom of this post that shouldn’t make sense now, but will by the end of the article. You may want to have those in mind as you read.

Creating Your Settings

Whether you’re using the Options or Settings API, you’re always saving to wp_options.

For most plugins and themes, there’s little concrete different between your “Settings” and your “Options” — they’re two words that refer to the same thing. (For that reason, I sometimes forget whether I harbor latent bad feelings for the Settings or Options API.) What’s important to keep in mind is that WordPress has an wp_options table, and that the “Options API” is the most direct way to access it. Whether you’re using the Options or Settings API, you’re always saving to wp_options; the Settings API just creates another level of separation between you and those values, for both good and bad.

Registering Your Settings

Where you can just get and set options with the get_option and update_option functions, you actually have to register a setting. That is, rather than just starting with writing an option with a given name to wp_options directly, you’re telling WordPress that this value in wp_options is a setting.

For my Require Featured Image plugin, that looks something like this:

add_action( 'admin_init', 'rfi_admin_init' );
function rfi_admin_init(){
    // Create Setting
    $settings_group = 'rfi';
    $setting_name = 'rfi_post_types';
    register_setting( $settings_group, $setting_name );
}

Basically, you wrap your call to the register_setting function so it’ll run on the admin_init action hook, and then you just call it. Two parameters are required: the name of the group to which the setting belongs, and the name of the setting itself.

The setting name is also the name of the option that gets saved into wp_option.

The value of the “setting name” argument also ends up being the name of the option that gets saved into wp_option. So to retrieve my rfi_post_types option value, I simply use get_option( 'rfi_post_types', 'default_value' );.

The “settings group” argument is one of those painful mysteries of the WordPress Settings API. Questions swirl into my head:

  • What is a settings group?
  • What does it mean that this setting is in this settings group?
  • What changes if I change the settings group?

It’s simplest to treat the settings group as the page slug for the page you’ll want the setting to appear in.

Again, the answer I’ve come to is this: it’s simplest to treat the settings group as the page slug for the page you’ll want the setting to appear in. There may be answers to those questions, but the best and simplest thing is to say, “The settings group is the slug for the page I want the setting to appear on,” and leave it at that.

Creating Settings Sections

So we’ve registered our setting. How do we chunk it up into settings sections? We’ll use the functions add_settings_section() and add_settings_field().

The full Settings-creation flow for Require Featured Image looks like:

add_action( 'admin_init', 'rfi_admin_init' );
function rfi_admin_init(){
 // Create Setting
 $section_group = 'rfi';
 $section_name = 'rfi_post_types';
 register_setting( $section_group, $section_name );

 // Create section of Page
 $settings_section = 'rfi_main';
 $page = $section_group;
 add_settings_section( 
 $settings_section,
 __( 'Post Types', 'require-featured-image' ),
 'rfi_main_section_text_output',
 $page
 );
 
 // Add fields to that section
 add_settings_field(
 $section_name,
 __('Post Types that require featured images ', 'require-featured-image' ),
 'rfi_post_types_input_renderer',
 $page,
 $settings_section
 );
}

add_settings_section()

We’ve already covered the first bit, where we’re registering our setting. Now we need to create our first (and only) section. As I said above, since we’ve only got one setting at all, creating a section is mostly an inconvenience in our case; but we do need to do it. The parameters to add_settings_section() are:

  1. The name of the section
  2. The title for the section. This will display on the settings page eventually.
  3. An (optional; if you don’t want it just put in '') name of a callback function to render before the fields you add to this section. In this case I just use it render a few extra paragraphs of explanatory text.
  4. The name of the page to which this section belongs.

Once you’ve added the section, then your job is to add your settings to the section so that they’ll finally come out on the page. That’s what add_settings_field does: it adds an existing setting to your section. Your setting should already exist, which is why our register_setting call precedes our add_settings_field one.

add_settings_field()

When you add a field, you’re specifying where and how the Settings API will present and make settable the option value. The parameters to add_settings_field() are:

  1. The name of the setting for which you’re making your field.
  2. The title for that settings field. If you’re not well acquainted with WordPress settings pages, this’ll appear to the left, in a table-like format where the label’s something like 30% of your page width. The actual field appears beside it to the right.
  3. The (name of the) function that’ll render your field. This appears to the right of the label. It’s worth knowing that if you want reusability — I wasn’t concerned in this use case, the sixth (optional) parameter lets you pass values to this function.
  4. The slug name of the page into which this setting should appear.
  5. The section in that page to which this field should be added.
  6. As I mentioned on #3, this is an associative array that is passed, as is, to your function. It’s useful if you want or need to do pass values to a common rendering function rather than create a separate function for each text field, for example.

Whew! Stay with me.

An Example Field-Rendering Function

As we just saw, add_settings_field()‘s third parameter requires that you write a function to actually render your setting’s field.

The specifics of how you want to render your setting’s individual options is really up to you; that’s why WordPress is making you write a function (rather than, say, just a field type). But I’ll provide my example, as it highlights the most important things you want to know about it:

function rfi_post_types_input_renderer() {
    $option = rfi_return_post_types_option();
    $post_types = rfi_return_post_types_that_support_featured_images();

    foreach ( $post_types as $type => $obj ) {
        if ( in_array( $type, $option ) ) {
            echo '<input type="checkbox" name="rfi_post_types[]" value="'.$type.'" checked="checked">'.$obj->label.'<br>';
        } else {
            echo '<input type="checkbox" name="rfi_post_types[]" value="'.$type.'">'.$obj->label.'<br>';
        }
    }
}

As I said, this is mostly specific to the details of how my plugin works. But the important part is that you need to know and track the value of the setting yourself, for which I’m using a little wrapper function around a basic get_option() call. (Remember: settings get saved as options; they are options, and you can talk to them with get_option().)

Similarly, I’ve got a small wrapper around a function that makes it easy for me to get all the post types that support featured images. Then I’m just creating checked or not-checked checkboxes based on whether or not the value is set.

One thing to note is that the name on your input should match your option. So for this case, where I want a serialized array of post types to be what gets saved, I can use the rfi_post_types[] as the name on all my checkboxes, and WordPress and PHP will take care of the shoving into an array for me. For simple inputs, though, you could have something as simple as:

function my_option_input_renderer() {
    echo '<input type="text" id="my_option" name="my_option" value="'.get_option('my_option', '').'">';
}

Keeping Your Settings Valid — and some Gotchas

Your call to register_setting() has a third, optional, parameter, called $sanitize_callback. This is the name of the function that you want to use to ensure that this setting keeps a valid value. For example, if the setting allows the user to enter text, you may want to sanitize that text to make sure it doesn’t contain malicious code.

What’s both nice and frustrating here is that any change to this option will pass through your callback function before being saved—no matter who’s changing the option, or from where. That’s great for data integrity, bad for someone not expecting it and wondering why their value’s getting mangled or disappeared.

For Require Featured Image, because it’s currently just a set of checkboxes — and because I like to live a little dangerously — there is no actual validation of the settings data. I’m just relying on the fact that it’s unlikely the data will be changed or made invalid because the primary entry method is checkboxes. Unlike, for example, text fields (where you might be expecting a phone number, but a bunch of malicious JavaScript code is just as easy to type), you’re unlikely get anything too awful entered into a checkbox.

But there’s unquestionably some value in understanding how this whole thing fits together, so let’s pretend I wanted to keep and store an email address:

add_action( 'admin_init', 'rfi_admin_init' );
function rfi_admin_init(){
    // Create Settings
    $option_group = 'rfi';
    $option_name = 'rfi_email';
    register_setting( $option_group, $option_name, 'rfi_email_validate');
}
function rfi_email_validate( $input ) {
    $validated = sanitize_email( $input );
    return $validated;
}

What we’re doing here is using WordPress’s built-in sanitize_email() function to clean up an email address we might theoretically be collecting. Like all similar sanitization callback functions, sanitize_email() simply takes the value that WordPress will save into the setting, “does things to validate and/or sanitize it” (wonderfully, we don’t need to know the specifics), and returns the sanitized result to be saved into wp_options.

Creating and Displaying Errors (Or Successes)

In the process of validating a value, you may find it useful to alert users that their input is not valid and so wasn’t saved, was modified before it was saved, and so on. Or you may want to tell a user that everything has gone off without a hitch!

In either case, you’ll want to use the functions in the Settings API meant to convey these kinds of messages: add_setting_error(). You’ll use add_setting_error() to create a message to display wherever the Settings API outputs messages. Let’s just gussy up our email validation example to use it:

function rfi_email_validate( $input ) {
    $validated = sanitize_email( $input );
    if ($validated !== $input) {
        $type = 'error';
        $message = __( 'Email was invalid', 'require-featured-image' );
        add_settings_error(
            'rfi_email',
            esc_attr( 'settings_updated' ),
            $message,
            $type
        );
    }
    return $validated;
}

Here, we’re saying that if the sanitize_email() function has changed the value, then the email was invalid. (sanitize_email() discards obviously invalid email addresses.) If this has happened, we’re creating an error message.

The first parameter for the add_settings_error is the name of the setting, the second is an identifier that will be used in the CSS. If you ask me, neither makes a lot of sense and both are examples of the odd and confusing parameter sets of the Settings API.

The third and fourth parameters, however, are somewhat reasonable: they’re the message that’ll be displayed to the user (either in the place where your template calls the function setting_errors(), or at the top by default) and whether or not that text should bear the identification as being an error or a simple, happy updated state. (If you ask me, this function should be split into two separate functions, add_settings_message() and add_settings_error(), thus eliminating the need a fourth parameter entirely; but C’est la vie!)

As I mentioned, to display or get settings error and success messages, there are the functions settings_errors() and get_settings_errors(). Personally, however, I find that the Settings API’s defaults for displaying these messages work automatically enough that I’ve never had practical cause to call either function.

Creating the Navigation to Your Page

As we covered in my last article, to register your settings page for display somewhere in the WordPress admin menu, you’ll use one of a number of very similar functions. They are all named in the format add_*SOMETHING*_page. For Require Featured Image, I wanted the page to be under the Settings tab in the admin sidebar. So I used the add_option_page() function, which is the easiest way you can get a page there; if you wanted it elsewhere, you’d use some other function that’d behave slightly differently. (Again, the previous article covers this in detail.)

This snippet creates the page in Settings:

add_action( 'admin_menu', 'rfi_admin_add_page' );
function rfi_admin_add_page() {
    add_options_page(
        'Require Featured Image Page',
        'Req Featured Image',
        'manage_options',
        'rfi',
        'rfi_options_page'
    );
}

We want our page to actually be registered with WordPress at the admin_menu hook — that’s universal for admin options pages, so that’s why we’re using that action. Then our function itself just calls the add_options_page function. The parameters are, in order:

  1. the title for the page,
  2. the menu title for the page as we want it to appear in the sidebar,
  3. the user capability that should be required to access the page,
  4. the menu slug — that is, the URL bit that WordPress’ll use,
  5. and finally the function that’ll actually display our page.

Whew! If, like me, that’s not something you trust yourself to remember, then do what I’ll do in the other functions here: create a clearly named variable for each of your parameters, and pass your parameters in as those variables. It’s a minuscule hit to your plugin’s memory usage and processing overhead, and it can give you back so much sanity.

Creating the Settings Page

The biggest difference between using the Settings API and the Options API is how the function that renders the settings page works.

The biggest difference between using the Settings API and the Options API is how the function that renders the settings page works. In the Options API, the function that renders the page is essentially pure HTML (and a bit of PHP to show variable values). With the Settings API, you have PHP function calls throughout your page.

To get the benefits of using the Settings API, you’ll want to use Settings API functions to display the discrete parts of your page. Specifically, you’ll need the settings_fields() and do_settings_sections() functions. For Require Featured Image, that looks like:

function rfi_options_page() {
?>
<div class="wrap">
    <h2><?php _e( 'Require Featured Image', 'require-featured-image' ) ?></h2>
    <form action="options.php" method="post">
        <?php settings_fields( 'rfi' ); ?>
        <?php do_settings_sections( 'rfi' ); ?>
         
        <input name="Submit" type="submit" value="<?php esc_attr_e( 'Save Changes', 'require-featured-image' ); ?>" class="button button-primary" />
    </form>
</div>
<?php
}

Required Elements of a Settings Page

The page we created has two crucial functions:

settings_fields( 'rfi' );
do_settings_sections( 'rfi' );

settings_fields()

settings_fields() takes the name of your settings group as its argument, and outputs three necessary, but invisible, form fields for you:

  1. A nonce, which is a security thing you can mostly forget about.
  2. An action, which is a form value used within the Settings API and again you can mostly forget about, and
  3. The name of the group (or page — again, for your sanity name them the same), that’ll allow the Settings API to make sure that someone who submits to your page returns to your page again.

The need to manually call settings_fields() is one of the Settings API’s annoying quirks. The other frustrating part is, if you remember my earlier definition of what “fields” means throughout most of the Settings API, this function’s use of the term “fields” doesn’t really match. For sanity: just remember to include settings_fields(), and don’t worry too much about the name.

do_settings_sections()

This function is what actually spits your sections of settings out onto the page.

do_settings_sections() takes the name of your page as its argument, and displays all the sections that that page has, in order. In other words, it’s what actually spits your sections of settings out onto the page. If you remember that pages are made out of sections, and sections are made out of sets of fields, it’s a little easier to wrap your head around the naming here.

As a note, if you want to keep control of the rendering order of your multiple sections, you can use a couple calls to do_settings_fields( $section_name ) instead of the single call to do_settings_sections( $page_name ) that we’re doing here.

Further Notes on the Settings Page Form

Note that, relative to the Options API solution I described earlier, the Settings API carries an extra layer of indirection with its call to do_settings_sections(): if you don’t know how that function works, our form itself doesn’t look like it contains any fields.

Note also that we’re also still creating many parts of the form HTML ourselves. While the actual rendering of the form fields is displaced into the Settings API, you’ll still create the form opening tags, and you’ll want to make sure that your method="POST" and that you keep an action="options.php" on there too. We’re also creating the save button ourselves, and we’ve given it the CSS classes button and button-primary so it’ll get that familiar (usually blue) styling of the most important buttons on any WordPress admin page.

Whether this indirection of the rendering of the form fields themselves into the Settings API is a good or bad is a matter of some debate, but it is the way the Settings API works.

What We Learned

If you’ve slept this long: the Settings API is a comprehensive, but somewhat confusing, solution built into WordPress that’s designed to make creating your own settings page relatively easy. I hope that you now understand the basics of the Settings API and what it’ll do for you.

While it’s hardly flawless, and it has a few especially confusing parts, the Settings API is a basically good solution that requires no external libraries, plugins, or complex and possibly unsafe code written by you.

In my mind, the biggest and most important takeaways that can unlock the Settings API for you are:

  • WordPress Settings pages, using the Settings API, are made up of sections which are themselves made up of one or many different fields.
  • Every individual setting gets saved as an option of the same name in wp_options. In other words, settings are options, and you can access them with get_option() and so forth.
  • You can, and should, treat your “settings group” and “page slug” as the same. Rather than trying to keep these two similar but subtly different ideas apart, it’s been easier for me to keep my sanity by using the same URL-safe string for both.
  • While it may not be your favorite, knowing the basics of how the Settings API works is useful, even if you prefer to roll your own from-scratch settings page framework or to use something like the Options Framework instead.

I hope you found this (really really) long article useful, and happy hacking!

Image credit: fdecomite


9 Responses

Comments

  • Mick Levin says:

    Good article!

    Still, WP4’s Customizer is superior. I use just 2 steps to register my configuration data, and the rest the WP to do, logically.

    Compare that to the Options API use in the article!

    Use can see my code at Snippler:

    http://snipplr.com/view/92798/2-step-use-of-wordpress-4-customizer/

    Sure, the Customizer makes the option-ing a bit different in the idea:

    You do need to account for the fact that the preview of the front page would be there, and it will try to apply the options while you are entering them, even before saving. So, where ever you use the custom options in your theme – be careful to check if they are valid and do extra validation, do not let your preview crash just because the options are not making any sense. Obviously that’s something we are already doing, right?

  • Chris says:

    Just one question, don’t you have to verify even checkbox inputs? How is a checkbox field any different from a text field in allowing malicious code to be passed? Since your only up against pros when guarding from hackers, is it really reasonable to think that they won’t simply change field type from checkbox to text in source view ? And then have the value straight in db simply because you expected true false,.. not a big issue for a settings page already guarded by wp-admin but an issue all the same right?

Pingbacks