Adding Settings to an Existing Page Using the Settings API


When it comes to saving options and settings in WordPress, you all probably know and use the Options API: add_option(), update_option(), get_option() and delete_option(). These functions are the first layer of another, broader and powerful, yet under-used, API: the Settings API.

Goal of this Tutorial

Some time ago I wrote a quick tutorial on how to use the Settings API to create your plugins' option pages, and then Otto went deeper in it with a more detailed WordPress Settings API Tutorial.

Most tutorials you'll find out there will show you how to create an option page for your plugin. In this tutorial we'll take a different approach and will use the Settings API to add fields to an existing page.

Scenario: you're making a neat site & theme for a client. The client's Boss wants to receive an email every time a new post is published, so you'll add an option in the Writing Settings page where the blog admin will enter the Boss' email address. The end result will be something like the following:

Throughout this tutorial we'll use 'ozhwpe' (for 'Ozh WP Engineer') as a prefix for functions and everything. I'll try to make code snippets as self explanatory as possible with comments for quick cut-and-paste, and then add more explanations for the first time understanding.

Enough chit-chat, it's time to write actual code!

Register and Define the New Setting

The function register_setting() tells WordPress that you'll need the Settings API and whitelists new options, and then add_settings_field() defines the input fields to be used.

// Register and define the settings
add_action('admin_init', 'ozhwpe_admin_init');

function ozhwpe_admin_init(){
	register_setting(
		'writing',                 // settings page
		'ozhwpe_options',          // option name
		'ozhwpe_validate_options'  // validation callback
	);
	
	add_settings_field(
		'ozhwpe_notify_boss',      // id
		'Boss Email',              // setting title
		'ozhwpe_setting_input',    // display callback
		'writing',                 // settings page
		'default'                  // settings section
	);

}

Let's dissect this a bit.

The three string parameters for register_setting() are:

  1. $option_group: the setting group name, or its "page". It's either created by you (for instance on a standalone plugin option page) or, in this case, an existing group. We'll target the 'writing' group, which as its name suggest belongs to the "Writing Settings" page.
  2. $option_name, here 'ozhwpe_options': the option name, as you would use it in a get_option()
  3. $sanitize_callback: an optional callback function used to sanitize the input. We'll define that function later

The function add_settings_field() is passed five parameters, which are:

  1. $id: the field id. Make it a unique string
  2. $title: the title of the setting, printed right next to the field
  3. $callback, here 'ozhwpe_setting_input': the callback function that will echo the form field itself
  4. $page: the settings page on which to show the field, here 'writing'
  5. $section: the section of the settings page in which to add the field, with a value of 'default' if omitted. The section is either an existing section in a page, or a whole new section defined using add_settings_section(). Here we'll add our field in the 'default' section of the 'writing' page.

Core Settings Pages and Sections Names

We've decided to target the 'default' section of the 'writing' Settings page, but of course it's possible to add form fields to all other existing page. Here is a comprehensive list of all these places:

Settings Page Settings Name Sections
General Settings 'general' 'default'
Writing Settings 'writing' 'default'

'remote_plublishing'

'post_via_email'
Reading Settings 'reading' 'default'
Discussion Settings 'discussion' 'default'
'avatars'
Media Settings 'discussion' 'default'
'embeds'
'uploads'
Privacy Settings 'privacy' 'default'
Permalink Settings 'permalink' 'optional'

First Callback: Display the Field

In the first code block, we've referred to a function callback 'ozhwpe_setting_input' that would display the field itself. Let's define that function now:

// Display and fill the form field
function ozhwpe_setting_input() {
	// get option 'boss_email' value from the database
	$options = get_option( 'ozhwpe_options' );
	$value = $options['boss_email'];
	
	// echo the field
	?>
<input id='boss_email' name='ozhwpe_options[boss_email]'
 type='text' value='<?php echo esc_attr( $value ); ?>' /> Boss wants to get a mail when a post is published
	<?php
}

Nothing hardcore here: we read any existing value of the field using get_option() and display an simple HTML text field.

Still, note the fine "array trick": options are stored in a new wp_options table entry named 'ozhwpe_options', which will contain an array of every values you'll have to save (see the HTML name of the input field). Remember: every time you save multiple options each in their own DB entry, God kills a narwhal. Please, all in one array.

While we're at it, also notice that $value is escaped before printing, using esc_attr() in order to kill any XSS joke you might want to attempt. But, again, that's nothing new to you, right? Right?

Second Callback: Validation Function

The first function call mentions 'ozhwpe_validate_options' as a callback that will validate the input. You don't want bogus stuff to be stored in your DB, do you?

// Validate user input and return validated data
function ozhwpe_validate_options( $input ) {
	$valid = array();
	$valid['boss_email'] = sanitize_email( $input['boss_email'] );
	return $valid;
}

To validate the user input, we're using here sanitize_email(), a built-in WordPress function that makes sure a string looks like an email.

Best Practice Alert™: when you want to validate or sanitize an array, pick and validate only selected parts of it and return a 100% safe new array. Naming it $valid and initializing it as an empty array is a good hint for code readers that you're doing it right and paying attention to security.

The opposite and sucky way would be here to validate $input['boss_email'] and return $input, which is sucky because this array may also contain $input['evilstring'].

Second Callback v2.0: Validation Function And Error Feedback

While we're at it, let's improve a bit things: using add_settings_error() we will display a nice error message if input data does not pass the validation test, so the user knows that we know that he is a dummy.

// Validate user input
function ozhwpe_validate_options( $input ) {
	$valid = array();
	$valid['boss_email'] = sanitize_email( $input['boss_email'] );
	
	// Something dirty entered? Warn user.
	if( $valid['boss_email'] != $input['boss_email'] ) {
		add_settings_error(
			'ozhwpe_boss_email',           // setting title
			'ozhwpe_texterror',            // error ID
			'Invalid email, please fix',   // error message
			'error'                        // type of message
		);		
	}
	
	return $valid;
}

The function add_settings_error() takes up to 4 parameters:

  1. $setting: the slug title of the setting that triggered the error
  2. $code: an error ID, that will be used in the HTML ID of the printed <div>
  3. $message: the error message itself, that will wrapped inside a <div> and <p>.
  4. $type: this optional parameter with a default value of 'error' controls the HTML class name for the error <div>. Core styles are 'error' or 'updated'.

Now, if Clueless Joe enters something that does not look like a valid email, he'll get an error message as shown here:

All Done

Let's review the whole entire plugin:

<?php
/*
Plugin Name: Settings API Example
Plugin URI: http://wpengineer.com/
Description: Complete and practical example of use of the Settings API to add fields on the Writing option page
Author: Ozh
Author URI: http://ozh.org/
*/

// We'll use ozhwpe (Ozh / WP Engineer) as a prefix throughout the plugin

// Register and define the settings
add_action('admin_init', 'ozhwpe_admin_init');
function ozhwpe_admin_init(){
	register_setting(
		'writing',                 // settings page
		'ozhwpe_options',          // option name
		'ozhwpe_validate_options'  // validation callback
	);
	
	add_settings_field(
		'ozhwpe_notify_boss',      // id
		'Boss Email',              // setting title
		'ozhwpe_setting_input',    // display callback
		'writing',                 // settings page
		'default'                  // settings section
	);

}

// Display and fill the form field
function ozhwpe_setting_input() {
	// get option 'boss_email' value from the database
	$options = get_option( 'ozhwpe_options' );
	$value = $options['boss_email'];
	
	// echo the field
	?>
<input id='boss_email' name='ozhwpe_options[boss_email]'
 type='text' value='<?php echo esc_attr( $value ); ?>' /> Boss wants to get a mail when a post is published
	<?php
}

// Validate user input
function ozhwpe_validate_options( $input ) {
	$valid = array();
	$valid['boss_email'] = sanitize_email( $input['boss_email'] );
	
	// Something dirty entered? Warn user.
	if( $valid['boss_email'] != $input['boss_email'] ) {
		add_settings_error(
			'ozhwpe_boss_email',           // setting title
			'ozhwpe_texterror',            // error ID
			'Invalid email, please fix',   // error message
			'error'                        // type of message
		);		
	}
	
	return $valid;
}

And that's it for the user interface part. Of course, the plugin now needs to have a few add_action() to trigger the email sending part when a new post is published, but this is outside of the scope of this tutorial.

We're done with the coding, but here are a couple of design considerations now.

Why Use The Settings API

The benefits of using the Settings API over manually coding yourself your whole plugin page and checking $_POST for data submission are pretty chunky: you focus just on defining what you need, and let WordPress manage the repetitive parts, which are:

  • output most of the HTML (<table> rows and stuff).
  • monitor form submission and take care of $_POST data
  • create and update options in the database if necessary
  • include all the anti-XSS and anti-CSRF stuff (that is, nonces and referrer checks)

I find the first point alone enough as a reason why freelancers and web agencies should use the Settings API: future proofness. When the interface of WordPress will change in 10 years (new styles, new colors, new CSS class names), your plugin interface will still flawlessly integrate because you did not hardcode any styled HTML.

And, obviously, the 4th point alone is enough as a reason why everybody should use this API: implement all the security measure without even thinking of them.

Adding Fields to an Existing Page or Making a New Page?

If you're making a plugin that will be downloaded by users, I think it's often best to make a standalone new option page. WordPress users are mostly used to installing plugins and then look for a new menu where they'll configure it, and it's certainly more convenient than forcing them to check every Settings page for new fields that may have appeared.

On the contrary, if you're delivering a key-in-hand CMS solution for a client, it would not make sense to split settings that relate over different pages. For such a client, what you're giving them is a consistant whole and there is no such thing as core features vs plugin feature, so adding fields to appropriate existing Settings pages makes much more sense.

Guest Post

This post was written byOzh, he has been using and hacking with WordPress since may 2004 on version 1.0.1, and released his first WordPress plugin more than 6 years ago. His passion for plugins recently shaped up in writing a dedicated book with Brad Williams and Justin Tadlock: Professional WordPress Plugin Development will hit the shelves in March 2011. If you like this article, you will love the book!

Comments are closed.

7 comments

  1. David Gwyer

    Great post, especially the sanitization parts. This is easily overlooked when writing Plugins. Once you have got things working, how many times do developers not really bother with sanitization.

    Maybe it's because they can't be bothered, or see the point, or not really know how to implement it properly.

    You showed in practice how easy this really is to do, and why it should always be implemented in your Plugins!

  2. Chris Lee

    How would then be able to access these values from say a templating point?

    I.e. i want an address field for the client to be displayed at the bottom footer.

  3. Anton

    Great post. I've been looking for a way to do this for a while as I very much agree with you finishing comments about global plugins vs custom CMSs.
    It's great to have the ability to hook into already built-in option pages in order to add more functionality.
    The less clutter on the front end, the better :)

  4. Ozh

    Chris Lee: well, just as usual... using get_option()...

  5. Chris Lee

    @ozh!

    ahh! thanks

  6. Kathy

    I've read both your tutorials and the one at Ottopress, but they all seem to leave me stuck on one thing.

    The callback function that generates the input appears static- which seems really limiting. If you have 10 textbox inputs, you wouldn’t want to have 10 textbox callbacks- when you could just have 1 callback that accepts arguments. I haven’t gotten a dynamic version to work yet, but this has to be possible right?

  7. Kathy

    Yup- its possible. You have to use the last parameter of add_settings_field to pass arguments as an array.

    so:
    [code]
    add_settings_field('plugin_text_string', 'Plugin Text Input', 'plugin_setting_string', 'plugin', 'plugin_main', $args=array("name"=>"string1"));

    [/code]

    and then :

    [code]
    function plugin_setting_string($args) {
    $options = get_option('plugin_options');
    echo '';
    }
    [/code]

    That's a good start, but I've still got a long way to go to reproduce all my input types. I'd also like to explore explore using the settings API with ajax.