Skip to main content

Course Progress

Loading...

⚙️ Theme Options & Settings

Create powerful admin interfaces for theme configuration

Master the WordPress Settings API and options management

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
Customizer: Visual settings, colors, fonts, layouts
Settings API: Complex data, API keys, advanced configurations, import/export

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

Text-Based Fields

<?php
// Text Input
function mytheme_text_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['text_field'] ) ? $options['text_field'] : '';
    ?>
    <input type="text" 
           name="mytheme_options[text_field]" 
           value="<?php echo esc_attr( $value ); ?>" 
           class="regular-text" />
    <?php
}

// Email Input
function mytheme_email_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['email_field'] ) ? $options['email_field'] : '';
    ?>
    <input type="email" 
           name="mytheme_options[email_field]" 
           value="<?php echo esc_attr( $value ); ?>" 
           class="regular-text" />
    <?php
}

// Number Input
function mytheme_number_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['number_field'] ) ? $options['number_field'] : 10;
    ?>
    <input type="number" 
           name="mytheme_options[number_field]" 
           value="<?php echo esc_attr( $value ); ?>" 
           min="1" 
           max="100" 
           step="1" />
    <?php
}

// Textarea
function mytheme_textarea_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['textarea_field'] ) ? $options['textarea_field'] : '';
    ?>
    <textarea name="mytheme_options[textarea_field]" 
              rows="5" 
              cols="50"><?php echo esc_textarea( $value ); ?></textarea>
    <?php
}

// Color Picker
function mytheme_color_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['color_field'] ) ? $options['color_field'] : '#ffffff';
    ?>
    <input type="text" 
           name="mytheme_options[color_field]" 
           value="<?php echo esc_attr( $value ); ?>" 
           class="mytheme-color-picker" />
    <?php
}

Selection Fields

<?php
// Select Dropdown
function mytheme_select_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['select_field'] ) ? $options['select_field'] : 'option1';
    ?>
    <select name="mytheme_options[select_field]">
        <option value="option1" <?php selected( $value, 'option1' ); ?>>
            <?php _e( 'Option 1', 'mytheme' ); ?>
        </option>
        <option value="option2" <?php selected( $value, 'option2' ); ?>>
            <?php _e( 'Option 2', 'mytheme' ); ?>
        </option>
        <option value="option3" <?php selected( $value, 'option3' ); ?>>
            <?php _e( 'Option 3', 'mytheme' ); ?>
        </option>
    </select>
    <?php
}

// Radio Buttons
function mytheme_radio_field_callback() {
    $options = get_option( 'mytheme_options' );
    $value = isset( $options['radio_field'] ) ? $options['radio_field'] : 'yes';
    ?>
    <fieldset>
        <label>
            <input type="radio" 
                   name="mytheme_options[radio_field]" 
                   value="yes" 
                   <?php checked( $value, 'yes' ); ?> />
            <?php _e( 'Yes', 'mytheme' ); ?>
        </label><br>
        <label>
            <input type="radio" 
                   name="mytheme_options[radio_field]" 
                   value="no" 
                   <?php checked( $value, 'no' ); ?> />
            <?php _e( 'No', 'mytheme' ); ?>
        </label><br>
        <label>
            <input type="radio" 
                   name="mytheme_options[radio_field]" 
                   value="maybe" 
                   <?php checked( $value, 'maybe' ); ?> />
            <?php _e( 'Maybe', 'mytheme' ); ?>
        </label>
    </fieldset>
    <?php
}

// Multiple Checkboxes
function mytheme_checkboxes_field_callback() {
    $options = get_option( 'mytheme_options' );
    $values = isset( $options['checkboxes_field'] ) ? $options['checkboxes_field'] : array();
    ?>
    <fieldset>
        <label>
            <input type="checkbox" 
                   name="mytheme_options[checkboxes_field][]" 
                   value="option1" 
                   <?php checked( in_array( 'option1', $values ) ); ?> />
            <?php _e( 'Option 1', 'mytheme' ); ?>
        </label><br>
        <label>
            <input type="checkbox" 
                   name="mytheme_options[checkboxes_field][]" 
                   value="option2" 
                   <?php checked( in_array( 'option2', $values ) ); ?> />
            <?php _e( 'Option 2', 'mytheme' ); ?>
        </label><br>
        <label>
            <input type="checkbox" 
                   name="mytheme_options[checkboxes_field][]" 
                   value="option3" 
                   <?php checked( in_array( 'option3', $values ) ); ?> />
            <?php _e( 'Option 3', 'mytheme' ); ?>
        </label>
    </fieldset>
    <?php
}

Media Upload Fields

<?php
// Media Upload Field
function mytheme_image_field_callback() {
    $options = get_option( 'mytheme_options' );
    $image_id = isset( $options['image_field'] ) ? $options['image_field'] : '';
    ?>
    <div class="mytheme-media-upload">
        <input type="hidden" 
               id="mytheme_image_field" 
               name="mytheme_options[image_field]" 
               value="<?php echo esc_attr( $image_id ); ?>" />
        
        <div id="mytheme_image_preview">
            <?php if ( $image_id ) : ?>
                <?php echo wp_get_attachment_image( $image_id, 'thumbnail' ); ?>
            <?php endif; ?>
        </div>
        
        <button type="button" 
                class="button mytheme-upload-button" 
                data-field="mytheme_image_field" 
                data-preview="mytheme_image_preview">
            <?php _e( 'Choose Image', 'mytheme' ); ?>
        </button>
        
        <button type="button" 
                class="button mytheme-remove-button" 
                data-field="mytheme_image_field" 
                data-preview="mytheme_image_preview">
            <?php _e( 'Remove Image', 'mytheme' ); ?>
        </button>
    </div>
    <?php
}

// Enqueue media scripts
function mytheme_admin_scripts( $hook ) {
    if ( 'toplevel_page_mytheme-options' !== $hook ) {
        return;
    }
    
    wp_enqueue_media();
    wp_enqueue_script(
        'mytheme-admin',
        get_template_directory_uri() . '/js/admin.js',
        array( 'jquery' ),
        '1.0.0',
        true
    );
}
add_action( 'admin_enqueue_scripts', 'mytheme_admin_scripts' );

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

Create a comprehensive theme options system that includes:

  1. Create main options page with icon
  2. Add three submenu pages (General, Design, Advanced)
  3. Implement tabbed navigation
  4. Add text, textarea, and checkbox fields
  5. Create color picker field
  6. Add media upload functionality
  7. Implement proper validation for all fields
  8. Add contextual help tabs
  9. Create export/import functionality
  10. Use options throughout the theme

Additional Resources