Originally posted here: http://ottodestruct.com/blog/2009/wordpress-settings-api-tutorial/

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() {
<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'); ?>" />


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.

Update: What if you have multiple options screens? Or just want to only be able to edit a few of your options? One downside of the validation method I detail above is that your single plugin_options field gets completely replaced by $newinput. If you only want to have it change a few of your options, then there’s an easy technique to do that:

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

Basically I load the existing options, then update only the ones I want to change from the $input values. Then I return the whole thing. The options I don’t change thus remain the same.

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



  1. […] am using Otto’s tutorial. to make a option page. I want to keep a default value for the input field. how can I do […]

  2. Add the default value as second parameter in get_option.

  3. I am really confused by the above do we need to create just one php file or several etc. If so what do we name them? A download link for the files would have been appreciated.

    • There are no files to be downloaded. Whether you put it in one file or a dozen makes no difference. The tutorial above is there to help you understand the code and how it works, not the file structure. Put your files wherever you like.

      • plugin code removed

        I created the above file but it just produced fatal errors when loading the plugin.
        All I want is to store values for Amazon Affiliate ID and two keys SECRET and ACCESS but then I also want to pass these to an Amazonapi.php program but I will also need one more variable the title I am searching for.

        • Well, that’s a real bummer, but posting a bunch of your plugin code in my comments section isn’t very nice. Try the support forums, or the WordPress Stackexchange if you have problems that need solutions. This is not a support forum.

      • Beginners to plugin development do need a bit of help.

        The sample code that you provided mentions ‘options.php’ as the form action, so the code goes there. The main plugin file should include it using

        include( dirname(__FILE__) . '/options.php' );
        • No, it absolutely should not include that file. Plugins don’t need to load core files like that, those files are loaded for you.

          The code works as given. It doesn’t need any additional explanation.

  4. I know this is a bit dated, but it’s still linked to on the documentation page. Could I suggest rephrasing “simple a unique id” to clarify that this is an HTML id attribute you’re referring to? This helps readers understand the context against which it must be unique. Thank you for this write up!

  5. any way to make this multi site compatible (WPMU)?
    what i’m looking to do is find a way to add some additional fields when creating new sites in the network admin

  6. I used the code above as shown and tried to change the option name ‘text_string’ to something else – ’embedded_url’. I have WP_DEBUG enabled. When I loaded my plugin’s settings page, right above my text input field is this error: “Notice: Undefined index: embedded_url in ….” and then it points to this line:

     echo &amp;quot;&amp;lt;input id='plugin_text_string' name='plugin_options[embedded_url]' size='40' type='text' value='{$options['embedded_url']}' /&amp;gt;&amp;quot;; 

    Why is this happening?

    • Because your $options[’embedded_url’] value isn’t set.

      The above code is a tutorial, not copy-pasta. It is intended to show you how things work, not to be copied and used as-is. It will not necessarily be debug-safe. You don’t need to copy it, you need to read it and understand the purpose of it. Then you can write your own code.

    • I get the same error. Not at a first trial, but later strange enough.
      It is a nice approach and good described, but with problems.
      The validation function (both) do not accept caps, although /i is given. Perhaps a PHP7 issue. I added A-Z in caps, and that works.

      Just adding as described by:
      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.
      does result in the index error.

      Please inform me/us about coding multiple variabels or options in the array. Until now only the first var seems to work. Whre should I set an other option? You say, repeat for each field (register_settings). But that leads to multiple entries in the database. How?

      My target is to create code that will not soon be depreciated. This approach seems very good in this perspective, but I think I’m missing things. Please help!

      • I found out that the example validation does the opposite of what is expected.
        It should be something like this to do what was intended:
        if(preg_match(‘/[^a-z0-9\s]+$/i’, $options[‘text_string’])) {
        $options[‘text_string’] = ”;
        return $options;

        I found out that all array options should be in this routine. Otherwise nothing is saved.

        To avoid ‘index not found’ one has to create a default array.
        Instead of using
        $options = get_option(‘plugin_options’);
        you should use:
        $options = wp_parse_args(get_option(‘plugin_options’), $defaults);
        The $defaults array should have an unique name and made available in the functin. Perhaps by making it global.

  7. Yeah, this didn’t work lol.

    How about posting the actual code in full rather than chopped up and disorganized? Followed it A-Z and nothing appears.

    • Actually it works fine, and has for about 5 years now. This isn’t copy-paste code, it’s a tutorial. You need to read it and understand the purpose of each step, not just follow some set of directions. This is programming, not cooking.

  8. Good job, Otto – you saved me a lot of time. Works perfectly.


  9. The method was very clear and works great. I am stuck however at passing the saved field value to a string as a variable. Something as the following

    echo 'This value of field is' .$options['text_string'];
  10. Hey Kim

    how did you do it??

    can you share??

  11. is there any working example of this or other example that works.

    if so please share…

    thanks alot

    great tutorial BTW,

  12. Hi Fabrico

    The example above works.

    Copy all the codes from step one to the last step.
    Paste it into your plugin file.
    Activate the plugin
    Check your dashboard->Settings

    You will see the new setting option.
    if you have issue. Reply.

  13. Thank you – this is an extremely helpful writeup and still relevant 5 years later.

    Only one thing I’m curious about – you claim that using this method everything “will get automatically saved to the database”… I couldn’t get it to work without adding update_option('plugin_options', $_POST["plugin_options"]); to the main page rendering function. Am I missing something that will do this for me automaticallY?

    • Yes, you don’t need to update the option yourself. The call to register_setting adds your option name to the whitelisted options.

      In the wp-admin/options.php file, where the data is submitted to, the whitelisted options get checked and update_option gets called on them automatically.

      Now, the update_option process also calls sanitize_option on each of these at the time of the update, and that will call the sanitize_option_{$option_name} filter. This is where your callback function for validation comes in. The callback function name is the third parameter to register_setting. That function you write to check your values needs to return the validated values, because it’s being used as a filter function. What it returns will be what gets saved into the database.

      So, if you’re having to call update_option yourself, then you probably didn’t point your submission form at the “options.php” page like the example shows, or your validation function isn’t returning things properly.

      • Yeah, when I do it brings me to /wp-admin/options.php with the error “ERROR: options page not found”.

        I’ve really double checked all of my naming conventions and I can’t find any errors…

        I can get it to work by submitting the form to the plugin php page and doing the update options call myself:

        &amp;lt;form method=&amp;quot;post&amp;quot; action=&amp;quot;&amp;lt;?php print get_bloginfo('wpurl').'/wp-admin/options-general.php?page=candycal_v3/candycal_v3.php'; ?&amp;gt;

        But that’s probably not as secure as the automatic way, right?

      • well that code got mangled, but you get the idea… submitting the form to /wp-admin/options-general.php?page=myplugin/myplugin.php

        • If you’re submitting it to the wrong place, then of course it’s not going to work. I didn’t use “options.php” in the example code arbitrarily.

          Do you have the proper call to settings_fields() as well? That will insert two hidden input fields which you need to have there for the options.php code to work.

          The first arguments to both settings_fields and register_settings need to be identical.

        • Ah – I figured it out!
          … sorry for the bother.
          I had a naming miss-match between register_setting( ) and settings_fields( ).

          Not a big deal, but perhaps consider renaming the first parameter of register_settings() in the tutorial to plugin_group or something else different from the second parameter would help avoid confusion between the “option group” and “option name”?

          Thanks again for the great tutorial and for taking the time to respond so quickly.

      • Otto is correct ………I wish I would have read this about 9 hours ago. There was definitely an issue with my validation function.

  14. Thanks for the useful tutorial.

    How can we write a function after settings saved.

    I use add_action( ‘update_option_theme_options’, ‘write_custom_css’, 10, 2 ); but it is not working good.

    Thanks in advance.

    • Good question. I’ve been digging around and I can’t find an answer to this either. The various ‘update_option’ hooks don’t seem to apply to the Settings API, but only occur in the update_option() function. Maybe I’m wrong about that, but in any case I can’t get them to work either.

  15. […] “Settings API Tutorial32,” Otto on WordPress […]

  16. great.. I added as like as your instruction. All working fine.. Can you tell me how can i make option with tab system??

  17. very nice tutorial, but I’m not sure if I missed it, but how do you retreive these values or settings to be used in the code ?


  18. Great tutorial!

    It’s much easier and scalable then the code I use for my plugins.

    May I translate it to portuguese and give the credits and original link?

  19. is this normal for the Settings API to exclude properties that are set to null, false or 0 when using get_option(‘options-array’)? Basically the checkbox unchecked value is omitted from the returning array. Might anyone please point me to some documentation regarding this? Not finding it myself. Thanks!

  20. I prefer not to validate the options and let the user enter what they like.

    With this in mind I removed the validation and changed the output of the field from:

    echo "<input id='plugin_text_string' name='plugin_options[text_string]' size='40' type='text' value='{$options['text_string']}' />";


    echo '<input id="plugin_text_string" name="theme_options[text_string]" size="40" type="text" value="' . ( isset( $options['text_string'] ) ? esc_attr( $options['text_string'] ) : '' ) . '" />';

    Also if an option is left empty I didn’t want it to remain in the database so I changed the validation function:

    function plugin_options_validate($input) {
    $options = get_option('plugin_options');
    $options['text_string'] = trim($input['text_string']);
    if($options['text_string'] == '') {
    return $options;
  21. Thanks so much .. was very easy to produce.

  22. hello otto, how do I display the form on the front end?

  23. May be this is a good place to ask.

    I am currently developing an Open Source Abstraction Framework for WordPress called WPExpress.

    I love WordPress as a platform but sometimes you just want to move straight to the meaningful coding.

    And we can do this installing several plugins like PODs or Building around a WordPress framework like Cherry, Genesis and things like that.

    However I want to give WordPress Developers a library they can require with composer.

    A library that renders with Mustache and Twig and provides reasonable separation of concerns.

    I have built the same example as in this post in the GitHub Documentation of the SettingsPage part of my library https://github.com/Page-Carbajal/WPExpress/wiki/The-SettingPage-Class

    If you find the time to read this far, I encourage you to go a little further and give me some feedback on this project.

    All best!

  24. I have used code samples from other pages, but came here to understand the code. I’m still not clear how you actually connect the options page to the actual plugin page, like how are is the data entered on the options page actually transferred to the plugin? None of the pages seems to explain this.

    • Hello Anja,
      The settings do not connect to any plugin on their own. They are stored in a table called wp_options.

      WordPress provides methods to access this options

      get_option( ‘optionName’, ‘defaultVale’ );


      What you are doing is building a settings page to allow the end user to save options into the DB using method add_option. Your plugin should retrieve this options using the method get_option

      More information on the WordPress Codex: https://codex.wordpress.org/Function_Reference/get_option

      You can also take a look at how to build Settings Pages with WPExpress following the link on my previous comment.

  25. Very nice and neat explanation.I’m new to wordpress development. I need to add settings for mobile apps in admin panel using API purpose with my application. I’ve read the blog thoroughly and getting doubts for which file i need to add above given code for implement my custom settings based on your blog.

  26. Hello Otto, I am very new to building wordpress plugins and settings pages. Your post on top is not that difficult to read and implement so it seems. But I failed to get it working. I get no error warning, but the saved database items simply don’t show up after saving. When you download and try my very simple jososs-menu by using this url http://www.josvanoort.nl/downloads/jososs-menu.zip, can you perhaps give it a try and see what I am doing wrong?

  27. Works well. Thank you for sharing this!

  28. Awesome article! Very clear and well written, really appreciate it! Also, Is there a tiny white dot that goes down the page or am I losing my mind?

  29. […] “Settings API Tutorial36,” Otto on WordPress […]

  30. It would be useful to make it explicit that


    is required. The Settings API does not make this clear. I expect many developers are using this great tutorial, as I did, to create their settings.

  31. If I wanted to reproduce this but with it’s own top-level menu, instead of being placed on a sub-menu of “Settings” is it easy adapted? I got it working great as it stands. Additionally, if I wanted to put this code into a separate file from the rest of my plugin called plugin-admin.php, what do I modify? Instead of calling the function plugin_options_page() I’d like it to refer to plugin-admin.php

  32. Haha, like the answers to the “ctrl-c, ctrl-v” people. :)))
    Tutorial is great! Thinking does not hurt! :)
    Keep up sharp, man, love you. :)

  33. This worked for me! Very helpful.

  34. Nice Tutorial
    thanks for posting it.

    But any idea how I can show the value of plugin_text_string to the front end.

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Need to post PHP code? Wrap it in [php] and [/php] tags.