WordPress Settings API Tutorial

Edit: This post has moved to here: http://ottopress.com/2009/wordpress-settings-api-tutorial/. Take your comments there.

When writing the Simple Facebook Connect plugin, I investigated how the Settings API worked. It’s relatively new to WordPress (introduced in version 2.7), and many things I read said that it was much easier to use.

It is much easier to use in that it makes things nice and secure almost automatically for you. No confusion about nonces or anything along those lines. However, it’s slightly more difficult to use in that there’s very little good documentation for it. Especially for the most common case: Making your own settings page.

So, here is my little documentation attempt.

Making your own settings page

First, add yourself an options page. Code to do that:

<?php // add the admin options page
add_action('admin_menu', 'plugin_admin_add_page');
function plugin_admin_add_page() {
add_options_page('Custom Plugin Page', 'Custom Plugin Menu', 'manage_options', 'plugin', 'plugin_options_page');
}
?>

What this does is quite simple, really:

a. It adds a link under the settings menu called “Custom Plugin Menu”.
b. When you click it, you go to a page with a title of “Custom Plugin Page”.
c. You must have the “manage_options” capability to get there though (admins only).
d. The link this will be will in fact be /wp-admin/options-general.php?page=plugin (so “plugin” needs to be something only you will use).
e. And the content of the page itself will be generated by the “plugin_options_page” function.

Oh wait, we need that function! Let’s go ahead and create that, shall we?

<?php // display the admin options page
function plugin_options_page() {
?>
<div>
<h2>My custom plugin</h2>
Options relating to the Custom Plugin.
<form action="options.php" method="post">
<?php settings_fields('plugin_options'); ?>
<?php do_settings_sections('plugin'); ?>

<input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" />
</form></div>


<?php
}?>

Hang on a minute, where’s all the options? Well, here’s where the Settings API kicks in a bit. Up to now, this has been more or less the same as previous tutorials. Adding the options pages is really quite easy. But now, we’re going to use two new functions.

First, we call settings_fields(‘plugin_options’). This outputs the hidden bits that we need to make our options page both do what we want and to make it secure with a nonce. The string “plugin-options” can be anything, as long as it’s unique. There is another call we’re going to have to make with this same string later.

Next, we call do_settings_sections(‘plugin’). This is going to output all of our input fields. Text input boxes, radio fields, anything we like. Obviously though, we have to tell it what those fields are and look like somewhere. We do both of these things in the next section.

Defining the settings

<?php // add the admin settings and such
add_action('admin_init', 'plugin_admin_init');
function plugin_admin_init(){
register_setting( 'plugin_options', 'plugin_options', 'plugin_options_validate' );
add_settings_section('plugin_main', 'Main Settings', 'plugin_section_text', 'plugin');
add_settings_field('plugin_text_string', 'Plugin Text Input', 'plugin_setting_string', 'plugin', 'plugin_main');
}?>

Here we’ve done three things. Let’s break that down, shall we?

<?php register_setting( 'plugin_options', 'plugin_options', 'plugin_options_validate' );?>

First, we register the settings. In my case, I’m going to store all my settings in one options field, as an array. This is usually the recommended way. The first argument is a group, which needs to be the same as what you used in the settings_fields function call. The second argument is the name of the options. If we were doing more than one, we’d have to call this over and over for each separate setting. The final arguement is a function name that will validate your options. Basically perform checking on them, to make sure they make sense.

Ignoring the validation function for a moment, lets move on to the setting section. This one is actually quite simple.

<?php add_settings_section('plugin_main', 'Main Settings', 'plugin_section_text', 'plugin'); ?>

This creates a “section” of settings.
The first argument is simply a unique id for the section.
The second argument is the title or name of the section (to be output on the page).
The third is a function callback to display the guts of the section itself.
The fourth is a page name. This needs to match the text we gave to the do_settings_sections function call.

That function callback in the third argument should look a bit like this:

<?php function plugin_section_text() {
echo '<p>Main description of this section here.</p>';
} ?>

Simple, really. You can put any HTML you like here.

Now that we’ve talked about the section itself, we need to talk about the fields in that section.

<?php add_settings_field('plugin_text_string', 'Plugin Text Input', 'plugin_setting_string', 'plugin', 'plugin_main'); ?>

The first argument is simply a unique id for the field.
The second is a title for the field.
The third is a function callback, to display the input box.
The fourth is the page name that this is attached to (same as the do_settings_sections function call).
The fifth is the id of the settings section that this goes into (same as the first argument to add_settings_section).

The only difficult one here is, again, the callback. Let’s look at that, shall we?

<?php function plugin_setting_string() {
$options = get_option('plugin_options');
echo "<input id='plugin_text_string' name='plugin_options[text_string]' size='40' type='text' value='{$options['text_string']}' />";
} ?>

Simple. It just gets the options then outputs the input HTML for it. Note the “name” is set to plugin_options[text_string]. This is not coincidence, the name *must* start with plugin_options in our case. Why? Because that is the second argument we passed to register_settings.

The settings pages use a whitelist system. Only valid options get read. Anything else gets tossed out, for security. Here, we’re using a php trick. PHP interprets an incoming GET or POST data of name[thing] as being an array called name with ‘thing’ as one of the elements in it. So, all our options are going to take the form of plugin_options[some_thing], so that we get that single array back, and the array name itself is whitelisted.

Since this is designed with security in mind, we have one last callback to deal with: The validation callback that we skipped earlier:

<?php // validate our options
function plugin_options_validate($input) {
$newinput['text_string'] = trim($input['text_string']);
if(!preg_match('/^[a-z0-9]{32}$/i', $newinput['text_string'])) {
$newinput['text_string'] = '';
}
return $newinput;
}
?>

Here I’m taking a liberty with the code. I’m going to say that our text_string has to be exactly 32 alphanumerics long. You can actually validate any way you want in here. The point of this function is simply to let you check all the incoming options and make sure they are valid in some way. Invalid options need to be fixed or blanked out. Finally, you return the whole input array again, which will get automatically saved to the database.

Take special note of the fact that I don’t return the original array. One of the drawbacks of this sort of approach is that somebody could, in theory, send bad options back to the plugin. These would then be in the $input array. So by validating my options and *only* my options, then any extra data they send back which might make it here gets blocked. So the validation function not only makes sure that my options have valid values, but that no other options get through. In short, $input is untrusted data, but the returned $newinput should be trusted data.

And we’re done. Wait, what?

Yes, the whole point of this exercise is that the options are automatically saved for you. And everything else. You have an options page, you have fields on it, you are validating them… and that’s it. The actual *display* of the page is even done for you. Remember the input we made? It’ll get put into a table with the title on the left side before it, waiting for input.

Another nice thing is that this is easily expandable. For each option to add we:
1. Do a new add_settings_field call.
2. Make the function to display that particular input.
3. Add code to validate it when it comes back to us from the user.

To add a new section, you:
1. Do a new add_settings_section call.
2. Make the function to display any descriptive text about it.
3. Add settings fields to it as above.

One last thing

Sometimes we don’t need a whole page. Sometimes we only have one setting, and it would work well on some existing page. Maybe on the general page, or the discussion page. Well, we can add settings to those too!

If you look through the core code, you’ll find references like do_settings_sections(‘general’) or do_settings_sections(‘writing’), and so on. These you can add on to like any others, getting your settings on the main WordPress settings pages instead of having to make your own.

Just do this:
1. Make an add_settings_section call. The last argument should be “general”, or wherever you want to add your new section.
2. Add fields to your new section, using add_settings_field.
3. You still need to make your own settings whitelisted. To do this, you’ll need to make a call to register_setting. The first argument should be the same as the page name, like ‘general’, or wherever you’re putting your settings. This will let that page recognize and allow your settings to come through.

All the callbacks will basically be the same for this method. You’re just skipping a step of making your own page. Easy.

And there you go. More reading: http://codex.wordpress.org/Settings_API

0 thoughts on “WordPress Settings API Tutorial

  1. I was embarassed to discover, after reading the Codex doc and agreeing with your assessment, that I was the person who wrote most (or all) of it. I remember just wanting to get something up there because there was no info at all, so I wrote about the things I had cobbled together (which at the time didn’t include adding a whole page).

    One thing about this great tutorial that could be greater is the submit buttons. The code you posted makes some weird plain looking ones, if instead of :

    <input name="Submit" type="submit" value="" />

    You use:
    <input type="submit" name="submit" value="" />

    Then they look like other pretty buttons in the admin.

    Either way thanks for documenting this. Some day maybe I’ll convert all your info into a Codex doc (or just add phpdoc to the actual functions :S )

    1. Yeah, I tend to use this code for my plugins, as well as sticking a class=”button-primary” on the submit input field as well.

      However, I left that stuff out specifically to make the code easy to understand. I was mainly focused on how the code works, not how to do admin like styling and such.

  2. Really nice tutorial, thanks!

    Would you know of a way to detect which submit button is clicked when multiple ones are provided (e.g. to provide a reset option)? It seems like the form gets redirected following the submit, so I cannot capture the name of the reset button and hook options reset functionality into this.

    1. That is where I’m stuck too – there seems to be no way of determining which button (if you add a reset button) has been pressed

      1. Normally, I don’t have multiple “submit” buttons. If you want to have them and detect them, then you have to make them return individual bits that will identify their characteristics.

        Note that my submit button in plugin_options_page() has no “name” on it. This is because I don’t care about it. Buttons without names submit no extra parameters, they just submit. But, give it a name, and it returns the value on it.

        There’s two ways to do this.
        1. Unique values
        2. Unique names

        The first way would look like this:
        input type=”submit” name=”plugin_options[submit]” value=”Submit”
        input type=”submit” name=”plugin_options[submit]” value=”Reset”

        In this case, my validation function would get back an $input['submit'] which would be set to either “Submit” or “Reset”, depending on which button I clicked. You could take action appropriately. Note that you would probably not want to set $newinput['submit'] to return from this function, unless you wanted to store that value in the database for some strange reason…

        Problem with this is that the “value” is what is actually displayed on the page. If you translate that into another language, your code now has to look for that value. It’s problematic.

        Second way:
        input type=”submit” name=”plugin_options[submit]” value=”Submit”
        input type=”submit” name=”plugin_options[reset]” value=”Reset”

        In this case, I will get either $input['submit'] or $input['reset'] back. Because only one will come back depending on what button is pressed, I can ignore the value and take action based on which one is set. Similarly, I’d want to not put this resulting value in $newinput, same as before.

  3. I’m stumped. After failing to implement this into my own plugin, I took all the bits above and combined them into a fake plugin to see which part makes the difference. Turns out that plugin doesn’t work either. By “doesn’t work” I mean it does generate the custom settings page (as did my own plugin), and the input elements are functional, but the setting (the string) never gets saved. Tried this in WP 2.9.1 and 3.0-alpha, no difference. Is there something missing from the code above, or am I missing something? I’m tempted to add some update_option()’s, but I thought the whole point was, as you point out, that “the options are automatically saved for you”.

    1. I don’t know what to tell you. I use this same technique in my Simple Facebook Connect plugin, and it works. I wrote this tutorial using things I learned from creating that plugin.

      Check your register_setting call. That call basically turns on the setting, so that it can get whitelisted.

      Also check your validation function. That function has to return the modified input, if you don’t return anything, then the variables get lost and nothing gets saved.

      Take a look at my SFC plugin. The base sfc.php file implements this, and you might see what you’re missing. Note that it doesn’t use any “update_option” calls anywhere.

      1. Thanks for taking the time to respond, Otto. I’ll need to take another look at the code armed with the tips you gave.

        Funnily, I just installed SFC today, and realized only afterwards the author links led me back to this blog. :)

  4. Great tutorial, However is there a way to add default settings? If you use one database entry for all settings im assuming this would be done in an array?