The WordPress Settings API

This weekend at WordCamp Sofia 2012, I gave a session about the Settings API, which is a way to create simple, flexible and secure options pages for your WordPress plugins and themes. The session covered the basic usage of the API along with a few more advanced topics. Here are my slides and notes/transcript.

[slideshare id=14847434&doc=settings-api-3-121023051935-phpapp01]

The notes might be a slightly tricky to follow without looking at the slides, so if you’re stuck or have any questions about the Settings API, don’t hesitate to get in touch.

Settings API Basics

There are three main components in the Settings API: The Setting, the Field and the Section. The Setting is the actual option that is stored inside the WordPress database. The Field is a form control rendered in our options page, such as a text input, or a dropdown select. The Section is just a way to group fields together.

The first step is to create a new item in the admin menu, that will lead to our options page. We use the add_options_page() function during the admin_menu action, like this:

add_action( 'admin_menu', 'my_admin_menu' );
function my_admin_menu() {
    add_options_page( 'My Plugin', 'My Plugin', 'manage_options', 'my-plugin', 'my_options_page' );
}

The first argument is the page title, which is displayed in the browser title bar. The second one’s the menu title. The third argument is the capability that’s required to access the options page. The fourth one is called the menu slug. We’ll have to keep this in mind, since we’re going to use it very soon. The last argument is the callback function, which will be used to render our settings form. We’ll define that at the very end since it brings all the things together.

This gives us a new entry under the Settings menu, which doesn’t do anything yet, so let’s jump to our Settings, Sections and Fields.

There are three functions in the Settings API that deal with those: register_setting(), add_settings_section() and add_settings_field(). We typically use them during the admin_init action, like this:

add_action( 'admin_init', 'my_admin_init' );
function my_admin_init() {
    register_setting( 'my-settings-group', 'my-setting' );
    add_settings_section( 'section-one', 'Section One', 'section_one_callback', 'my-plugin' );
    add_settings_field( 'field-one', 'Field One', 'field_one_callback', 'my-plugin', 'section-one' );
}

The register_setting() function lets WordPress know that we’re going to use an option with the Settings API. The first argument is the option group. We will use this group when rendering our options page. The second argument is the option name. It’s the one we use with functions like get_option() and update_option().

Next, we create a new section using the add_settings_section() function. The first argument is the section ID, which will be used to add fields to this section. The second argument is the section title, the third one’s the section callback function. This gets fired right after rendering the section title, and it’s usually used to render some help text. The last argument is the page ID, that’s the menu slug we used in our add_options_page() function.

Finally, we add a field to our section with the add_settings_field() function. First argument is the field ID. The second one’s the field title, the one that’s displayed on the left side of the control. The third argument is the field callback function. This is used to render a form control, such as a text input, or a checkbox. The fourth argument is, again, the menu slug that we used in add_options_page(). The last argument is the section ID we want our field to appear in.

We have two callback functions here, so let’s define them:

function section_one_callback() {
    echo 'Some help text goes here.';
}

The section_one_callback() function, which is called right after the section heading is displayed. We’ll make it echo some text. And the field_one_callback() function, which is a little trickier:

function field_one_callback() {
    $setting = esc_attr( get_option( 'my-setting' ) );
    echo "<input type='text' name='my-setting' value='$setting' />";
}

Here we use get_option() to fetch the value of the setting that we registered earlier. We then render a simple text input, with the setting as the value. It’s important here, that the control name matches the option name, so that WordPress knows, which option to update, when we change the value.

Bringing it all Together

Let’s rewind to our add_options_page() call. We used “my_options_page” as a callback function, that will render our page contents. We never defined that function, so let’s do it now. Here’s how it looks:

function my_options_page() {
    ?>
    <div class="wrap">
        <h2>My Plugin Options</h2>
        <form action="options.php" method="POST">
            <?php settings_fields( 'my-settings-group' ); ?>
            <?php do_settings_sections( 'my-plugin' ); ?>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

A simple div, with a simple heading and a simple form. Make sure that the form points to options.php with the POST method. Now inside the form we’ve got three functions.

The settings_fields() function renders a few hidden fields, that tell WordPress which settings we are about to update on this page. The only argument is the option group, which we used when we registered our setting with register_setting.

The do_settings_sections() function is where all the magic happens. It outputs all the sections and fields that have been added to the options page. The only argument here is the page ID. That’s the menu we used during our add_options_page() call, as well as add_settings_section() and add_settings_field().

The last function is submit_button(). It just renders a submit button, and we’re done!

Recap

  • add_options_page() — creates a new item in the WordPress admin menu
  • register_setting() — tell WordPress that we’re going to use an option with the Settings API
  • add_settings_section() and add_settings_field() — add sections and fields to our options page
  • settings_fields() — hidden fields for WordPress to know which options we intend to update on our page
  • do_settings_sections() — render all the sections and fields registered to our page
  • submit_button() — render a blue submit button

Theme Options vs the Customizer

A quick note on Theme Options. Many themes these days, will come with configurable settings, such as a color scheme or font selection. Since version 3.4, WordPress comes with this thing called the Customizer, which is the most appropriate place for theme configuration. It’s got an easy and flexible API, as well as a set of controls that we can use right out of the box.

However, if you decide to still create your own options panel for your theme, please use the Settings API.

Advanced Topics

In this section we cover a few slightly more advanced topics and techniques to deal with the Settings API, including using arrays with the API, sanitization and validation, and reusing controls with the Settings API.

Using Arrays with the Settings API

If we have quite a few options for our plugin or theme, we can register a setting for every single one and have them as separate rows in the WordPress database. We’ll have to make sure we’re registering all our settings to the same group.

register_setting( 'my-settings-group', 'color' );
register_setting( 'my-settings-group', 'size' );
register_setting( 'my-settings-group', 'quantity' );

However, it’s much easier and more efficient to use a single option to store all our values, so we’ll have a single call to register_setting() with the name of our single option:

register_setting( 'my-settings-group', 'my-settings' );

We will then use the setting as an array in our field callbacks, and we’re just using different array keys for the different options. Also note how the field name attribute has changed to an array:

function field_one_callback() {
    $settings = (array) get_option( 'my-settings' );
    $color = esc_attr( $settings['color'] );
    echo "<input type='text' name='my-settings[color]' value='$color' />";
}

We will use a similar approach to retrieve these options throughout our theme or plugin, and it’s also useful to provide an array of defaults, when working with arrays and the Settings API.

Sanitization

The register_setting() function takes a third optional argument, which is a callback function to sanitize your setting.

register_setting( 'my-settings-group', 'my-settings', 'my_settings_sanitize' );

This function will be called every time the setting is updated, so it’s our chance to make sure the input values are right. For example, one of our options is “quantity,” so let’s make sure that the quantity is always an absolute integer value.

function my_settings_sanitize( $input ) {
    $input['quantity'] = absint( $input['quantity'] );
    return $input;
}

This way, no matter what was input in our quantity field, it’ll will get passed through the absint() function, converting the value to an absolute integer. If you’re using an array for your settings, you can sanitize all your options in a single function.

But sometimes sanitizing is not good enough and we’ll want to actually validate our values instead, like an e-mail address. This brings us to validation.

Validation

Here’s the trick with validation. We’ll use the same technique as sanitization, but we will return the current option value, if the new input value was not good enough.

function my_settings_validate( $input ) {
    $output = get_option( 'my-settings' );

    if ( is_email( $input['email'] ) )
        $output['email'] = $input['email'];
    else
        add_settings_error( 'my-settings', 'invalid-email', 'You have entered an invalid e-mail address.' );

    return $output;
}

In the example of an e-mail address, we grab the current option value with get_option(), then use the is_email() function to check whether the input e-mail was a valid one.

If it was, we set the output e-mail to the input e-mail, which we finally return, but if the e-mail address was not valid, we use a function called add_settings_error() to render a big red error message, and not update our setting.

Reusing Controls with the Settings API

Let’s say we have an options page with twenty or thirty text fields. We can create a new callback function for each individual field, which is a pain, very difficult to manage and code duplication we don’t really need.

The add_settings_field() function has an optional $args parameter, which allows us to pass anything we want to our callback function.

add_settings_field( 'email', 'E-mail', 'my_text_input', 'my-plugin', 'section-one', array(
    'name' => 'my-settings[email]',
    'value' => $settings['email'],
) );

In this snippet we’re passing the name and value of the setting to our field callback function, which will now look like this:

function my_text_input( $args ) {
    $name = esc_attr( $args['name'] );
    $value = esc_attr( $args['value'] );
    echo "<input type='text' name='$name' value='$value' />";
}

So we will have the $args parameter with all the passed in values, which we’re using in our text input.

This $args parameter may include more than just the name and value of the field. It can contain an optional description, or a list of choices for a dropdown select, or a group of checkboxes. This allows us to create a generic callback function for anything we like, including color pickers, image uploaders and more.

We can modify and adapt our callback functions to all our needs, and eventually we’ll end up with a little “options framework” which we can use and reuse in all of our projects.

References: add_options_page, register_setting, add_settings_section, add_settings_field, settings_fields, do_settings_sections, submit_button, data validation, roles and capabilities.

About the author

Konstantin Kovshenin

WordPress Core Contributor, ex-Automattician, public speaker and consultant, enjoying life in Moscow. I blog about tech, WordPress and DevOps.

5 comments

  • Thanks for sharing this. I had implemented this a while ago, but needed a refresher and this is maybe the best tutorial on the subject I’ve found. However, I ran into one problem, and I think it might be wrong in your example (or if it’s not I’m confused). Following your example, I got a “Error: options page not found” when saving my fields. However, I found that I needed the first arguments of add_settings_section() and register_setting() to match to solve the problem. I think this is what the note on the register_setting() codex page is referencing when it explains the error, although the codex isn’t super clear on that either.

    Either way, thanks!

    • Hey Mark, thanks for your feedback! The “options page not found” error is typical, for when WordPress is confused which settings should be updated. The two functions that deal with that are register_setting and settings_fields. They both need an $option_group argument which has to match, and which tells WordPress which options we’re updating.

      Here’s a quick gist that brings all the above together. If you’re still having issues, reach me on Twitter or Skype, and we’ll sort them out!

  • Hi Konstantin,

    Gotta hand it to you; the Settings API is probably one of the most obtuse APIs in WordPress, it’s one of the few that I really struggle with and you’ve done an excellent job of mapping it out so at least it’s understandable how to use it. Kudos!

    -Mike