Incorporating the Settings API in WordPress Themes

Filed in Web DevelopmentTags: Settings API, Themes, tutorials, WordPress

Register Settings and Define Form Sections/Fields

Here, we use the Settings API to register our settings, and to define our Settings page form sections and form fields.

Register Settings

The first step is simple; we register our settings:

register_setting( 'theme_oenology_options', 'theme_oenology_options', 'oenology_options_validate' );
Function: register_setting()

Codex: register_setting()

The register_setting() function associates an option group (the $option_group argument passed to settings_fields() and do_settings_sections(), above) with a database entry and a data-validation callback function.

The register_setting( $option_group, $option_name, $sanitize_callback ) function accepts three arguments:

  • $option_group: the name of the option group. Must be the same as the $option_group defined in settings_fields(). This is the "flag" that ties everything together outside of the database (i.e. within the Settings page)
  • $option_name: the name of the option as stored in the database.
  • $sanitize_callback: the name of the function that holds the data validation and whitelisting

Since we have only one database entry, which is an array that holds all of our options, we only need to call register_setting() once. If we instead had multiple database entries, we would have to call register_setting() once for each database entry.

Note: this function call needs to be hooked into admin_init.

With this simple step out of the way, we now move on to the meat of our Settings API implementation: building out the form sections and form fields.

Define Form Sections and Fields

Separating Settings Per Tab

Our Settings page has multiple tabs, but we only defined one form. We only have one form, and therefore call settings_fields() and do_settings_sections() only once within that form. If you recall, both of these settings have arguments that tie both the database entry (via register_setting()) and the settings sections (via add_settings_section()) to the form.

We want to ensure that the form only outputs/handles the settings applicable to each tab. There are at least two ways to accomplish this separation:

  1. Assign each tab's related options to a separate $option_group and $page, and add a current-tab conditional around the settings_fields() and do_settings_sections() calls.
  2. Assign all options, regardless of tab, to the same $option_group and $page, and add a current-tab conditional around the functions that register the form sections and form fields.

Because it is what I thought of first, I went with the latter option; however, the former option might make a bit more semantic sense (assuming it works; I've not tried it).

Here's how I handled it:

global $pagenow;
if ( 'themes.php' == $pagenow && isset( $_GET['page'] ) && 'oenology-settings' == $_GET['page'] ) :
     if ( isset ( $_GET['tab'] ) ) :
          $tab = $_GET['tab'];
     else:
          $tab = 'general';
     endif;
     switch ( $tab ) :
          case 'general' :
               require( get_template_directory() . '/functions/options-register-general.php' );
               break;
          case 'varietals' :
               require( get_template_directory() . '/functions/options-register-varietals.php' );
               break;
     endswitch;
endif;

Essentially, a separate file is included, depending on which tab is displayed. Each file contains the code that registers the form sections and form fields related to each tab.

The $pagenow global is basically the URL of the current page. When we hooked our Settings page into the admin menu, the add_theme_page() function appends a parameter "page" to the URL, in the form of "page=oenology-options". Our custom function that handles tabs also adds a parameter, "tab", to the URL, in the form of "tab=general". So, if we are on the General tab of our Settings page, the URL would look something like:

http://www.domain.tld/wp-admin/themes.php?page=oenology-settings?tab=general

We take advantage of these URL parameters, via $pagenow, to build our conditional.

Defining Form Sections

Form sections are added via the add_settings_section() function.

Function: add_settings_section()

Codex: add_settings_section()

As its name implies, the add_settings_section() function defines a form section. Sections are simply groupings of (presumably similar/related) form fields. A settings form may have any number of sections. All form fields must be added to form sections.

The add_settings_section( $section, $title, $callback, $page ) function accepts four arguments:

  • $section: the name/id of the section. This argument correlates to the $section argument that is passed to each call to add_settings_field(), and is also passed as the CSS ID selector of the section's containing DIV.
  • $title: the title of the section, as output on the form.
  • $callback: function that contains markup/text to be output with the section, before associated settings fields are output (e.g. to provide explanatory text/description)
  • $page: the name of the Settings page for which to output the section. Must be the same as the $page argument passed to do_settings_sections(), which we addressed previously

We simply add one call to add_settings_section() for each form section.

General Tab

For the General tab, we will define two sections: one for Header options and one for Footer options:

// Add a form section for the Header settings
add_settings_section('oenology_settings_general_header', 'Header Options', 'oenology_settings_general_header_section_text', 'oenology');

// Add a form section for the Footer settings
add_settings_section('oenology_settings_general_footer', 'Footer Options', 'oenology_settings_general_footer_section_text', 'oenology');
Varietals Tab

For our Varietals tab, we will define only one section:

// Add a form section for the Varietal theme settings
add_settings_section('oenology_settings_varietal', 'Oenology Theme Varietals', 'oenology_settings_varietal_section_text', 'oenology');

Adding Form Fields to Sections

Form sections are added via the add_settings_field() function.

Function: add_settings_field()

Codex: add_settings_field()

As its name implies, the add_settings_field() function defines a form field.

The add_settings_field( $field, $title, $callback, $page, $section ) function accepts five arguments:

  • $field: the name/id of the field; also passed as the CSS ID selector of the field's containing DIV.
  • $title: the title of the field, as output on the form.
  • $callback: function that contains markup/text to be output
  • $page: the name of the Settings page for which to output the section. Must be the same as the $pageargument passed to do_settings_sections(), which we addressed previously
  • $section: the name of the form section to which the field is added. This argument correlates to the $section argument that is passed to each call to add_settings_section()
General Tab

For our General tab, we will define three options: two of which will belong to the Header options section, and one of which will belong to the Footer options section:

// Add Header Navigation Menu Position setting to the Header section
add_settings_field('oenology_setting_header_nav_menu_position', 'Header Nav Menu Position', 'oenology_setting_header_nav_menu_position', 'oenology', 'oenology_settings_general_header');

// Add Header Navigation Menu Depth setting to the Header section
add_settings_field('oenology_setting_header_nav_menu_depth', 'Header Nav Menu Depth', 'oenology_setting_header_nav_menu_depth', 'oenology', 'oenology_settings_general_header');

// Add Footer Credit Link setting to the Footer section
add_settings_field('oenology_setting_display_footer_credit', 'Footer Credit', 'oenology_setting_display_footer_credit', 'oenology', 'oenology_settings_general_footer');
Varietals Tab

For our Varietals tab, we will define one option, which will belong to our only defined section:

// Add Varietal setting to the Varietal section
add_settings_field('oenology_setting_varietal', 'Available Varietals', 'oenology_setting_varietal', 'oenology', 'oenology_settings_varietal');

And now that we have defined all of our form sections and fields, we move on to defining the callback functions that (finally!) contain all of the markup that gets output on the Settings page.

Feedback

Comments (Comments are closed)

47 Responses to “Incorporating the Settings API in WordPress Themes”
  1. chip_bennett says:

    Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress

  2. Sayontan says:

    This is an exceptionally detailed tutorial, Chip! Looks like you beat me to it – I had started writing one based on my experience.

    I have grappled with a lot of these concepts while rewriting the options for Suffusion. In my case the situation was more complex – I have 2 levels of tabs: a horizontal one similar to your General / Varietals, and a vertical one within each. So basically I have GUI settings and Back-End Settings, with the first having Header settings, footer settings, fonts etc, and the back-end having tabs for SEO, Analytics etc.

    Also, instead of using one form, I had to use one form for each tab. Otherwise with my number of options I was running into issues with PHP installations that had Suhosin enabled (that restricts the number of post variables). That pushed the complexity to a whole different label, and I had to keep track of post variables in other forms (otherwise the whole array gets rewritten to only have options from the current form). Anyway, I guess you can see what I am saying when I submit my next version for review.

    One small note – in page 3 you are retrieving “settings-updated”. That applies to WP 3.1 onwards. In WP 3.0 you get “updated” as the returned URL parameter.

  3. chip_bennett says:

    Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: please RT

  4. chip_bennett says:

    Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: please RT

  5. Hi Chip,

    Great (and comprehensive!) tutorial here! One thing I had to find out the hard way: if you use the Settings API, you need the ‘manage_options’ capability to update the options. Normally this works fine, as only administrators have those two capabilities, but if you give editors the ability to edit theme options, they’ll need ‘manage_options’ too. This is built into the settings api, since it’s assumed it’s going to be used for options pages.

    Cheers!
    -John

  6. Chip Bennett says:

    @John P. Bloch

    Are you 100% certain that you are unable to use the edit_theme_options capability with add_theme_page()? I can confirm that I have used it with no problems whatsoever, and edit_theme_options is the capability officially recommended by the WordPress Theme Review Team and far-more-expert-than-I Theme developers such as Justin Tadlock.

  7. jonnyjaniero says:

    huge thanks for this. shines a big beam of light on the whole process.

  8. Looks like a fantastic tutorial.
    I didn’t read it yet, since now is not a good time for me to do this and so I wanted to print out the whole tutorial which sadly didn’t work.
    Not even using the print button to print individual pages worked but cropped content on each article.
    So I have to come back another time or cut and paste each part individually.
    Nevertheless, I think it will be worth it an am very greatful for this tutorial.

  9. Chip Bennett says:

    Christian:

    Check the post pagination links at the bottom of the post. I added an “all” link, that will output the entire post on a single page – specifically so that this post could be printed. Let me know if it’s not working for you!

  10. Rilwis says:

    Hi,

    I’ve read your article, and follow most of it. But I have a problem with tabs. As you said, you haven’t tried 2 options for tabs that handle form fields which are in current tabs. Sadly, when I update the form, only fields in current tab are save, fields in other tabs are ignored.

    I’m thinking that WP don’t automatically recognize tabs and save them. We need to do it ourselves. Do you know how to make it easily?

  11. Chip Bennett says:

    @Rilwis:

    I’m sorry; I’m not exactly following your question.

    The method that I describe here separates, via PHP, the form fields on one tab from the form fields on other tabs. When viewing one tab, the form fields on other tabs are never called. They don’t currently exist on the page. So, it is true: WordPress doesn’t save any settings not on the current page, because at the current point in time, WordPress doesn’t load the fields that allow those other settings to be modified.

    To do something different, such as loading all the settings on a single page, and then separate them via tabs, you’d have to take an entirely different approach (such as using jQuery to show/hide groups of settings).

  12. Sayontan says:

    @Chip, @Rilwis,
    How I handled this is by invoking the hidden fields in the validation function itself. Basically if you have separate forms you tend to lose the settings of other forms unless you have them conveyed to the back-end. Since you are anyway constructing the forms based on information you have in the back-end, it is easy to pull up the options from other screens in the validation function without passing them at form submission.

    Moreover I prefer passing it through the validation function because that keeps your form lighter.

  13. Rilwis says:

    @Chip,
    Sorry if I didn’t say clearly. Sayontan said exactly what I meant.

    It seems that the validation function is a good solution here. I’ll try it.

  14. Tobias says:

    Thanks, very useful tutorial!

  15. newbiewpcoder says:

    how can i display a message based on which button has been clicked for example if i click reset i would like it to display a message saying “settings reset” also like Sayontan says i had to change “settings-updated” to “updated” within the form code as it didn’t show the message “settings saved” when i clicked save settings

  16. Chip Bennett says:

    @newbiewpcoder:

    I’ll get back to you on your first question.

    Regarding your second question, by some point this morning (hopefully), you’ll need to use settings-updated, rather than updated. ;)

  17. newbiewpcoder says:

    thanks for this tutorial i’m building an options page using this method and trying to move all my old options over to this but i cannot get multi-select checkboxes to save as i have some older code to grab all pages into an array created in wordpress like

    $pages_array = get_pages('hide_empty=0');

    $site_pages = array();

    foreach ($pages_array as $pagg) {
    $site_pages[$pagg->ID] = $pagg->post_title;
    $pages_ids[] = $pagg->ID;
    }

    function oenology_get_default_options() {
    $options = array(
    'header_nav_menu_position' => 'top',
    'header_nav_menu_depth' => 1,
    'display_footer_credit' => false,
    'varietal' => 'cuvee',
    'menu_pages' => $pages_ids

    );
    return $options;
    }

  18. @Chip

    (Sorry for the delayed response)
    ‘edit_theme_options’ for the theme page is fine. It’s the Settings API that requires the ‘manage_options’ capability. The settings API requires you to send form data to /wp-admin/options.php, which, on lines 30-31 has this code:

    if ( !current_user_can('manage_options') )
    wp_die(__('Cheatin’ uh?'));

    The theme options page that this code creates will work without a problem, and saving the content will work without a problem — most of the time.

    The case where it doesn’t work is when you have anybody who can edit theme options but cannot manage options.

    For example, I program clients’ sites with a non-standard ‘Admin Lite’ role which is an editor that can create, edit, and delete non-Administrator users, as well as manage theme options. Because of the way options.php handles capabilities, I cannot use the settings API to validate or save the data from my theme options page. These are very specific use cases, and it might help to know about this behavior for anybody looking up this tutorial in the future. Just trying to share some hard-won knowledge in case it’s useful. :)

    -John

  19. newbiewpcoder says:

    you can delete my last comment as i managed to solve it myself, i have created a function to return each option here’s the function

    function theme_get_option($name) {
    $options = get_option('theme_oenology_options');
    if (isset($options[$name])) {
    return $options[$name];
    } else {
    return false;
    }
    }

    example of usage

    of course you may not want to echo the option but you get the idea

  20. newbiewpcoder says:

    oops my code was cut off here it is

    echo theme_get_option('display_footer_credit');

Trackbacks

  1. The WordPress Setting API | Roberto Baca
  2. ????Settings API??????????Zespia
  3. Adding Theme Options To Bloggy | Poetic Coding
  4. The WordPress Theme Review Experiment – Take II » Aquoid Themes