Custom Application Developer · Project Consultant · Database Engineer
In response to a previous article which covers custom WordPress plugin structure, How to Write a WordPress Plugin, a reader asked me the following question.
What are the advantages of using this approach then using add_settings_field() and add_settings_section()?
My previous article covers one method of adding a settings page to a plugin. In this article, I'm going to elaborate on WordPress's settings framework.
Class based plugins are easier to understand and maintain. In my previous tutorial I separated the custom post types into their own classes and files. In this example, I'm going to do the same for the plugin's settings. Create a settings.php file in your main plugin directory and add the following.
<?php if(!class_exists('WP_Plugin_Template_Settings')) { class WP_Plugin_Template_Settings { // Settings related functions go here } // END class WP_Plugin_Template_Settings } // END if(!class_exists('WP_Plugin_Template_Settings'))
In your main plugin's php file, add the following lines to the constructor.
<?php // Initialize Settings require_once(sprintf("%s/settings.php", dirname(__FILE__))); $WP_Plugin_Template_Settings = new WP_Plugin_Template_Settings();
Before all else, every setting that you register needs to be registered with WordPress. This is extremely straight forward, for each setting you wish to manage using the settings framework you must call register_setting (http://codex.wordpress.org/Function_Reference/register_setting).
<?php register_setting($option_group, $option_name, $sanitize_callback);
$option_group (string | required) is an identifier for grouping options. To keep things organized and simple I generally use the theme/plugin slug that the settings are used to manage. In the case of a plugin named "WP Plugin Template" I used an $option_group of "wp_plugin_template-group". The "-group" part of the identifier comes in handy when your plugin/theme/etc is more complicated and requires different groups of settings. As a general rule of thumb, if I want the settings fields to be separated into seperate form fieldsets I use seperate "-group" ids. ie: "wp_plugin_template-group_a" and "wp_plugin_template-group_b"
$option_name (string | required) is the name of the option I want to save. I don't get too fancy with these, use general variable naming convention. The option name should indicate what kind of value is stored in it.
$sanitize_callback (string | optional) a callback function for validation and sanitation. You can put any validation you want in this callback function, BUT the function must return a value of some kind. So if for the supplied value doesn't pass your validation rules, return an empty string return '';.
See the previous article, How to Write a WordPress Plugin, for where I generally register settings. As a general rule: hook into the admin_init action and do it there. see http://codex.wordpress.org/Plugin_API/Action_Reference/admin_init
<?php /* In your settings class */ public function __construct() { // register actions ... add_action('admin_init', array(&$this, 'admin_init')); ... } public function admin_init() { ... // register your plugins settings register_setting('wp_plugin_template-group', 'setting_a'); register_setting('wp_plugin_template-group', 'setting_b'); ... }
Your first real choice is the page that you want your admins to manage these settings. You can use an existing page such as "media" or "general" OR create a new page. In my plugin tutorial, I choose the latter and create a new settings type page.
Adding a new page is fairly easy, just hook into the admin_menu action and call a wrapper function for the type of submenu page you need. I generally like to use add_options_page. see http://codex.wordpress.org/Administration_Menus#Using_Wrapper_Functions) for a list of wrapper functions
<?php /* In your settings class */ public function __construct() { // register actions ... add_action('admin_menu', array(&$this, 'add_menu')); ... } /** * add a menu */ public function add_menu() { // Add a page to manage this plugin's settings add_options_page( 'WP Plugin Template Settings', // Page Title 'WP Plugin Template', // Menu Title 'manage_options', // required permission/capability 'wp_plugin_template', // menu slug array(&$this, 'plugin_settings_page') // callback ); } // END public function add_menu()
At this point if you are using a custom page to display your settings you could conceivably just echo html from your plugin_settings_page function and everything would be awesome. Except, you would have mixed template/HTML logic in with your application/plugin logic. Instead, lets include a template from our templates directory w/i the callback. That way the template logic is separate.
<?php /* In your settings class */ /** * Menu Callback */ public function plugin_settings_page() { if(!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } // Render the settings template include(sprintf("%s/templates/settings.php", dirname(__FILE__))); } // END public function plugin_settings_page()
Now you need a templates/settings.php file in order to render the content from plugin_settings_page. If you are NOT using add_settings_section and add_settings_field then you can simply add your desired HTML in this file like so.
<div class="wrap"> <h2>WP Plugin Template</h2> <form method="post" action="options.php"> <?php @settings_fields('wp_plugin_template-group'); ?> <?php @do_settings_fields('wp_plugin_template-group'); ?> <table class="form-table"> <tr valign="top"> <th scope="row"><label for="setting_a">Setting A</label></th> <td><input type="text" name="setting_a" id="setting_a" value="<?php echo get_option('setting_a'); ?>" /></td> </tr> <tr valign="top"> <th scope="row"><label for="setting_b">Setting B</label></th> <td><input type="text" name="setting_b" id="setting_b" value="<?php echo get_option('setting_b'); ?>" /></td> </tr> </table> <?php @submit_button(); ?> </form> </div>
With this you have a working settings page from the menu sup page you created. But what if you wanted to not write any form field HTML? What if you wanted to add your settings to an existing menu page? The next couple sections go over using add_settings_section and add_settings_field and the benefits/disadvantages of doing so.
A setting section is simply a logical grouping of settings. Earlier I wrote about grouping settings and using unique $group_names based on form fieldsets. Each Section needs a unique name as well. see http://codex.wordpress.org/Function_Reference/add_settings_section
<?php /* In your settings class */ public function __construct() { // register actions ... add_action('admin_init', array(&$this, 'admin_init')); ... } public function admin_init() { ... // register your settings section add_settings_section( 'wp_plugin_template-section', 'WP Plugin Template Settings', array(&$this, 'settings_section_wp_plugin_template'), // callback for this section 'wp_plugin_template' // or 'media' or 'wp_plugin_template' if you created a page with that $menu_slug in the previous step ); ... } /** * This callback provides a help-text like blurb above a settings section */ public function settings_section_wp_plugin_template() { // Think of this as help text for the section. echo 'These settings do things for the WP Plugin Template.'; } // END public function settings_section_wp_plugin_template()
Now you have registered settings and chosen/created a page to display your settings and created a settings section. The last step is to add fields to that section for each of your settings. see http://codex.wordpress.org/Function_Reference/add_settings_field
<?php /* In your settings class */ /** * hook into WP's admin_init action hook */ public function admin_init() { ... // add your setting's fields add_settings_field( 'wp_plugin_template-setting_a', // the unique name for this settings field 'Setting A', // title/label array(&$this, 'settings_field_input_text'), // Callback to render this field's input element 'wp_plugin_template', // page 'wp_plugin_template-section', // section array( 'field' => 'setting_a' ) // optional key/value pairs that get passed to the callback ); add_settings_field( 'wp_plugin_template-setting_b', 'Setting B', array(&$this, 'settings_field_input_text'), 'wp_plugin_template', 'wp_plugin_template-section', array( 'field' => 'setting_b' ) ); ... } // END public static function activate
Note: I'm using the $args array to make this callback reusable for all of my input type="text" fields. You could do a separate callback for each settings field, this is a tiny bit neater.
<?php /* In your settings class */ /** * This function provides text inputs for settings fields */ public function settings_field_input_text($args) { // Get the field name from the $args array $field = $args['field']; // Get the value of this setting $value = get_option($field); // echo a proper input type="text" echo sprintf('<input type="text" name="%s" id="%s" value="%s" />', $field, $field, $value); } // END public function settings_field_input_text($args)
templates/settings.php?Now that you are using settings fields and sections you can refactor your settings page template to not have field related markup. see http://codex.wordpress.org/do_settings_sections
<div class="wrap"> <h2>WP Plugin Template</h2> <form method="post" action="options.php"> <?php @settings_fields('wp_plugin_template-group'); ?> <?php @do_settings_fields('wp_plugin_template-group'); ?> <?php do_settings_sections('wp_plugin_template'); ?> <?php @submit_button(); ?> </form> </div>
If you decided to not create a sub menu page for your settings and use and existing page such as general or media, then you do not need a templates/settings.php template. Instead the page will automatically render your settings section at the bottom of the page after all of the existing sections.
settings.php Listing<?php if(!class_exists('WP_Plugin_Template_Settings')) { class WP_Plugin_Template_Settings { /** * Construct the plugin object */ public function __construct() { // register actions add_action('admin_init', array(&$this, 'admin_init')); add_action('admin_menu', array(&$this, 'add_menu')); } // END public function __construct /** * hook into WP's admin_init action hook */ public function admin_init() { // register your plugin's settings register_setting('wp_plugin_template-group', 'setting_a'); register_setting('wp_plugin_template-group', 'setting_b'); // add your settings section add_settings_section( 'wp_plugin_template-section', 'WP Plugin Template Settings', array(&$this, 'settings_section_wp_plugin_template'), 'wp_plugin_template' ); // add your setting's fields add_settings_field( 'wp_plugin_template-setting_a', 'Setting A', array(&$this, 'settings_field_input_text'), 'wp_plugin_template', 'wp_plugin_template-section', array( 'field' => 'setting_a' ) ); add_settings_field( 'wp_plugin_template-setting_b', 'Setting B', array(&$this, 'settings_field_input_text'), 'wp_plugin_template', 'wp_plugin_template-section', array( 'field' => 'setting_b' ) ); // Possibly do additional admin_init tasks } // END public static function activate public function settings_section_wp_plugin_template() { // Think of this as help text for the section. echo 'These settings do things for the WP Plugin Template.'; } /** * This function provides text inputs for settings fields */ public function settings_field_input_text($args) { // Get the field name from the $args array $field = $args['field']; // Get the value of this setting $value = get_option($field); // echo a proper input type="text" echo sprintf('<input type="text" name="%s" id="%s" value="%s" />', $field, $field, $value); } // END public function settings_field_input_text($args) /** * add a menu */ public function add_menu() { // Add a page to manage this plugin's settings add_options_page( 'WP Plugin Template Settings', 'WP Plugin Template', 'manage_options', 'wp_plugin_template', array(&$this, 'plugin_settings_page') ); } // END public function add_menu() /** * Menu Callback */ public function plugin_settings_page() { if(!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } // Render the settings template include(sprintf("%s/templates/settings.php", dirname(__FILE__))); } // END public function plugin_settings_page() } // END class WP_Plugin_Template_Settings } // END if(!class_exists('WP_Plugin_Template_Settings'))
#1 - Wow, that was more answer than I could ever hope for, very impressed! As I'm still learning to write plugins I prefer to let WordPress handle the form field HTML as much as possible (less risk of me messing it up), so this article is the answer I was looking for when I asked the question, especially the section above about refactoring the settings template. Thank you!
Adrian B on Feb. 17, 2013
#2 - Hey, thank you for your article. Im experimenting with settings api and I think its a great alternative to writing code - handling data, manually. I prefer to store my data in the database as an array , in a single row. Currently you example stores data in 2 separate rows. ATM Im not quite sure how to modify the code to meet those requirement and was wondering if you could elaborate?
Andrey on March 26, 2013
#3 - Hello, I really liked you post on creating a plugin. I am working on a plugin and I would like to add a media button to upload a logo. What I am stuck on is creating a version of ´public function settings_field_input_text($args)´ for the upload button. Can you help me here?
grappler on March 30, 2013
#4 - Just curious why are you sprintf'ing the dirname?
zane on April 20, 2013
#5 - Thanks for the article. Could you put the raw code up somewhere? Copy/paste from this page loses all the line breaks in the code. Thanks!
Jason Lewis on April 30, 2013
#6 - @Jason, All of the code for this is here: https://github.com/fyaconiello/wp_plugin_template
Francis Yaconiello on May 3, 2013
#7 - @Zane, sprintf is a little cleaner than concatenation, and I use dirname(__FILE__) to make paths absolute relative to the current file. Its just a convention I use. I fell into it because it always works cross system and makes my code more easily portable.
Francis Yaconiello on May 3, 2013
#8 - grappler, So if you want to create a file field setting instead of a text input setting, you need to call register_setting for that setting with a callback function, that in turn calls wp_handle_upload. Then you need to create a custom function for showing file upload fields. Adapt settings_field_input_text to a function called settings_field_input_file. In that function, display an <input type="file"... and if an image has been uploaded, also display <img href="<?php echo get_option("SETTING_NAME"); ?>"... Let me know if you need more help. see: see: http://wpquestions.com/question/show/id/1339 for an example.
Francis Yaconiello on May 3, 2013
#9 - Andrey, In WordPress's register_setting function there is an optional callback function argument. see: http://codex.wordpress.org/Function_Reference/register_setting Specify a callback to call a function that takes the various values you want stored together, serializes those values, and returns them. If specified, whatever gets returned from the callback function gets stored in the database. Then all you need to do is deserialize the setting value before using it. Also, you really should consider not storing your settings this way, its somewhat inefficient given how WP stores and retrieves options data.
Francis Yaconiello on May 3, 2013
#10 - Thank you! But can you help me figure out how to set default options? I've seen sample code that uses wp_parse_args but I can't figure out how to integrate that with the code I copied from you. Thank you!
Gil Reich on May 8, 2013
#11 - @Gil Rech - according to the docs, when you access the setting via get_option, call get_option("setting_name", "my default value") http://codex.wordpress.org/Function_Reference/get_option, if you need more let me know.
Francis Yaconiello on May 8, 2013
#12 - Thanks. But the get_option call is in the settings_field_input_text callback function where I don't know the field or the default. Wait -- Just got it. OK, In the add_settings_field function I added a line to pass the default value. The bottom of the function now reads like this: array( 'field' => 'setting_a', 'default' => 'true' ) And I changed the setting_field_input_text function so that it starts with this: $field = $args['field']; $default = $args['default']; // Get the value of this setting $value = get_option($field, $default);
Gil Reich on May 8, 2013
#13 - Sorry, I was trying to format the code in that post and it posted before i was ready. And took out the line breaks. Nonetheless, I now have working code that uses your code but also passes a default. Involved adding default as a parameter to the callback function, adding that parameter to the array that creates the call to the callback function, and then passing that parameter in the get_option function. And as a bonus I now understand your code (and php and WordPress plugins) much better. Thank you again for your help.
Gil Reich on May 8, 2013
Fill out the form below to start a discussion.