The inter-tubes are awash in tutorials for how to add Theme Options to WordPress Themes - so why write another? Primarily, because most such tutorials are several years old, don't implement current best-practices, and were written without any awareness of the WordPress Settings API.
While others such as Otto and Ozh have done yeomen's work in explaining how to implement the Settings API, I have not yet come across anything that really put everything together, and explained the process and implementation from beginning to end, in a way that even the less-experienced Theme developers (like me) could easily understand.
This tutorial will attempt to fill that gap, by providing examples of current (as of the pending release of WordPress 3.1) best-practice implementation, not merely of the Settings API, but of Theme Options implementation as a whole, including:
- Registering options in the database as a single options array
- Initializing default options
- Creating a single Theme Settings page (with tabs)
- Defining settings page sections and fields
- Validating and white-listing user-input form data
- Adding Settings Page contextual help
- Enqueueing custom CSS for the Settings page
- Implementing settings in the Theme template files
- Enqueueing front-end CSS
Throughout this tutorial, I will be using code from my Oenology Theme for implementation examples. For full code, see the latest development version.
Assumptions: Best Practices
The following will be the working list of best practices that will be incorporated:
- Theme Settings defined as an options array in a single database entry
- Theme settings added to a single Theme Settings Page
- Theme Settings page is added to the "Appearance" menu
- Theme Settings page added using the "edit_theme_options" user capability
- Theme Settings registered, updated, and validated using the WordPress Settings API
- Theme Settings modify the template using action/filter hooks wherever possible
Getting Started
The first thing we need to do, even before touching any code, is to determine:
- What options to include in the Theme
- How to organize those options on the Theme Settings Page
In my case, I am adding only a handful of simple options:
- Header Navigation Menu Position: currently, Oenology displays the Header Navigation Menu above the site title and description. I am adding a setting optionally to display the Header Navigation Menu below the site title and description.
- Header Navigation Menu Depth: currently, Oenology is designed so that only the top-level Pages display in the Header Navigation Menu, and Child Pages display in a left-column sub-menu. I am adding a setting optionally to display Pages to a depth of one (top-level Pages only), two, or three, with hover drop-down menus.
- Footer Credit Link: currently, Oenology does not display any form of footer credit link. I am adding a setting optionally to display a footer credit link.
- Varietals: the default style of Oenology is intentionally minimal. It is intended to be clean, simple, and cross-browser. I am adding a setting optionally to select from among Theme "skins" (which, in keeping with the oenology metaphor, will be called "varietals"), which will apply different color/style schemes to the Theme.
Given that I'm only adding four Theme options, I could very easily put all four together on one Settings page. However, I may want to add additional options in the future - and also, I want to provide a proof-of-concept for creating complex Theme Settings pages in a way that supports the Settings API. So, the Theme Settings page will have two tabs: "General", and "Varietals". Further, the "General" tab will have two sections: "Header Options" and "Footer Options".
So, that's our basis. Let's get started!
Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress
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.
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
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: ple …
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
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: ple …
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: ple …
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: ple …
RT @chip_bennett: Incorporating the Settings API in WordPress Themes – http://www.chipbennett.net/2011/02/17/in… #wordpress – I don’t do this often, but: ple …
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
@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.
huge thanks for this. shines a big beam of light on the whole process.
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.
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!
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?
@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).
@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.
@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.
Thanks, very useful tutorial!
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
@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. 😉
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;
}
@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
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
oops my code was cut off here it is
echo theme_get_option('display_footer_credit');
Thank you for this detailed tutorial, very helpful! One thing I would like to know is if it is possible to do an ajax save when using the
Settings API
. I know how to do this when using standard options, but I have not come across a way that shows how to do this with the settings API.Thanks for your help!
Thijs.
Hello Chip.
Great tutorial. I followed example and created options page, but I can’t save options. Any idea what can cause this?
Thanks
George
Hi Chip, I know it’s asking too much but is it possible to make a version of this tutorial which does things from scratch instead of lifting code from the Oenology Theme? That way other developers (like myself) can easily integrate it into their own themes since the one here is a bit confusing.
@enchance:
I’m not sure how I could make a more from-scratch tutorial. I added options to Oenology specifically with creating this tutorial in mind.
What would you like to see done differently?
(test comment)
Thanks for your reply.
It started to get pretty confusing by the 5th page and was a bit overwhelmed when a looked at options-register-general.php and options-register-varietal.php files. You’re really a master at this and I commend you for your skill.
Maybe you can write a “Lite” version of the tutorial which people can build on? Something from the ground up would really be best, that way it’s easier to follow and can be ported to any theme.
A “Lite” version is a good idea. I’ve thought of doing something like that, for implementing less-complex Theme Options schemes.
Let me see what I can do!
(Note: I wrote the tutorial with the assumption that the reader could easily follow the files, via the GitHub repository. I setup the file structure intentionally, so that anyone using the Oenology files as an example/reference while developing a Theme could easily follow what was happening.)
Great! I’ll be looking forward to it.
Hi Chip,
John P. Bloch is correct in his comment: When using the Settings API, the form action goes to options.php which checks ‘manage_options’ (and in MultiSite also checks is super admin). The capability check in the add_*_page functions is only for whether to show the page in the menu. (This is because you can add a page and post to itself, handling the cap checks manually.) So basically, when using the Settings API, the checks are particularly strict. A user with ‘edit_theme_options’ but not ‘manage_options’ will see the page, but on submit, will get the “Cheatin’ Eh?” error message.
This is a super tutorial, Thanks Chip, I picked up quite a bit!
WraithKenny (Ken Newman)
In case you’re already writing the “Lite” version of this post, what really differentiates this from the other posts is how it shows you how to insert tabs to your theme settings using the latest Settings API.
Hi! Great tutorial, thanks so much for doing it 🙂 It certainly shed some light on how the Settings API works, which was anything but obvious…
I never managed to get checkboxes working properly until I saw this simpler tutorial. Whew, checkboxes are always a nightmare! Using the Options API I used to avoid them like the plague…
A stupid question… when using the code for emitting the “Settings saved” banner, I got it displayed… twice. So I’ve just deleted that bit of code and tested it on a different site as well. Apparently, the Settings API already deals with this! Now my question is, if I want to personalise that banner somehow (saying, for instance, what settings have been changed…) how would I change it? Is there a registration function for this? I see there is an (undocumented)
add_settings_error()
function, I wonder what it does?Anyway, thanks so much for pointing me in the right direction!
Very thorough and informative tutorial. Guided me nicely as I coded my first Theme Settings page. Thank you very much, Mr Bennett.
Nice tutorial, I’ve tried it. Is it permissible to
apply for my free theme?
Hey Chip,
I’ve used your tutorial for adding a reset option to my Theme Options, which works great! I just have one issue now with submitting textarea fields.
Here is what is happening. I can add in HTML in the text field fine. But, let’s say I want to remove the HTML and then save. My code must not be quite right, because whenever I save, it reverts back to the code that was in there before. Here is what I have to validate the input:
$validated_input['inline_header_scripts'] = ( '' != $input['inline_header_scripts'] ? trim( $input['inline_header_scripts'] ) : $validated_input['inline_header_scripts'] );
What do I need to change to make sure I can do this?
Thanks in advance!
On a side note, how can I set this textarea to strip out any text that is not within an HTML tag? I haven’t figured that one out yet 🙂
Thanks for writing this great tutorial. It is about time the new best practise examples and tuts surface in search engines. Where is the tip jar?!
Best, Jan
I was looking for a tutorial using add_settings_error() in the validation function. Looks like it’s not possible with the Settings API. I guess I will validate also client side to display errors in the form.
Your tutorial is great.
Hello Chip,
I am having difficulty adding image upload fields to these forms. Do you know of a tutorial that covers this?
Chip,
Thanks very much for this very in depth tutorial!
But, I’ve been trying to find an answer to the issue that John Block mentioned about the superadmin user role and the Settings API. What wasn’t answered was what the workaround is for this issue.
I’m trying to create a theme for multisite based off of TwentyEleven and I want users to be able to add their personal information on a theme options page, which will them populate into a page template, basically an about me page. None of my users will be superadmins.
What would be the best way to get around this issue?
Jim
This is extremely detailed and all a bit over my head. All I want is a drop down menu on one of my navigation tabs, like you are implementing at the top of your page. Is there an easier way to go about this?
Thanks.
Absolutely possible! Simply omit the tab-based code in your settings page markup, and in the sanitization callback, etc.