Settings API options page
A complete options page using register_setting, sections and fields with sanitise callbacks and a capability gate.
WordPressFormssettings-apioptionsadminsanitising
Code
<?php
/**
* Settings page: Settings -> Acme.
*
* @package Acme_Core
*/
defined( 'ABSPATH' ) || exit;
add_action( 'admin_menu', static function (): void {
add_options_page( __( 'Acme settings', 'acme-core' ), __( 'Acme', 'acme-core' ), 'manage_options', 'acme-settings', 'acme_settings_render' );
} );
add_action( 'admin_init', static function (): void {
register_setting( 'acme_settings_group', 'acme_options', array(
'type' => 'array',
'sanitize_callback' => 'acme_sanitize_options',
'default' => array( 'api_url' => '', 'per_page' => 10, 'enabled' => 0 ),
) );
add_settings_section( 'acme_main', __( 'General', 'acme-core' ), '__return_false', 'acme-settings' );
$fields = array(
'api_url' => array( __( 'API URL', 'acme-core' ), 'url' ),
'per_page' => array( __( 'Items per page', 'acme-core' ), 'number' ),
'enabled' => array( __( 'Enable feature', 'acme-core' ), 'checkbox' ),
);
foreach ( $fields as $key => [ $label, $type ] ) {
add_settings_field( $key, $label, 'acme_field_render', 'acme-settings', 'acme_main', array( 'key' => $key, 'type' => $type, 'label_for' => 'acme_' . $key ) );
}
} );
function acme_sanitize_options( $input ): array {
$input = is_array( $input ) ? $input : array();
return array(
'api_url' => esc_url_raw( $input['api_url'] ?? '' ),
'per_page' => min( 100, max( 1, absint( $input['per_page'] ?? 10 ) ) ),
'enabled' => empty( $input['enabled'] ) ? 0 : 1,
);
}
function acme_field_render( array $args ): void {
$opts = get_option( 'acme_options', array() );
$key = $args['key'];
$value = $opts[ $key ] ?? '';
$name = 'acme_options[' . $key . ']';
if ( 'checkbox' === $args['type'] ) {
printf( '<input type="checkbox" id="acme_%1$s" name="%2$s" value="1" %3$s>', esc_attr( $key ), esc_attr( $name ), checked( 1, (int) $value, false ) );
return;
}
printf( '<input type="%1$s" id="acme_%2$s" name="%3$s" value="%4$s" class="regular-text">', esc_attr( $args['type'] ), esc_attr( $key ), esc_attr( $name ), esc_attr( (string) $value ) );
}
function acme_settings_render(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
echo '<div class="wrap"><h1>' . esc_html( get_admin_page_title() ) . '</h1><form method="post" action="options.php">';
settings_fields( 'acme_settings_group' );
do_settings_sections( 'acme-settings' );
submit_button();
echo '</form></div>';
}
How it works
What it solves
Plugins and themes need configuration screens that behave like WordPress itself: nonce-protected, capability-gated, with errors shown in the standard place. The Settings API provides that, if the pieces are wired in the right order.
How it works
All values live in one array option. register_setting() attaches a sanitise callback, so every save passes through acme_sanitize_options, which cleans the URL, clamps the number and normalises the checkbox. A section and three fields are declared in a loop and rendered by one callback that escapes each attribute. The page callback prints settings_fields() (nonce and option group) and do_settings_sections().
WordPress itself handles the POST to options.php.
Adapting it
Add fields by extending the array and the sanitiser together. Use select and textarea types with extra branches in the renderer. Read values elsewhere with a helper such as get_option( 'acme_options' )['per_page'] ?? 10. For multisite network settings, use a network admin page and update_site_option.
Pitfalls
- A field that is missing from the sanitiser is silently dropped on save.
- Unchecked checkboxes send nothing, so default them to zero explicitly.
- Store secrets outside plain options when you can; options are readable by any plugin.
label_formust match the input id to associate the label.
How to use it
Place the file in inc/ and require it from your plugin or theme bootstrap.
Open Settings, then Acme, to edit values.
Read a value: $opts = get_option( 'acme_options' ); $per = (int) ( $opts['per_page'] ?? 10 );.
Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.