Skip to main content

Theme Settings and Options

Duration: 40 minutes
Module 4: Session 4.5

Learning Objectives

  • Understand WordPress Options API and Settings API
  • Create custom theme options pages
  • Build settings forms with proper validation
  • Implement different field types for settings
  • Store and retrieve theme options efficiently
  • Create tabbed options interfaces
  • Export and import theme settings
  • Integrate with the Customizer API

Introduction to Theme Settings

Theme settings and options allow users to configure various aspects of their theme without editing code. While the Customizer provides a live preview interface, theme options pages offer more complex configuration capabilities for advanced settings.

⚙️
Important Note
Modern WordPress development favors the Customizer API for theme options. However, understanding the Settings API is crucial for complex configurations and backward compatibility.

WordPress APIs for Theme Settings

Options API

  • Simple key-value storage
  • Direct database interaction
  • No built-in UI
  • Functions: get_option(), update_option()
  • Best for: Simple data storage

Settings API

  • Built on Options API
  • Form handling & validation
  • Security (nonces, capability checks)
  • Admin page integration
  • Best for: Complex settings pages

Settings API Workflow

1
Register Settings
2
Add Sections
3
Add Fields
4
Create Page
5
Render Form

Creating a Theme Options Page

// Step 1: Add admin menu
add_action('admin_menu', 'mytheme_add_admin_menu');

function mytheme_add_admin_menu() {
    add_theme_page(
        'Theme Options',           // Page title
        'Theme Options',           // Menu title
        'manage_options',          // Capability
        'mytheme-options',         // Menu slug
        'mytheme_options_page'     // Callback function
    );
}

// Step 2: Register settings
add_action('admin_init', 'mytheme_settings_init');

function mytheme_settings_init() {
    // Register a setting
    register_setting('mytheme_options_group', 'mytheme_options');
    
    // Add a section
    add_settings_section(
        'mytheme_general_section',
        'General Settings',
        'mytheme_general_section_callback',
        'mytheme-options'
    );
    
    // Add fields
    add_settings_field(
        'mytheme_logo_url',
        'Logo URL',
        'mytheme_logo_url_render',
        'mytheme-options',
        'mytheme_general_section'
    );
    
    add_settings_field(
        'mytheme_footer_text',
        'Footer Text',
        'mytheme_footer_text_render',
        'mytheme-options',
        'mytheme_general_section'
    );
}

// Section callback
function mytheme_general_section_callback() {
    echo '<p>Configure general theme settings below:</p>';
}

// Field callbacks
function mytheme_logo_url_render() {
    $options = get_option('mytheme_options');
    $value = isset($options['logo_url']) ? $options['logo_url'] : '';
    ?>
    <input type="url" name="mytheme_options[logo_url]" 
           value="<?php echo esc_attr($value); ?>" 
           class="regular-text" />
    <p class="description">Enter your logo URL</p>
    <?php
}

function mytheme_footer_text_render() {
    $options = get_option('mytheme_options');
    $value = isset($options['footer_text']) ? $options['footer_text'] : '';
    ?>
    <textarea name="mytheme_options[footer_text]" 
              rows="4" cols="50"><?php echo esc_textarea($value); ?></textarea>
    <p class="description">Text to display in the footer</p>
    <?php
}

// Step 3: Create the options page
function mytheme_options_page() {
    ?>
    <div class="wrap">
        <h1>Theme Options</h1>
        <form action="options.php" method="post">
            <?php
            settings_fields('mytheme_options_group');
            do_settings_sections('mytheme-options');
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

Creating a Tabbed Options Interface

Theme Options

General Settings

Upload or enter your logo URL

Choose your brand color

Tabbed Interface Code

function mytheme_tabbed_options_page() {
    $active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'general';
    ?>
    <div class="wrap">
        <h1>Theme Options</h1>
        
        <h2 class="nav-tab-wrapper">
            <a href="?page=mytheme-options&tab=general" 
               class="nav-tab <?php echo $active_tab == 'general' ? 'nav-tab-active' : ''; ?>">
                General
            </a>
            <a href="?page=mytheme-options&tab=layout" 
               class="nav-tab <?php echo $active_tab == 'layout' ? 'nav-tab-active' : ''; ?>">
                Layout
            </a>
            <a href="?page=mytheme-options&tab=typography" 
               class="nav-tab <?php echo $active_tab == 'typography' ? 'nav-tab-active' : ''; ?>">
                Typography
            </a>
        </h2>
        
        <form method="post" action="options.php">
            <?php
            if ($active_tab == 'general') {
                settings_fields('mytheme_general_options');
                do_settings_sections('mytheme-general');
            } elseif ($active_tab == 'layout') {
                settings_fields('mytheme_layout_options');
                do_settings_sections('mytheme-layout');
            } elseif ($active_tab == 'typography') {
                settings_fields('mytheme_typography_options');
                do_settings_sections('mytheme-typography');
            }
            
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

Common Field Types

📝
Text Field
📄
Textarea
☑️
Checkbox
Radio Button
📋
Select Dropdown
🎨
Color Picker
🖼️
Media Upload
📅
Date Picker

Field Implementation Examples

// Checkbox field
function mytheme_checkbox_field_render() {
    $options = get_option('mytheme_options');
    $checked = isset($options['show_sidebar']) ? $options['show_sidebar'] : 0;
    ?>
    <input type="checkbox" name="mytheme_options[show_sidebar]" 
           value="1" <?php checked(1, $checked); ?> />
    <label>Show sidebar on all pages</label>
    <?php
}

// Radio buttons
function mytheme_radio_field_render() {
    $options = get_option('mytheme_options');
    $layout = isset($options['layout']) ? $options['layout'] : 'right';
    ?>
    <label>
        <input type="radio" name="mytheme_options[layout]" 
               value="left" <?php checked('left', $layout); ?> />
        Left Sidebar
    </label><br>
    <label>
        <input type="radio" name="mytheme_options[layout]" 
               value="right" <?php checked('right', $layout); ?> />
        Right Sidebar
    </label><br>
    <label>
        <input type="radio" name="mytheme_options[layout]" 
               value="full" <?php checked('full', $layout); ?> />
        Full Width
    </label>
    <?php
}

// Select dropdown
function mytheme_select_field_render() {
    $options = get_option('mytheme_options');
    $font = isset($options['font_family']) ? $options['font_family'] : 'arial';
    ?>
    <select name="mytheme_options[font_family]">
        <option value="arial" <?php selected('arial', $font); ?>>Arial</option>
        <option value="georgia" <?php selected('georgia', $font); ?>>Georgia</option>
        <option value="helvetica" <?php selected('helvetica', $font); ?>>Helvetica</option>
        <option value="times" <?php selected('times', $font); ?>>Times New Roman</option>
    </select>
    <?php
}

// Media upload field
function mytheme_media_field_render() {
    $options = get_option('mytheme_options');
    $image = isset($options['header_image']) ? $options['header_image'] : '';
    ?>
    <input type="text" name="mytheme_options[header_image]" 
           id="header_image" value="<?php echo esc_url($image); ?>" 
           class="regular-text" />
    <input type="button" class="button button-secondary" 
           value="Upload Image" id="upload_image_button" />
    
    <script>
    jQuery(document).ready(function($) {
        $('#upload_image_button').click(function() {
            var mediaUploader = wp.media({
                title: 'Select Image',
                button: { text: 'Use This Image' },
                multiple: false
            });
            
            mediaUploader.on('select', function() {
                var attachment = mediaUploader.state().get('selection').first().toJSON();
                $('#header_image').val(attachment.url);
            });
            
            mediaUploader.open();
        });
    });
    </script>
    <?php
}

⚠️ Validation and Sanitization

Always validate and sanitize user input before saving to the database:

// Register setting with sanitization callback
register_setting(
    'mytheme_options_group',
    'mytheme_options',
    'mytheme_sanitize_options'
);

function mytheme_sanitize_options($input) {
    $sanitized = array();
    
    // Sanitize text field
    if (isset($input['footer_text'])) {
        $sanitized['footer_text'] = sanitize_text_field($input['footer_text']);
    }
    
    // Sanitize URL
    if (isset($input['logo_url'])) {
        $sanitized['logo_url'] = esc_url_raw($input['logo_url']);
    }
    
    // Sanitize checkbox
    if (isset($input['show_sidebar'])) {
        $sanitized['show_sidebar'] = ($input['show_sidebar'] == 1) ? 1 : 0;
    }
    
    // Sanitize select field
    if (isset($input['layout'])) {
        $valid_layouts = array('left', 'right', 'full');
        if (in_array($input['layout'], $valid_layouts)) {
            $sanitized['layout'] = $input['layout'];
        } else {
            $sanitized['layout'] = 'right'; // Default
        }
    }
    
    // Sanitize color hex
    if (isset($input['primary_color'])) {
        if (preg_match('/^#[a-f0-9]{6}$/i', $input['primary_color'])) {
            $sanitized['primary_color'] = $input['primary_color'];
        } else {
            $sanitized['primary_color'] = '#0073aa'; // Default
        }
    }
    
    // Add error messages if needed
    if (empty($input['footer_text'])) {
        add_settings_error(
            'mytheme_options',
            'footer_text_error',
            'Footer text cannot be empty',
            'error'
        );
    }
    
    return $sanitized;
}

Using Theme Options

// Get theme options
$options = get_option('mytheme_options');

// Use with defaults
$footer_text = isset($options['footer_text']) ? $options['footer_text'] : 'Copyright © 2024';
$logo_url = isset($options['logo_url']) ? $options['logo_url'] : get_template_directory_uri() . '/images/logo.png';
$show_sidebar = isset($options['show_sidebar']) ? $options['show_sidebar'] : true;

// Helper function for getting options
function mytheme_get_option($option_name, $default = '') {
    $options = get_option('mytheme_options');
    return isset($options[$option_name]) ? $options[$option_name] : $default;
}

// In your theme templates
<?php if (mytheme_get_option('show_sidebar', true)) : ?>
    <aside class="sidebar">
        <?php get_sidebar(); ?>
    </aside>
<?php endif; ?>

// Output custom CSS based on options
add_action('wp_head', function() {
    $primary_color = mytheme_get_option('primary_color', '#0073aa');
    $font_family = mytheme_get_option('font_family', 'Arial, sans-serif');
    ?>
    <style>
        :root {
            --primary-color: <?php echo esc_attr($primary_color); ?>;
            --font-family: <?php echo esc_attr($font_family); ?>;
        }
        
        body {
            font-family: var(--font-family);
        }
        
        a {
            color: var(--primary-color);
        }
        
        .button-primary {
            background-color: var(--primary-color);
        }
    </style>
    <?php
});

Export and Import Settings

Allow users to backup and transfer theme settings:

// Export settings
function mytheme_export_settings() {
    if (!isset($_GET['action']) || $_GET['action'] != 'export_settings') {
        return;
    }
    
    if (!current_user_can('manage_options')) {
        return;
    }
    
    $options = get_option('mytheme_options');
    $json = json_encode($options);
    
    header('Content-Type: application/json');
    header('Content-Disposition: attachment; filename="theme-settings-' . date('Y-m-d') . '.json"');
    echo $json;
    exit;
}
add_action('admin_init', 'mytheme_export_settings');

// Import settings
function mytheme_import_settings() {
    if (!isset($_POST['import_settings'])) {
        return;
    }
    
    if (!current_user_can('manage_options')) {
        return;
    }
    
    if (!isset($_FILES['import_file'])) {
        return;
    }
    
    $file = $_FILES['import_file']['tmp_name'];
    $json = file_get_contents($file);
    $options = json_decode($json, true);
    
    if ($options) {
        update_option('mytheme_options', $options);
        add_settings_error(
            'mytheme_options',
            'settings_imported',
            'Settings imported successfully!',
            'success'
        );
    }
}
add_action('admin_init', 'mytheme_import_settings');

// Add to options page
function mytheme_export_import_section() {
    ?>
    <h2>Export/Import Settings</h2>
    
    <h3>Export Settings</h3>
    <p>Export your theme settings for backup or transfer.</p>
    <a href="<?php echo admin_url('admin.php?page=mytheme-options&action=export_settings'); ?>" 
       class="button button-secondary">Export Settings</a>
    
    <h3>Import Settings</h3>
    <form method="post" enctype="multipart/form-data">
        <input type="file" name="import_file" accept=".json" />
        <?php wp_nonce_field('import_settings_nonce'); ?>
        <input type="submit" name="import_settings" 
               value="Import Settings" class="button button-secondary" />
    </form>
    <?php
}

✅ Best Practices for Theme Settings

  • Use Customizer when possible:For visual settings with live preview
  • Group related settings:Organize into logical sections
  • Provide defaults:Always have fallback values
  • Validate and sanitize:Never trust user input
  • Use descriptive names:Clear option keys and labels
  • Cache expensive operations:Use transients for complex queries
  • Document settings:Add help text and descriptions
  • Version your options:Track schema changes
  • Minimize database calls:Get all options at once
  • Provide reset option:Allow users to restore defaults

Practice Exercise

💻
Hands-On Practice
  1. Create a theme options page with tabs
  2. Add text, checkbox, and select fields
  3. Implement proper sanitization callbacks
  4. Add a color picker field
  5. Create a media upload field
  6. Store and retrieve options in your theme
  7. Output dynamic CSS based on options
  8. Add export/import functionality
  9. Create a reset to defaults button
  10. Integrate some options with the Customizer

Additional Resources