Contents
Overview
This tutorial explains, in complete detail, how to create an admin options page in WordPress using the Settings API in PHP. It covers concepts, required functions, a step-by-step example plugin that registers an options page, registers settings/sections/fields, sanitizes input, uses the WordPress color picker and media uploader, displays settings errors, and demonstrates best practices for security and compatibility.
Why use the Settings API?
- Security and consistency: The Settings API automatically handles nonces and options storage patterns when used correctly.
- Separation of concerns: You register settings and field callbacks and let WordPress output form markup with settings_fields() and do_settings_sections().
- Built-in validation hooks: register_setting() accepts a sanitize callback for centralized validation and sanitization.
- Scalability: Settings can be grouped into sections, and many fields can be added systematically.
Key Settings API functions and terminology
register_setting | Register an option name, optional sanitize callback, and associates it with a settings group. |
add_settings_section | Define a logical section on the options page (title and description). |
add_settings_field | Register a specific field (label and callback) associated with a section. |
settings_fields | Outputs hidden fields including the nonce and option_group value. Place inside your form. |
do_settings_sections | Outputs all sections and fields registered for a given settings page. |
submit_button | Outputs the standard Save Changes button. |
get_option | Retrieve the saved option(s). |
add_settings_error settings_errors | Programmatically add and display admin notices for settings validation, success, or errors. |
Prerequisites and notes
- WordPress 4.7 is recommended Settings API has been available for many versions, but APIs like the color picker and media uploader rely on current admin scripts.
- The user managing settings should have the capability to manage_options (typically administrators).
- Always sanitize and escape data. register_setting()s sanitize callback should return sanitized data or use add_settings_error() for invalid values.
- Use esc_attr(), esc_html(), esc_textarea(), checked(), selected() when outputting to HTML.
Step-by-step example: Complete plugin demonstrating Settings API
Below is a complete example plugin that:
- Registers an options page under Settings.
- Registers one option (an array) with default values, a sanitize callback, one section, and a variety of field types (text, textarea, checkbox, select, radio, color, image upload).
- Enqueues necessary admin scripts for color picker and media uploader.
- Shows how to read options with get_option() and output safely.
lt?php / Plugin Name: Example Settings API Options Page Description: Demonstrates creating an options page with the WordPress Settings API Version: 1.0 Author: Example Author / if ( ! defined( ABSPATH ) ) { exit } class Example_Settings_API_Plugin { private option_name = example_settings_api_options private defaults = array( text_field =gt , textarea =gt , checkbox =gt 0, select =gt option1, radio =gt yes, color =gt #ffffff, image_id =gt 0, ) public function __construct() { add_action( admin_menu, array( this, add_admin_menu ) ) add_action( admin_init, array( this, register_settings ) ) add_action( admin_enqueue_scripts, array( this, enqueue_admin_assets ) ) } public function add_admin_menu() { // Add an options page under Settings add_options_page( Example Settings, Example Settings, manage_options, example-settings-api, array( this, options_page_html ) ) } public function register_settings() { // Register the option with a sanitize callback register_setting( example_settings_group, // option group (settings_fields() uses this) this->option_name, // option name in wp_options array( this, sanitize_callback ) // sanitize callback ) // Add a section add_settings_section( example_main_section, Main Settings, array( this, section_text ), example-settings-api ) // Add fields (each fields callback outputs the input HTML) add_settings_field( text_field, Text Field, array( this, render_text_field ), example-settings-api, example_main_section ) add_settings_field( textarea, Text Area, array( this, render_textarea_field ), example-settings-api, example_main_section ) add_settings_field( checkbox, Enable Option, array( this, render_checkbox_field ), example-settings-api, example_main_section ) add_settings_field( select, Select Choice, array( this, render_select_field ), example-settings-api, example_main_section ) add_settings_field( radio, Radio Choice, array( this, render_radio_field ), example-settings-api, example_main_section ) add_settings_field( color, Color Picker, array( this, render_color_field ), example-settings-api, example_main_section ) add_settings_field( image, Image Upload, array( this, render_image_field ), example-settings-api, example_main_section ) } public function enqueue_admin_assets( hook ) { // Only enqueue on our options page if ( hook !== settings_page_example-settings-api ) { return } // Color picker wp_enqueue_style( wp-color-picker ) wp_enqueue_script( wp-color-picker ) // Media uploader wp_enqueue_media() // Our inline script to instantiate color picker and media uploader script = jQuery(document).ready(function(){ // Color picker (.example-color-field).wpColorPicker() // Media uploader for image field var file_frame (document).on(click, .example-upload-button, function(e){ e.preventDefault() var button = (this) var input = button.prev(.example-image-id) var preview = button.siblings(.example-image-preview) if (file_frame) { file_frame.open() return } file_frame = wp.media.frames.file_frame = wp.media({ title: Select Image, button: { text: Use this image }, multiple: false }) file_frame.on(select, function() { var attachment = file_frame.state().get(selection).first().toJSON() input.val(attachment.id) if (preview.length) { preview.attr(src, attachment.sizes attachment.sizes.thumbnail ? attachment.sizes.thumbnail.url : attachment.url) } }) file_frame.open() }) (document).on(click, .example-remove-image, function(e){ e.preventDefault() var button = (this) var input = button.prevAll(.example-image-id) var preview = button.siblings(.example-image-preview) input.val() if (preview.length) { preview.attr(src, ) } }) }) wp_add_inline_script( wp-color-picker, script ) } public function section_text() { echoConfigure the example plugin settings below.
} private function get_options_with_defaults() { options = get_option( this->option_name, array() ) if ( ! is_array( options ) ) { options = array() } return wp_parse_args( options, this->defaults ) } // Field render callbacks public function render_text_field() { options = this->get_options_with_defaults() echo ltinput type=text name= . esc_attr( this->option_name ) . [text_field] value= . esc_attr( options[text_field] ) . class=regular-text /> echo ltp class=description>A single-line text field. } public function render_textarea_field() { options = this->get_options_with_defaults() echo lttextarea name= . esc_attr( this->option_name ) . [textarea] rows=6 cols=50> . esc_textarea( options[textarea] ) . lt/textarea> echo ltp class=description>A multi-line textarea. } public function render_checkbox_field() { options = this->get_options_with_defaults() echo ltlabel> echo ltinput type=checkbox name= . esc_attr( this->option_name ) . [checkbox] value=1 . checked( 1, options[checkbox], false ) . /> echo nbspEnable this option echo lt/label> echo ltp class=description>A boolean checkbox. } public function render_select_field() { options = this->get_options_with_defaults() echo ltselect name= . esc_attr( this->option_name ) . [select]> echo ltoption value=option1 . selected( option1, options[select], false ) . gtOption 1lt/option> echo ltoption value=option2 . selected( option2, options[select], false ) . gtOption 2lt/option> echo ltoption value=option3 . selected( option3, options[select], false ) . gtOption 3lt/option> echo lt/select> } public function render_radio_field() { options = this->get_options_with_defaults() echo ltlabel>ltinput type=radio name= . esc_attr( this->option_name ) . [radio] value=yes . checked( yes, options[radio], false ) . /> Yeslt/label>nbspnbsp echo ltlabel>ltinput type=radio name= . esc_attr( this->option_name ) . [radio] value=no . checked( no, options[radio], false ) . /> Nolt/label> } public function render_color_field() { options = this->get_options_with_defaults() echo ltinput type=text name= . esc_attr( this->option_name ) . [color] value= . esc_attr( options[color] ) . class=example-color-field /> echo ltp class=description>Use the color picker to choose a hex color. } public function render_image_field() { options = this->get_options_with_defaults() image_id = intval( options[image_id] ) image_src = image_id ? wp_get_attachment_image_url( image_id, thumbnail ) : echo ltinput type=hidden name= . esc_attr( this->option_name ) . [image_id] class=example-image-id value= . esc_attr( image_id ) . /> echo ltimg src= . esc_attr( image_src ) . alt= class=example-image-preview style=max-width:120px display:block margin-bottom:8px /> echo ltbutton class=button example-upload-button>Upload/Choose Imagelt/button> echo ltbutton class=button example-remove-image>Removelt/button> echo ltp class=description>Select an image from the Media Library. } // Sanitize callback: validate and sanitize input before saving public function sanitize_callback( input ) { output = array() // Ensure input is an array if ( ! is_array( input ) ) { add_settings_error( example_settings_api, invalid_input, Invalid input format., error ) return this->defaults } // text_field if ( isset( input[text_field] ) ) { output[text_field] = sanitize_text_field( input[text_field] ) if ( strlen( output[text_field] ) gt 100 ) { add_settings_error( example_settings_api, text_too_long, Text field cannot exceed 100 characters., error ) output[text_field] = substr( output[text_field], 0, 100 ) } } else { output[text_field] = this->defaults[text_field] } // textarea if ( isset( input[textarea] ) ) { output[textarea] = wp_kses_post( input[textarea] ) } else { output[textarea] = this->defaults[textarea] } // checkbox (stored as 1 or 0) output[checkbox] = isset( input[checkbox] ) intval( input[checkbox] ) === 1 ? 1 : 0 // select (validate allowed values) allowed_selects = array( option1, option2, option3 ) output[select] = in_array( input[select], allowed_selects, true ) ? input[select] : this->defaults[select] // radio allowed_radios = array( yes, no ) output[radio] = in_array( input[radio], allowed_radios, true ) ? input[radio] : this->defaults[radio] // color: validate as hex color (#rrggbb or #rgb) if ( isset( input[color] ) ) { color = trim( input[color] ) if ( preg_match( /^#([A-Fa-f0-9]{6}[A-Fa-f0-9]{3})/, color ) ) { output[color] = color } else { add_settings_error( example_settings_api, invalid_color, Invalid color value. Please provide a valid hex color., error ) output[color] = this->defaults[color] } } else { output[color] = this->defaults[color] } // image_id: ensure its an integer and attachment exists image_id = isset( input[image_id] ) ? intval( input[image_id] ) : 0 if ( image_id ) { mime = get_post_mime_type( image_id ) if ( mime strpos( mime, image/ ) === 0 ) { output[image_id] = image_id } else { add_settings_error( example_settings_api, invalid_image, Selected file is not a valid image., error ) output[image_id] = 0 } } else { output[image_id] = 0 } // After sanitization, add a success message if no settings errors if ( ! get_settings_errors() ) { add_settings_error( example_settings_api, settings_updated, Settings saved., updated ) } return output } // Options page HTML public function options_page_html() { if ( ! current_user_can( manage_options ) ) { return } // Show settings errors and messages settings_errors( example_settings_api ) echo ltdiv class=wrap> echo lth2>Example Settingslt/h2> echo ltform method=post action=options.php> // Output nonce, option_page and other hidden fields for the registered setting group settings_fields( example_settings_group ) // Output settings sections and fields do_settings_sections( example-settings-api ) // Submit button submit_button() echo lt/form> echo lt/div> } } new Example_Settings_API_Plugin() ?gt
How the code above is organized (explanation)
- Constructor hooks into admin_menu, admin_init, and admin_enqueue_scripts.
- add_admin_menu() registers the options page (add_options_page).
- register_settings() calls register_setting(), add_settings_section(), and add_settings_field() for each field. register_setting ties a sanitize callback to the option.
- Field render callbacks output the actual HTML for each field. They retrieve options with get_option() and provide sensible defaults using wp_parse_args().
- sanitize_callback() centralizes validation and sanitization. Use WP functions like sanitize_text_field(), wp_kses_post(), intval(), and regex validation for color values. Use add_settings_error() to provide feedback on validation problems.
- options_page_html() builds the HTML form using settings_fields() to output the necessary nonces and option group hidden fields, do_settings_sections() to output sections and their fields, and submit_button() to provide the Save Changes button. settings_errors() prints messages created via add_settings_error().
- enqueue_admin_assets() enqueues the WP color picker and Media Library scripts/CSS, and registers an inline script to initialize them only on this admin page.
How the Settings API flow works on form submit
- User submits the form on the options page.
- WordPress verifies the nonce generated by settings_fields() and user capability.
- WordPress calls the sanitize callback registered with register_setting(), passing the raw posted data.
- Sanitize callback should return the sanitized/validated data (or defaults). You can use add_settings_error() within sanitize callback to report issues.
- WordPress updates the option in the database (wp_options) when sanitize callback returns the value(s).
- On page reload settings_errors() displays any messages you added.
Accessing and using saved settings in theme or plugin code
Retrieve saved options with get_option(). The example stores one option as an array under the key example_settings_api_options. Always provide defaults and escape output for safety.
options = get_option( example_settings_api_options, array() ) options = wp_parse_args( options, array( text_field =gt , color =gt #ffffff, // other defaults... ) ) // Use values safely: echo ltdiv style=background: . esc_attr( options[color] ) . > echo lth2> . esc_html( options[text_field] ) . lt/h2> echo lt/div>
Common field types and their output/escape patterns
- Text input: use esc_attr() for the value attribute.
- Textarea: use esc_textarea() for contents.
- Checkbox: saved as 1 or 0 use checked( 1, value ) when rendering.
- Select/radio: validate allowed values in sanitize callback use selected()/checked() for rendering.
- Color: store as a hex string validate via regex in sanitize callback use esc_attr() on output.
- Image uploads: store attachment ID verify MIME and existence on sanitize get image URL with wp_get_attachment_image_url() and escape with esc_url().
Using settings_errors() and add_settings_error()
add_settings_error() allows you to add error or update notices programmatically. Pass a unique slug, error code, message, and type (error or updated). Call settings_errors( setting_slug ) on your options page to display any messages for that slug.
Best practices and tips
- Use a single option array (recommended for many related settings). It reduces DB rows and simplifies defaults handling. But for big settings or when other plugins/themes should use separate options, separate option names are acceptable.
- Sanitize centrally with the sanitize callback of register_setting(). Validate all fields and do not trust POSTed data.
- Escape at output time with the appropriate escaping function. Sanitize for storage, escape for output.
- Capability checks: Ensure only authorized users can view and save settings (manage_options or other capability appropriate to your plugin).
- Enqueue assets conditionally: Only load scripts/styles (color picker, media uploader) on your settings page.
- Messages: Use add_settings_error() for storing warnings/errors from sanitize callback and show them with settings_errors().
- Defaults and migrations: Provide defaults with wp_parse_args() and handle migrations if you rename option keys in future plugin versions.
- Nonces: settings_fields() outputs required nonces — do not try to handle them manually for options registered via Settings API.
Troubleshooting common issues
- Fields not saving: Ensure option_group in settings_fields() matches the group used in register_setting(). Also confirm register_setting() is called on admin_init (not too late) and that your option name matches the input name attribute.
- Nonces missing / permissions errors: Verify settings_fields() is present in your form and the user has required capability (manage_options by default).
- Values sanitized away unexpectedly: Check your sanitize callback and ensure you are returning the final sanitized value. If your callback returns null or an empty string unintentionally, values will be removed.
- Scripts not loading: Confirm admin_enqueue_scripts only enqueues assets on the correct hook. The hook value for options pages under Settings is settings_page_{menu_slug}.
Advanced topics (brief)
- Repeatable fields / arrays: Store repeating fields as nested arrays and sanitize each row in the sanitize callback. Use JS to add/remove rows in the admin UI handle array keys carefully in field names (e.g., name=my_option[field][0][subfield]).
- REST API integration: You can expose options via custom REST endpoints if needed, but avoid exposing sensitive data publicly.
- Multisite: If building for multisite, you may need to use get_site_option() / update_site_option() for network-wide options and handle capability checks for network admins.
- Settings sections on multiple pages: The (page) parameter of add_settings_section and add_settings_field ties fields to specific admin pages reuse sections across pages by using the same page slug.
References
- WordPress Developer Reference: Settings API
- register_setting()
- add_settings_section()
- add_settings_field()
- settings_fields()
Summary
The Settings API provides a structured, secure, and extensible way to add settings and options pages in WordPress. The recommended approach is to register settings and fields using register_setting(), add_settings_section(), and add_settings_field(), output the form with settings_fields() and do_settings_sections(), and sanitize input in the sanitize callback. Enqueue assets only on your page, validate and escape everything, and use add_settings_error() to communicate validation and success messages to the user.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |