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() {
echo Configure 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 🙂 |
