Creating Custom Options Pages in WordPress

Out with the old; in with the new. Not too long ago, the process of creating an Options page for a WordPress theme was a bit of a nightmare. Sure, it was relatively easy to do once you’d wrapped your head around the process, but, it required an extensive amount of work, including writing security checks, creating the forms, manually adding hidden inputs, and so on.

Luckily, as of WordPress 2.7, the process has been streamlined, thanks to the new Settings API. In this tutorial, Jeffrey Way, author of Build Your Own Wicked WordPress Themes demonstrates exactly how to build an Options page from scratch. He also included a video version of this tutorial for those who prefer a more visual approach. You’ve no doubt seen a number of these sorts of articles around the web, but this will make the process as understandable as possible—particularly for those of you who are just getting started! Let’s begin.

Planning the Finished Product

First up, let’s organize our thoughts a bit, and determine exactly what we want our final product to look like and do.

This Options page should allow us to upload images, add text, and perhaps choose color schemes. Figure 1, “The finished Options page” shows what we’d like our Options page to look like by the time we’re done.

Figure 1. The finished Options page

The finished Options page


Once a user has entered values into this form, we can retrieve them from within our theme template files using the helpful get_option method, like so:

<?php $options = get_option('plugin_options'); ?><h2><?php echo $options['banner_heading']; ?></h2>

In accordance with the value entered in Figure 1, “The finished Options page”, this will render “Buy Me.” Pretty convenient, right? Okay, let’s build this thing.

Making a Start

We have two alternatives when it comes to creating the Options page. We can either treat it as a plugin, and store it in the plugins folder, or we can make it a theme-specific Options page, and store it within our theme’s functions.php file (or another file included from functions.php). In this case, we’ll go with the latter solution, though either approach will work.

As functions.php will store a variety of functions for your theme, it’s best to keep your Options page in its own file, which we can then include from functions.php. Start by creating a folder called functions within your theme folder. In this folder, create a new file called admin-menu.php. Next, we need to make this page visible from our core functions file. Return to functions.php, and include this file like so:

Example 1. theme/functions.php (excerpt)

<?php require_once(TEMPLATEPATH . '/functions/admin-menu.php'); ?>


As TEMPLATEPATH will automatically generate the path to your theme folder, we need only continue the path to admin-menu.php.

Adding the Options page

Next, we must tell WordPress to add a new Options page to the WordPress dashboard. Conveniently, WordPress provides the useful add_options_page function. However, we can’t immediately call this method—we have to register the function with the admin_menu hook first:

Example 2. theme/functions/admin-menu.php (excerpt)

add_action('admin_menu', 'create_theme_options_page');function create_theme_options_page() {  add_options_page('Theme Options', 'Theme Options', 'administrator', __FILE__, 'build_options_page');}


The code above instructs WordPress to call the create_theme_options_page function, which adds the page to the Settings section of the WordPress dashboard. The add_options_page function requires a handful of parameters:

  • $page_title: the title of the page

  • $menu_title: the name of the menu

  • $access_privileges: who has permission to access this page

  • $page_name: can be a unique string, but many developers prefer to use __FILE__ to ensure that the name is unique, and doesn’t have the potential to clash with any other pages

  • $callback: the function that will handle the creation of the options form

Let’s break this down. We’re essentially saying, “Add a new page to the Settings section of the dashboard, use these values for the title and access privileges, and then call the build_options_page function to handle the creation of the page.”

Figure 2, “The Theme Options link in the WordPress dashboard” shows the link output in the Settings section of the WordPress dashboard menu.

Figure 2. The Theme Options link in the WordPress dashboard

The Theme Options link in the WordPress dashboard


Building the Options Page

We’ve called the build_options_page function; now we need to create it:

Example 3. theme/functions/admin-menu.php (excerpt)

function build_options_page() {?>  <div id="theme-options-wrap">    <div class="icon32" id="icon-tools"> <br /> </div>    <h2>My Theme Options</h2>    <p>Take control of your theme, by overriding the default settings with your own specific preferences.</p>    <form method="post" action="options.php">      <p class="submit">        <input name="Submit" type="submit" class="button-primary" value="<?php esc_attr_e('Save Changes'); ?>" />      </p>    </form>  </div><?php}


Figure 3. The basic outline of our Options page

The basic outline of our Options page


This should be familiar to you, except perhaps for a couple points:

  • The div with a class of icon32 is used to add that little icon. WordPress has a variety of icons that it uses for various pages; we’re just borrowing this one for our custom page.

  • By default, WordPress dashboard buttons are grey. By applying a class of button-primary, for which WordPress has already written styling, we can make the button blue, and have it “pop” a bit more.

  • We use esc_attr_e('Save Changes') as the value for the Submit button. We use the translation function to ensure that this text can be localized.

Remember, when possible, to always use WordPress’s built-in classes. This makes your pages more consistent with the visuals of the dashboard.

Register the Settings

You must have noticed that—somewhat strangely, perhaps—we didn’t add any inputs to our form. If we were using the old method, we would have done so; however, with the Settings API, the process works a bit differently.

We need to register our new option in the database using the register_setting function. But before we can do that, we must again listen for the admin_init hook. The admin_init hook is triggered when you access the dashboard. This is the point when we need to register our option settings. As the second parameter, we’ll pass a callback function name that will handle the process of registering our settings:

Example 4. theme/functions/admin-menu.php (excerpt)

add_action('admin_init', 'register_and_build_fields');function register_and_build_fields() {   register_setting('plugin_options', 'plugin_options', 'validate_setting');}


This register_setting function accepts three parameters:

  • $option_group: a string representing the name of the settings group

  • $option_name: the name of the option

  • $sanitize_callback: a callback function that can handle any specific operations or sanitizing

By registering this plugin_options setting, we can then ultimately access its stored values using this code:

$options = get_option('plugin_options');echo $options['value'];

Since we’ve specified validate_setting as the callback function, we need to define it. Our callback function needs to accept the $plugin_options variable, and return it when it’s finished validating or sanitizing:

Example 5. theme/functions/admin-menu.php (excerpt)

function validate_setting($plugin_options) {  return $plugin_options;}


We’ll be doing more with this function in the next example.

Next, we need to use the add_settings_section method, which allows us to create sections—groups of like items—on our Options page. Sections let us organize the information that’s displayed, to make it more usable. You can create multiple sections should your project need them, but in most cases, one will do just fine. So let’s append the following code to our register_and_build_fields function:

Example 6. theme/functions/admin-menu.php (excerpt)

add_settings_section('main_section', 'Main Settings', 'section_cb', __FILE__);


This function can accept four parameters:

  • $id: a unique ID for the section

  • $title: a heading that will be displayed above the fields on the page

  • $callback: can handle the creation of the section; if it’s declared, you must create the function, or an error will be thrown

  • $page: defines the type of settings page that this section should be applied to; in our case, it should apply to our custom page, so we used __FILE__ as the name

To avoid throwing an error, we need to define the callback function we just set. It’s simple:

Example 7. theme/functions/admin-menu.php (excerpt)

function section_cb() {}


Adding the Fields

The next step is to add the fields, or the inputs, for our Options page using the add_settings_field function. Let’s start with the banner_heading field. The following code can again be appended to the register_and_build_fields function:

Example 8. theme/functions/admin-menu.php (excerpt)

add_settings_field('banner_heading', 'Banner Heading:', 'banner_heading_setting', __FILE__, 'main_section');


We can pass a total of six arguments to this function, though we’ll only use five in this application:

  • $id: a unique ID for the attribute

  • $title: the title of the field; this will essentially be the label for the input

  • $callback: handles the creation of the input; from this function, we’ll echo out the necessary HTML

  • $page: the type of settings page to which this section should be applied; again, in this case, we use __FILE__ as the name

  • $section: the name of the section to which this field relates; we called ours main_section

  • $args (optional): any additional arguments, passed as an array

With this code, we’ve added a new settings field, and have declared banner_heading_section as the callback function that will handle the creation of the input. We can build that function now:

Example 9. theme/functions/admin-menu.php (excerpt)

// Banner Headingfunction banner_heading_setting() {  $options = get_option('plugin_options');  echo "<input name='plugin_options[banner_heading]' type='text' value='{$options['banner_heading']}' />";}


This code requires a bit of explanation. In its simplest form, the function echos out a textbox. We’ve given this input the name plugin_options[banner_heading], to allow us to add a new key to the plugin_options setting that we registered previously. Lastly, these inputs won’t always be blank. To remind the administrator which values are currently set in each option, we use the value attribute of the textbox to display the current value of this particular option.

Rendering the Inputs

We’ve successfully registered our plugin_options setting, and we’ve added a new field. Next, we must return to our form and designate where we want to insert each field, or input. Return to the build_options_page function, and modify the form like so:

Example 10. theme/functions/admin-menu.php (excerpt)

<form method="post" action="options.php" enctype="multipart/form-data">  <?php settings_fields('plugin_options'); ?>  <?php do_settings_sections(__FILE__); ?>  <p class="submit">    <input name="Submit" type="submit" class="button-primary" value="<?php esc_attr_e('Save Changes'); ?>" />  </p></form>


Notice that we’ve added two new functions:

  • settings_fields: This extremely helpful function will echo out all of the hidden fields for us, increasing the security of our plugin. Before the Settings API existed, we had to do this manually. This function accepts one parameter: the option_group name that was specified in the register_setting function. We called ours plugin_options.

  • do_settings_sections: This function cycles through all of the fields that we’ve created (only one so far) and displays the inputs accordingly. It also accepts one parameter, which should be equal to the unique page ID for our Options page.

If you return to the WordPress dashboard and click on the Theme Options page, you’ll see a form field for the banner heading, as shown in Figure 4, “Our new banner heading option”.

Figure 4. Our new banner heading option

Our new banner heading option


The textbox from the image above is blank because we haven’t yet set a value for the banner_heading option. As soon as we enter a value, click Save Changes, and refresh the page, it’ll display the current value of the option within the database.

Retrieving Option Values

Believe it or not, you now have a working Options page! It only has one option, but now, the process of adding more is amazingly simple. So let’s imagine that we applied a value of “My Awesome Banner Heading” to the plugin_options[banner_heading] field above. Your question might now be, “How do I retrieve this value in my theme?” Easy.

Anywhere within your theme template files, add this code:

1<?php $options = get_option('plugin_options'); ?><h2> <?php echo $options['banner_heading']; ?> </h2>

This code will output an h2 element, as shown in Figure 5, “Displaying the banner heading”.

Figure 5. Displaying the banner heading

Displaying the banner heading


Styling the Options Page

Our Options page works just fine in its current form, but let’s style it just a tad. To do so, we can use a mixture of WordPress’s native classes, as well as some of our own CSS. To reference a stylesheet from the Options page, we must listen for the admin_head hook—this action will run when the head section for the dashboard is created. This is where we’ll insert our stylesheet reference as follows:

Example 11. theme/functions/admin-menu.php (excerpt)

// Add stylesheetadd_action('admin_head', 'admin_register_head');function admin_register_head() {  $url = get_bloginfo('template_directory') . '/functions/options_page.css';  echo "<link rel='stylesheet' href='$url' />n";}


admin_register_head is the callback function that will handle the creation of our stylesheet. Assuming that we’ve added a new options-page.css stylesheet to the functions folder, we can echo out a link tag that points to that file.

Before we begin to edit our newly created stylesheet, let’s add to our page a few classes for which WordPress has, handily, already written the styling. Return to the build_options_page function, and add a class of widefat to the theme-options-page div:

Example 12. theme/functions/admin-menu.php (excerpt)

<div id="theme-options-wrap" class="widefat">


The form will now appear as shown in Figure 6, “The display after we add the widefat class”.

Figure 6. The display after we add the widefat class

The display after we add the widefat class


This helps a bit by adding some borders; however, it still needs work. In the new options-page.css file that we just created, add the following:

Example 13. theme/functions/options-page.css

#theme-options-wrap {  width: 700px;  padding: 3em;  background: white;  background: -moz-linear-gradient(top, #f4f2f2, white 20%, #f4f2f2 80%, white);  background: -webkit-gradient(linear, left top, left bottom, from(#f4f2f2), color-stop(.2, white), color-stop(.8, #f4f2f2), to(white));  border-top: 1px solid white;}#theme-options-wrap #icon-tools {  position: relative;  top: -10px;}#theme-options-wrap input, #theme-options-wrap textarea {  padding: .7em;}


With this additional CSS, the form will appear as shown in Figure 7, “The Options form with our new custom CSS”.

Figure 7. The Options form with our new custom CSS

The Options form with our new custom CSS


We can certainly take this much further, however, anything more would be beyond the scope of this tutorial. Here, we’ve just added a width, some padding, and a subtle background gradient for Mozilla and Webkit-based browsers.

Adding More Options

Now that we’ve successfully created a working Options form, we can flesh it out and add more fields. Let’s add a new section that allows the user of the theme to upload a logo.

The first step is to add a new add_settings_field to the register_and_build_fields function that we created:

Example 14. theme/functions/admin-menu.php (excerpt)

add_settings_field('logo', 'Logo:', 'logo_setting', __FILE__, 'main_section'); // LOGO


Next, we create the logo_setting callback function that we specified above. This handles the creation and echoing of the input:

Example 15. theme/functions/admin-menu.php (excerpt)

function logo_setting() {  echo '<input type="file" name="logo" />';}


That should do it (almost)—it’ll add a new field to the page. Remember, the do_settings_sections function cycles through all of the settings fields and renders them on the page. As you can see in Figure 8, “The file upload field for the logo option”, a file upload field is now displayed.

Figure 8. The file upload field for the logo option

The file upload field for the logo option


Uploading Files

Okay, I lied! True, we’ve added the input to the page, but we haven’t written the logic that will handle the process of uploading and saving the image so that it can be accessed.

Because we’re now uploading files from our form, we need to add the enctype attribute to the form:

Example 16. theme/functions/admin-menu.php (excerpt)

<form method="post" action="options.php" enctype="multipart/form-data">


This attribute determines how the form data will be encoded.

Continuing on, we need to perform a bit of validation, then upload the image to the server. If you’ll refer back to the register_setting definition, you’ll see it offers a third parameter that can handle any specific operations or sanitizing. We called ours validate_setting again:

Example 17. theme/functions/admin-menu.php (excerpt)

register_setting('plugin_options', 'plugin_options', 'validate_setting');


This is the perfect place to ensure that the user uploaded an image (and not, for example, an .exe file), and then upload the file to the server. Change your validate_setting function to reflect the following code:

Example 18. theme/functions/admin-menu.php (excerpt)

function validate_setting($plugin_options) { $keys = array_keys($_FILES); $i = 0; foreach ( $_FILES as $image ) {   // if a files was upload   if ($image['size']) {     // if it is an image     if ( preg_match('/(jpg|jpeg|png|gif)$/', $image['type']) ) {       $override = array('test_form' => false);       // save the file, and store an array, containing its location in $file       $file = wp_handle_upload( $image, $override );       $plugin_options[$keys[$i]] = $file['url'];     } else {       // Not an image.        $options = get_option('plugin_options');       $plugin_options[$keys[$i]] = $options[$logo];       // Die and let the user know that they made a mistake.       wp_die('No image was uploaded.');     }   }   // Else, the user didn't upload a file.   // Retain the image that's already on file.   else {     $options = get_option('plugin_options');     $plugin_options[$keys[$i]] = $options[$keys[$i]];   }   $i++; } return $plugin_options;}


This will be the most confusing section of our code. When a file is uploaded, it’s made available via the $_FILES superglobal variable. This array will contain information about the name, type, tmp_name, error, and size of the file. However, in this case, we can’t assume that only one file is being uploaded at a time. In fact, your Options page could include multiple file inputs. As such, we must use a foreach statement to filter through all of the items in the $_FILES array. Within the foreach statement, each uploaded image’s data will be represented via the $image array.

We first determine if the image’s size is a size greater than 0, to ensure that the user did upload a file.

Next, we perform a modest level of validation, to make sure that the user uploaded an image and not some other sort of file. Truthfully, in any other situation, this approach wouldn’t be adequate. A user could still change the extension of a file to, for instance, .png, and successfully upload their file. However, because this page is already limited to administrators only, this validation is meant more as a convenience for them. Should you require stronger protection for image uploads, use the getimagesize PHP function.

This preg_match function determines if the regular expression jpg|jpeg|png|gif is included in the image type. If it is, we can assume that, most likely, the user did upload an image.

Next, we call the wp_handle_upload WordPress function, which will take care of moving and saving the image to the uploads directory of your WordPress folder.

$file will now be equal to an array of data containing the information about the upload—most notably, the URL of the location where it was saved (accessed with $file['url']).

Lastly, we add a new key to our plugin_options option, and make it equal to a path to the image on the server.

That’s the basic overview of uploading images. Truthfully, this script does more than I’ve described, but, as this tutorial is specifically about creating an Options page, I don’t want to stray too far from that specific topic. We can always discuss this function more within this article’s comments if you wish.

With that out of the way, just like with the banner_heading option, we can access our logo from our theme files by writing this code:

<?php $options = get_option('plugin_options'); ?><img src="<?php echo $options['logo']; ?>" alt="Logo" />

Conclusion

If you’ve followed and worked along with this entire tutorial, give yourself a pat on the back. The most confusing part of the Settings API is memorizing all of its functions; however, once you do, you then have access to a great deal of power. For instance, we only created two options in this tutorial, but now, creating a new option will take you less than a minute!

So what could be the next step, if you want to take your Options page further? Try adding a layer of JavaScript to allow for the creation of new options dynamically, without having to return to your plugin file!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Anonymous

    Great tutorial!!!! Where can I download source?

  • http://www.inspiritblog.com Abhinav Sood

    Can you fix the broken image links in your post?

  • Alex

    The images on this page dont appear. Could you fix it? thanks

  • Seb

    Does this work in wp 3.2? I have the new options form working, but when i click save it just takes you to a blank screen a the address : http://localhost/wp1/wp-admin/options.php

    any ideas please?