Options and dashboard pages
The phone number in the header, a third-party service key, the number of products per page must not be written into the code: the site owner, not a programmer, will change them. WordPress stores such values in options and lets you add a dashboard page for them.
Reading and writing
update_option('mytheme_phone', '+1 202 555 0123');
$phone = get_option('mytheme_phone', '');
delete_option('mytheme_phone');The second argument of get_option() is the value used when the option does not exist yet. The value may be an array: WordPress stores it by itself.
Printing in a template
<?php
// header.php
$phone = get_option('mytheme_phone');
if ($phone) {
printf('<a href="tel:%s">%s</a>', esc_attr(preg_replace('/[^+\d]/', '', $phone)), esc_html($phone));
}A page in the dashboard
// functions.php
add_action('admin_menu', function () {
add_options_page(
'Theme settings',
'Theme',
'manage_options',
'mytheme',
'mytheme_render_settings',
);
});The arguments: the page title, the menu label, the required capability, the page slug and the function that prints it. The item appears under "Settings".
| Function | Where it adds the item |
|---|---|
add_options_page() | Under "Settings" |
add_menu_page() | In the main dashboard menu |
add_submenu_page() | In the submenu of any section |
add_theme_page() | Under "Appearance" |
Registering options
The Settings API takes care of saving and of checking the token and the rights:
add_action('admin_init', function () {
register_setting('mytheme', 'mytheme_phone', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
]);
add_settings_section('mytheme_contacts', 'Contacts', '__return_null', 'mytheme');
add_settings_field('mytheme_phone', 'Phone', function () {
printf(
'<input type="text" name="mytheme_phone" value="%s" class="regular-text">',
esc_attr(get_option('mytheme_phone', '')),
);
}, 'mytheme', 'mytheme_contacts');
});sanitize_callback cleans the value before it is saved.
The form
function mytheme_render_settings(): void
{
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form method="post" action="options.php">
<?php
settings_fields('mytheme');
do_settings_sections('mytheme');
submit_button();
?>
</form>
</div>
<?php
}settings_fields() prints the hidden fields with the token, do_settings_sections() the sections and fields, submit_button() the "Save" button. WordPress handles the form itself.
Your own check
register_setting('mytheme', 'mytheme_per_page', [
'type' => 'integer',
'default' => 12,
'sanitize_callback' => function ($value): int {
$value = absint($value);
return $value >= 1 && $value <= 100 ? $value : 12;
},
]);Autoloading
By default options are loaded on every page view — in one query, all at once. Large values that are rarely needed are saved without autoloading:
update_option('mytheme_import_log', $log, false);Common mistake
A dashboard page without a capability check is open to any logged-in user who knows its address. The third argument of add_options_page() — manage_options — closes it to everyone but administrators. If your own code handles the form rather than the Settings API, check both the current_user_can('manage_options') capability and the token with check_admin_referer() in it.