January 1, 2011

Extended WordPress Settings API Tutorial, Part 1

The WordPress Settings API has been around for a little while, but I haven’t used it until now. I highly recommend it. It does a lot of work for you and is still flexible enough to create custom-styled options pages (with a little jQuery).

Once you’re done with this tutorial, this is what you’ll have:

Theme Options screenshot

This will be a little more complicated than just putting code into functions.php (that’s why it’s an “extended” tutorial). Here we go!

If you want to download the complete code in advance to follow along, here it is:

Download the files (updated June 5, 2011)

How Options are Stored in the Database

Using the Settings API, WordPress creates an array of options and puts it into a single database entry. This is the cleanest, most efficient way to do it. Without the Settings API, it requires a decent chunk of code to accomplish that. You can create more than one options array if you wish, but in this tutorial we’re putting all the options in the same array.

1. Set Up the Theme Options Class

Almost everything we do is going to be contained in a custom PHP class so we can be more liberal with our function and variable names. First, create the PHP file. The naming standard for PHP class files is class.classname-here.php or class-classname-here.php. For the tutorial, I’m naming the class My_Theme_Options.

We’ll start with a couple of lines of code in functions.php:

if ( file_exists( STYLESHEETPATH . '/class.my-theme-options.php' ) ) {
	require_once( STYLESHEETPATH . '/class.my-theme-options.php' );
}

This will start up our theme options class. The rest of the PHP in this tutorial is written within that class, so you can close functions.php. Note: since we haven’t written the class yet, this will break your theme if you make it live before the next step.

Make a new file in the theme folder and make sure the filename matches the code above. In that file, create the My_Theme_Options class:

<?php

class My_Theme_Options {

	/* Array of sections for the theme options page */
	private $sections;

	/* Initialize */
	function __construct() {
		// This will keep track of the checkbox options for the validate_settings function.
		$this->checkboxes = array();
		$this->settings = array();
		$this->get_settings();
		
		$this->sections['general']      = __( 'General Settings' );
		$this->sections['appearance']   = __( 'Appearance' );
		
		add_action( 'admin_menu', array( &$this, 'add_pages' ) );
		add_action( 'admin_init', array( &$this, 'register_settings' ) );
		
		if ( ! get_option( 'mytheme_options' ) )
			$this->initialize_settings();
	}

	/* Add page(s) to the admin menu */
	public function add_pages() {
		$admin_page = add_theme_page( 'Theme Options', 'Theme Options', 'manage_options', 'mytheme-options', array( &$this, 'display_page' ) );
	}

	/* HTML to display the theme options page */
	public function display_page() {
		// code
	}

	/* Define all settings and their defaults */
	public function get_settings() {
		// code
	}

	/* Initialize settings to their default values */
	public function initialize_settings() {
		// code
	}

	/* Register settings via the WP Settings API */
	public function register_settings() {
		// code
	}
 }
 ?>

What’s going on here? Well, the __construct() function is called automatically when the class is used to create a variable. (This only works in PHP5; for PHP4, you must use the class name as the function name.) We’re defining the sections in which we want our theme options organized, and then using two WordPress hooks to run a couple of functions. The add_pages() function is adding our theme options page to the menu in the WordPress admin, under “Appearance.” It’s just a blank page right now, but we’ll fill it in later.

2. Register the Settings

Registering settings with the WordPress Settings API is extremely simple if you only have a few settings, but once you get a dozen or more settings involved, it’s best to streamline your code.

In the WordPress Settings API, individual settings need a callback function to display the HTML. This does not need to be a unique function for each setting. If it shares a callback function with other settings, you need to pass arguments to that callback function to tell it which setting it needs to display.

To add all the settings to our database, we’re going to create our own helper function to register the setting with WordPress and then pass the proper arguments to the callback function. Put this inside the class:

/* Create settings field */
public function create_setting( $args = array() ) {

	$defaults = array(
		'id'      => 'default_field',
		'title'   => 'Default Field',
		'desc'    => 'This is a default description.',
		'std'     => '',
		'type'    => 'text',
		'section' => 'general',
		'choices' => array(),
		'class'   => ''
	);

	extract( wp_parse_args( $args, $defaults ) );

	$field_args = array(
		'type'      => $type,
		'id'        => $id,
		'desc'      => $desc,
		'std'       => $std,
		'choices'   => $choices,
		'label_for' => $id,
		'class'     => $class
	);

	if ( $type == 'checkbox' )
		$this->checkboxes[] = $id;

	add_settings_field( $id, $title, array( $this, 'display_setting' ), 'mytheme-options', $section, $field_args );

}

The only thing that needs to be changed here are the default arguments at the top. You can make these whatever you like. Here’s what each one does:

  • id: the ID of the setting in our options array, and the ID of the HTML form element
  • title: displayed as the label for the HTML form element
  • desc (optional): description displayed under the HTML form element
  • std: default value for this setting
  • type: which HTML form element to use
  • section: the section to put this setting into—must match the array ID of a section defined in __construct()
  • choices (optional): different choices in radio buttons or a drop-down menu
  • class (optional): add a custom class to the HTML form element

When you create the settings themselves, you’ll have to fill out these arguments for each one. If any of the arguments is left blank, then the default set in this function will be applied.

We’re also keeping track of which fields are checkboxes, because they need special treatment when the settings are saved.

The final line in this function is the WordPress Settings API in action. The “mytheme-options” argument is the slug for the admin page (it needs to match the slug used in the add_theme_page() function inside our add_pages() function.

In order for the settings to be initialized when the theme is activated, we specify our settings in a separate function, get_settings().

/* Define all settings and their defaults */
public function get_settings() {
	
	/* General Settings
	===========================================*/
	
	$this->settings['example_text'] = array(
		'title'   => __( 'Example Text Input' ),
		'desc'    => __( 'This is a description for the text input.' ),
		'std'     => 'Default value',
		'type'    => 'text',
		'section' => 'general'
	);
	
	$this->settings['example_textarea'] = array(
		'title'   => __( 'Example Textarea Input' ),
		'desc'    => __( 'This is a description for the textarea input.' ),
		'std'     => 'Default value',
		'type'    => 'textarea',
		'section' => 'general'
	);
	
	$this->settings['example_checkbox'] = array(
		'section' => 'general',
		'title'   => __( 'Example Checkbox' ),
		'desc'    => __( 'This is a description for the checkbox.' ),
		'type'    => 'checkbox',
		'std'     => 1 // Set to 1 to be checked by default, 0 to be unchecked by default.
	);
	
	$this->settings['example_heading'] = array(
		'section' => 'general',
		'title'   => '', // Not used for headings.
		'desc'    => 'Example Heading',
		'type'    => 'heading'
	);
	
	$this->settings['example_radio'] = array(
		'section' => 'general',
		'title'   => __( 'Example Radio' ),
		'desc'    => __( 'This is a description for the radio buttons.' ),
		'type'    => 'radio',
		'std'     => '',
		'choices' => array(
			'choice1' => 'Choice 1',
			'choice2' => 'Choice 2',
			'choice3' => 'Choice 3'
		)
	);
	
	$this->settings['example_select'] = array(
		'section' => 'general',
		'title'   => __( 'Example Select' ),
		'desc'    => __( 'This is a description for the drop-down.' ),
		'type'    => 'select',
		'std'     => '',
		'choices' => array(
			'choice1' => 'Other Choice 1',
			'choice2' => 'Other Choice 2',
			'choice3' => 'Other Choice 3'
		)
	);
	
	/* Appearance
	===========================================*/
	
	$this->settings['header_logo'] = array(
		'section' => 'appearance',
		'title'   => __( 'Header Logo' ),
		'desc'    => __( 'Enter the URL to your logo for the theme header.' ),
		'type'    => 'text',
		'std'     => ''
	);
	
	$this->settings['favicon'] = array(
		'section' => 'appearance',
		'title'   => __( 'Favicon' ),
		'desc'    => __( 'Enter the URL to your custom favicon. It should be 16x16 pixels in size.' ),
		'type'    => 'text',
		'std'     => ''
	);
	
	$this->settings['custom_css'] = array(
		'title'   => __( 'Custom Styles' ),
		'desc'    => __( 'Enter any custom CSS here to apply it to your theme.' ),
		'std'     => '',
		'type'    => 'textarea',
		'section' => 'appearance',
		'class'   => 'code'
	);
	
}

I included one of each possible type in this tutorial. You can just copy and paste these to add more settings to the page. Note: The ‘section’ argument must match the ID of a section defined in __construct().

Now that we have our helper function and the settings in place, let’s use them! Update the register_settings() function with this:

/* Register settings via the WP Settings API */
public function register_settings() {

	register_setting( 'mytheme_options', 'mytheme_options', array ( &$this, 'validate_settings' ) );

	foreach ( $this->sections as $slug => $title )
		add_settings_section( $slug, $title, array( &$this, 'display_section' ), 'mytheme-options' );

	$this->get_settings();
	
	foreach ( $this->settings as $id => $setting ) {
		$setting['id'] = $id;
		$this->create_setting( $setting );
	}

}

At the beginning of the function, register_setting() belongs to the Settings API. This is making our master settings array in the database, which is being named “mytheme_options.” The “validate_settings” argument is a function we will define later, to add custom validation to the user-entered data.

The add_settings_section() function is registering the different sections we defined in __construct().

3. Initialize Settings

When the theme is first activated, the settings need to be initialized and set to their default values (this is especially important if the options control title tags or other front-end output).

/* Initialize settings to their default values */
public function initialize_settings() {
	
	$default_settings = array();
	foreach ( $this->settings as $id => $setting ) {
		if ( $setting['type'] != 'heading' )
			$default_settings[$id] = $setting['std'];
	}
	
	update_option( 'mytheme_options', $default_settings );
	
}

What’s next?

At this point, the settings are all defined, and there are only two things left to do: tell WordPress how to display the settings, and create our custom validation function (which is entirely optional). That’s it!

Read Part 2 of the WordPress Extended Settings API Tutorial »

View this post online: http://alisothegeek.com/2011/01/wordpress-settings-api-tutorial-1/

Leave a Reply

20 thoughts on “Extended WordPress Settings API Tutorial, Part 1

  1. Random Wordpresser says:

    dang, you beat me to it — i was just planning on writing something like this

  2. Anonymous says:

    Thank you very much for the code here. I’m working on integration with my Flickr Shortcode Importer plugin.

  3. Faxorn says:

    great work

  4. Splendidangst says:

    I’m looking to create an api login for the 4 sites I own that would be affiliated with each other, is it possible to use this tutorial to allow registered members on the main site to login with that sites credentials on any other wordpress, buddypress, social engine, and php site?

  5. lawrenced says:

    i love this, this is great, and you have the option easy as an array so even an idiot like I can add more options and pages etc…thanks for this mwahhaa!!!!

  6. DrLightman says:

    Thank you for this excellent tutorial and the undoubtly clean tutorial files :)

  7. Robert Harm says:

    Great tutorial, thanks – saves me a lot of time. Nevertheless I have a problem which that I couldnt solve yet – I hope someone can help:

    I want use the settings class for my plugin, which has its own admin page and subpages:

        add_menu_page(__(‘Leaflet Maps Marker’, ‘lmm’), ‘Leaflet Maps Marker’, ‘activate_plugins’, ‘leaflet_markers’, array(&$this, ‘leafletmapsmarker_options_markers’), LEAFLET_PLUGIN_URL.’/img/menu-icon.png’ );
        add_submenu_page(‘leaflet_markers’, __(‘Leaflet Maps Marker – Marker’, ‘lmm’), __(‘Markers’, ‘lmm’), get_option( ‘leafletmapsmarker_capabilities’ ), ‘leaflet_markers’, array(&$this,

    So I wanted to make your settings page appear under my admin-menu “leaflet_markers”. So I changed your line

    $admin_page = add_theme_page( __( ‘Theme Options’ ), __( ‘Theme Options’ ), ‘manage_options’, ‘mytheme-options’, array( &$this, ‘display_page’ ) );

    to

    $admin_page = add_submenu_page(‘leaflet_markers’, __(‘Leaflet Maps Marker – Einstellungen’, ‘lmm’), __(‘Settings’, ‘lmm’), ‘activate_plugins’, ‘leaflet_defaults’, array( &$this, ‘display_page’ ));

    Result: the new entry “settings” appears in my custom admin menu, URL appearing when hovering the link is not
    “/wp-admin/admin.php?page=leaflet_defaults” but “/wp-admin/leaflet_defaults”

    When I open “/wp-admin/admin.php?page=leaflet_defaults” manually, I get the error “You do not have sufficient permissions to access this page.” Somehow, the hookname is missing (according to http://hungred.com/how-to/sufficient-permissions-access-page-plugin-admin-page-update-wordpress-30/)

    Does anyone know what has to be changed in order to display the settings menu entry not within Design but within a custom Admin menu page???

    Any help is really appreciated!

    • Jamesmehorter says:

      Hi Robert, did you ever find a solution to this? It would appear that the settings api is only available for admin pages under ‘Settings’. I wanted to use the settings api with a plugin that lives under ‘Appearance’ but could not seem to find a way to use the api in conjunction with add_submenu_page()

      • Robert Harm says:

        Hi James,

        yes, I found a solution:

        1. I added the class within my plugin file (I renamed the class to Leafletmapsmarker_options)
        require_once( plugin_dir_path( __FILE__ ).’class-leaflet-options.php’ );

        2. then in my plugin file, I created a submenu-page with callback to function lmm_settings

        $this, ‘lmm_help’) );
            $page7 = add_submenu_page(‘leafletmapsmarker_markers’, ‘Leaflet Maps Marker – ‘ . __(‘Settings’, ‘lmm’), __(‘Settings’, ‘lmm’), ‘activate_plugins’,'leafletmapsmarker_settings’, array(&$this, ‘lmm_settings’) );

        3. the function lmm_settings calls the display_page-function in the settings class:

          function lmm_settings() {
            $lmm_options = new Leafletmapsmarker_options();
            $lmm_options->display_page();
          }

        BTW: I also added versioning to settings in order to automatically initialize new default settings on plugin updates. This works like this (example code – for full code please see mapsmarker.com – plugin will be released soon):

                $this->settings['default_basemap_name_osm_osmarender'] = array(
                    ‘version’ => ’1.1′,
                    ‘title’   => ‘OpenStreetMap (Osmarender)’,
                    ‘desc’    => ”,
                    ‘std’     => ‘OSM Osmarender’,
                    ‘type’    => ‘text’,
                    ‘section’ => ‘basemaps’
                );
        ….
            public function save_defaults_for_new_options() {
                //info:  set defaults for options introduced in v1.1
                if (get_option(‘leafletmapsmarker_version’) == ’1.0′ )
                {
                    $new_options_defaults = array();
                    foreach ( $this->settings as $id => $setting )
                    {
                        if ( $setting['type'] != ‘heading’ && $setting['type'] != ‘helptext’ && $setting['version'] == ’1.1′)
                        {
                        $new_options_defaults[$id] = $setting['std'];
                        }
                    }
                $options_current = get_option( ‘leafletmapsmarker_options’ );
                $options_new = $options_current + $new_options_defaults;
                update_option( ‘leafletmapsmarker_options’, $options_new );
                }

  8. Anonymous says:

    This is one of te best articles and tutorials on understanding the WordPress Settings API. Thank You.

  9. It’s amazing!
    I’m bookmarking to take my pretty not-so-functional themes to the next level!

    I’d be cool if you use this same way to add fields for a plugin settings page, either to appear under the settings section or its own menu. I’d take 10 minutes for you and 3 days for me – so far what’s been taking me to add settings page to my plugin :(  

  10. Newb says:

    Hello! Thanks for creating this amazing panel. I’m using Windows with Safari browser, and i noticed that the tabs are not working in Safari. All the other browsers are OK, but safari the tab does not work when I click ober them…

  11. ronaldao says:

    HELLO! PLEASE, i just want to use add_menu_page()  and add_submenu_page() togeter and it DOES NOT WORK never!

    I tried this one:

    public function add_pages() { $admin_page    = add_menu_page(__( THEMENAME.’ Configurações’, THEMENAME ), __( THEMENAME, THEMENAME ), ‘manage_options’, ‘pp-options’, array( &$this, ‘display_page’ ), get_bloginfo(‘stylesheet_directory’).’/images/favicon.ico’ ); add_submenu_page(  ’admin.php?page=pp-options#cores’, __( ‘Cores’, THEMENAME ), __( ‘cores’, THEMENAME ), ‘manage_options’, ‘pp-options’, array( &$this, ‘display_page’ ) ); add_action( ‘admin_print_scripts-’ . $admin_page, array( &$this, ‘scripts’ ) );add_action( ‘admin_print_styles-’ . $admin_page, array( &$this, ‘styles’ ) ); }

    But does not work. How can I do that, please?

  12. ronaldao says:

    I have an ideia to improve this script.

    1. Actually, when we hit the save button, after that, it goes to first menu again. For exemple, if I have 3 tabs (Dashboard, Color, Config) and I’m on Colors tab and click save, it redirects to the Dashboard tab again. Everytime I have to selecr Color Tab. 

    The idea:

    It could be better if after clicking Save button, to be redirect to the same Tab where we are editing by:

    /admin.php?page=options&settings-updated=true&#colors

    where the end #colors is the name of the current tab.

    How Can I do that?

  13. Chaz says:

    Hey everyone! I’ve managed to insert a file upload button, but can’t get a WYSIWYG editor to work. It shows up in theme options, but it doesn’t save changes. Any hints how could I fix that?

  14. trswart says:

    I’m using the WordPress Settings API to create a theme options page that handles images and links. Does anyone know of a way to dynamically let the user add a new field (add_settings_field) on the fly. For instance if it started with 1 field, but they needed a second for another picture/video/post.

  15. Fred says:

    Hello, this is an excellent tutorial!
    A question, where is the validate_settings() function? Because when I save the settings, nothing happen.
    Did I miss something?
    Thanks!

  16. Shiba says:

    I am facing a problem in this… When i select any tab it disappears, rather i want it to be visible with its URL changing automatically. Where in the code do I have to make necessary changes? Can somebody help me solve this?

  17. Shiba says:

    I downloaded the .zip.. Can somebody tell me what is the use of function mytheme_option($option). How do i use this function? What exactly should be passed to this function?

  18. Simon says:

    Could I use this code for my own commercial clients websites? There’s no licenses and such? I won’t use it straight of, It will be a customized one

I'm speaking at WordCamp Minneapolis 2013