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 »