Learning Objectives
- Understand the WordPress Settings API
- Create admin menu pages and subpages
- Register settings with proper validation
- Build custom settings sections and fields
- Save and retrieve theme options
- Create tabbed settings pages
- Implement field validation and sanitization
- Use WordPress nonces for security
Understanding Theme Options
Theme options allow users to configure various aspects of their theme through an admin interface. While the Customizer is preferred for visual settings, admin pages are better for complex configurations.
When to Use What
Settings API Structure
1
Create Admin Page
add_menu_page() or add_submenu_page()
2
Register Settings
register_setting() with validation callback
3
Add Sections
add_settings_section() for grouping fields
4
Add Fields
add_settings_field() for individual options
Creating Admin Pages
Add Theme Options Page
<?php
/**
* Add Theme Options Menu
*/
function mytheme_add_admin_menu() {
add_menu_page(
__( 'Theme Options', 'mytheme' ), // Page title
__( 'Theme Options', 'mytheme' ), // Menu title
'manage_options', // Capability
'mytheme-options', // Menu slug
'mytheme_options_page', // Callback function
'dashicons-admin-generic', // Icon
61 // Position
);
// Add submenu pages
add_submenu_page(
'mytheme-options', // Parent slug
__( 'General Settings', 'mytheme' ), // Page title
__( 'General', 'mytheme' ), // Menu title
'manage_options', // Capability
'mytheme-options', // Menu slug (same as parent for first submenu)
'mytheme_options_page' // Callback
);
add_submenu_page(
'mytheme-options',
__( 'Advanced Settings', 'mytheme' ),
__( 'Advanced', 'mytheme' ),
'manage_options',
'mytheme-advanced',
'mytheme_advanced_page'
);
}
add_action( 'admin_menu', 'mytheme_add_admin_menu' );
/**
* Options Page Callback
*/
function mytheme_options_page() {
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
// Output security fields
settings_fields( 'mytheme_options_group' );
// Output setting sections and fields
do_settings_sections( 'mytheme-options' );
// Output save button
submit_button( __( 'Save Settings', 'mytheme' ) );
?>
</form>
</div>
<?php
}
add_menu_page()
Creates top-level menu item
add_menu_page($page_title, $menu_title, $capability, $menu_slug, $callback, $icon, $position)
add_submenu_page()
Creates submenu item
add_submenu_page($parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback)
add_theme_page()
Adds to Appearance menu
add_theme_page($page_title, $menu_title, $capability, $menu_slug, $callback)
Implementing Settings API
Register Settings, Sections, and Fields
<?php
/**
* Register Theme Settings
*/
function mytheme_settings_init() {
// Register settings
register_setting(
'mytheme_options_group', // Option group
'mytheme_options', // Option name
'mytheme_sanitize_options' // Sanitize callback
);
// Add settings section
add_settings_section(
'mytheme_general_section', // Section ID
__( 'General Settings', 'mytheme' ), // Section title
'mytheme_general_section_callback', // Section callback
'mytheme-options' // Page slug
);
// Add settings fields
add_settings_field(
'mytheme_logo_text', // Field ID
__( 'Logo Text', 'mytheme' ), // Field title
'mytheme_logo_text_callback', // Field callback
'mytheme-options', // Page slug
'mytheme_general_section' // Section ID
);
add_settings_field(
'mytheme_footer_text',
__( 'Footer Text', 'mytheme' ),
'mytheme_footer_text_callback',
'mytheme-options',
'mytheme_general_section'
);
add_settings_field(
'mytheme_enable_feature',
__( 'Enable Special Feature', 'mytheme' ),
'mytheme_enable_feature_callback',
'mytheme-options',
'mytheme_general_section'
);
// Add another section
add_settings_section(
'mytheme_social_section',
__( 'Social Media Settings', 'mytheme' ),
'mytheme_social_section_callback',
'mytheme-options'
);
add_settings_field(
'mytheme_facebook_url',
__( 'Facebook URL', 'mytheme' ),
'mytheme_facebook_url_callback',
'mytheme-options',
'mytheme_social_section'
);
add_settings_field(
'mytheme_twitter_url',
__( 'Twitter URL', 'mytheme' ),
'mytheme_twitter_url_callback',
'mytheme-options',
'mytheme_social_section'
);
}
add_action( 'admin_init', 'mytheme_settings_init' );
/**
* Section Callbacks
*/
function mytheme_general_section_callback() {
echo '<p>' . __( 'Configure general theme settings.', 'mytheme' ) . '</p>';
}
function mytheme_social_section_callback() {
echo '<p>' . __( 'Enter your social media URLs.', 'mytheme' ) . '</p>';
}
/**
* Field Callbacks
*/
function mytheme_logo_text_callback() {
$options = get_option( 'mytheme_options' );
$value = isset( $options['logo_text'] ) ? $options['logo_text'] : '';
?>
<input type="text"
id="mytheme_logo_text"
name="mytheme_options[logo_text]"
value="<?php echo esc_attr( $value ); ?>"
class="regular-text" />
<p class="description">
<?php _e( 'Text to display in the logo area.', 'mytheme' ); ?>
</p>
<?php
}
function mytheme_footer_text_callback() {
$options = get_option( 'mytheme_options' );
$value = isset( $options['footer_text'] ) ? $options['footer_text'] : '';
?>
<textarea id="mytheme_footer_text"
name="mytheme_options[footer_text]"
rows="5"
cols="50"><?php echo esc_textarea( $value ); ?></textarea>
<p class="description">
<?php _e( 'Text to display in the footer.', 'mytheme' ); ?>
</p>
<?php
}
function mytheme_enable_feature_callback() {
$options = get_option( 'mytheme_options' );
$checked = isset( $options['enable_feature'] ) ? $options['enable_feature'] : 0;
?>
<label>
<input type="checkbox"
id="mytheme_enable_feature"
name="mytheme_options[enable_feature]"
value="1"
<?php checked( 1, $checked ); ?> />
<?php _e( 'Enable this special feature', 'mytheme' ); ?>
</label>
<?php
}
function mytheme_facebook_url_callback() {
$options = get_option( 'mytheme_options' );
$value = isset( $options['facebook_url'] ) ? $options['facebook_url'] : '';
?>
<input type="url"
id="mytheme_facebook_url"
name="mytheme_options[facebook_url]"
value="<?php echo esc_url( $value ); ?>"
class="regular-text" />
<?php
}
function mytheme_twitter_url_callback() {
$options = get_option( 'mytheme_options' );
$value = isset( $options['twitter_url'] ) ? $options['twitter_url'] : '';
?>
<input type="url"
id="mytheme_twitter_url"
name="mytheme_options[twitter_url]"
value="<?php echo esc_url( $value ); ?>"
class="regular-text" />
<?php
}
Validation and Sanitization
Always validate and sanitize user input to ensure data integrity and security.
Sanitization Callback
<?php
/**
* Sanitize Options
*/
function mytheme_sanitize_options( $input ) {
$sanitized = array();
// Sanitize text field
if ( isset( $input['logo_text'] ) ) {
$sanitized['logo_text'] = sanitize_text_field( $input['logo_text'] );
}
// Sanitize textarea
if ( isset( $input['footer_text'] ) ) {
$sanitized['footer_text'] = wp_kses_post( $input['footer_text'] );
}
// Sanitize checkbox
if ( isset( $input['enable_feature'] ) ) {
$sanitized['enable_feature'] = ( $input['enable_feature'] == 1 ) ? 1 : 0;
}
// Sanitize URLs
if ( isset( $input['facebook_url'] ) ) {
$sanitized['facebook_url'] = esc_url_raw( $input['facebook_url'] );
}
if ( isset( $input['twitter_url'] ) ) {
$sanitized['twitter_url'] = esc_url_raw( $input['twitter_url'] );
}
// Validate email
if ( isset( $input['contact_email'] ) ) {
if ( is_email( $input['contact_email'] ) ) {
$sanitized['contact_email'] = sanitize_email( $input['contact_email'] );
} else {
add_settings_error(
'mytheme_options',
'invalid-email',
__( 'Please enter a valid email address.', 'mytheme' ),
'error'
);
}
}
// Validate number range
if ( isset( $input['posts_per_page'] ) ) {
$number = absint( $input['posts_per_page'] );
if ( $number >= 1 && $number <= 20 ) {
$sanitized['posts_per_page'] = $number;
} else {
add_settings_error(
'mytheme_options',
'invalid-number',
__( 'Posts per page must be between 1 and 20.', 'mytheme' ),
'error'
);
$sanitized['posts_per_page'] = 10; // Default value
}
}
return $sanitized;
}
Common Field Types
Creating Tabbed Settings
Tabbed Options Page
<?php
/**
* Tabbed Options Page
*/
function mytheme_tabbed_options_page() {
$active_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : 'general';
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<h2 class="nav-tab-wrapper">
<a href="?page=mytheme-options&tab=general"
class="nav-tab <?php echo $active_tab == 'general' ? 'nav-tab-active' : ''; ?>">
<?php _e( 'General', 'mytheme' ); ?>
</a>
<a href="?page=mytheme-options&tab=social"
class="nav-tab <?php echo $active_tab == 'social' ? 'nav-tab-active' : ''; ?>">
<?php _e( 'Social Media', 'mytheme' ); ?>
</a>
<a href="?page=mytheme-options&tab=advanced"
class="nav-tab <?php echo $active_tab == 'advanced' ? 'nav-tab-active' : ''; ?>">
<?php _e( 'Advanced', 'mytheme' ); ?>
</a>
</h2>
<form method="post" action="options.php">
<?php
if ( $active_tab == 'general' ) {
settings_fields( 'mytheme_general_group' );
do_settings_sections( 'mytheme-general' );
} elseif ( $active_tab == 'social' ) {
settings_fields( 'mytheme_social_group' );
do_settings_sections( 'mytheme-social' );
} elseif ( $active_tab == 'advanced' ) {
settings_fields( 'mytheme_advanced_group' );
do_settings_sections( 'mytheme-advanced' );
}
submit_button();
?>
</form>
</div>
<?php
}
Using Options in Your Theme
Retrieving and Using Options
<?php
// Get all options
$options = get_option( 'mytheme_options', array() );
// Get specific option with default
$logo_text = isset( $options['logo_text'] ) ? $options['logo_text'] : 'Default Logo';
$footer_text = isset( $options['footer_text'] ) ? $options['footer_text'] : 'Copyright © 2024';
// Helper function for getting options
function mytheme_get_option( $key, $default = '' ) {
$options = get_option( 'mytheme_options', array() );
return isset( $options[$key] ) ? $options[$key] : $default;
}
// Usage in templates
?>
<header class="site-header">
<div class="logo">
<?php echo esc_html( mytheme_get_option( 'logo_text', get_bloginfo( 'name' ) ) ); ?>
</div>
</header>
<footer class="site-footer">
<div class="footer-text">
<?php echo wp_kses_post( mytheme_get_option( 'footer_text' ) ); ?>
</div>
<?php if ( mytheme_get_option( 'enable_social', false ) ) : ?>
<div class="social-links">
<?php if ( $facebook = mytheme_get_option( 'facebook_url' ) ) : ?>
<a href="<?php echo esc_url( $facebook ); ?>" target="_blank">Facebook</a>
<?php endif; ?>
<?php if ( $twitter = mytheme_get_option( 'twitter_url' ) ) : ?>
<a href="<?php echo esc_url( $twitter ); ?>" target="_blank">Twitter</a>
<?php endif; ?>
</div>
<?php endif; ?>
</footer>
Best Practices
Development Best Practices
- Use the Customizer when possible: For visual settings that benefit from live preview
- Group related settings: Use sections to organize options logically
- Always sanitize input: Never trust user input
- Provide defaults: Always have fallback values
- Use nonces: Protect against CSRF attacks
- Check capabilities: Ensure users have permission
- Escape output: Always escape when displaying options
- Cache options: Store frequently used options in variables
Never use update_option() directly in theme files for user-facing settings. Always use the Settings API for proper validation and security.
Practice Exercise
Build Complete Settings System