WordPress provides two related but distinct APIs for storing theme configuration: the Theme Customizer system (get_theme_mod() / set_theme_mod()) and the general Options API (get_option() / update_option()). Theme mods are stored as a single serialised array under the theme_mods_{stylesheet} option key, scoped to the currently active theme — if the user switches themes, the old theme’s mods are automatically ignored (not deleted) and the new theme starts with its own mods. The Options API stores individual keys directly in wp_options. The correct choice depends on the data’s relationship to the theme: settings that change the visual appearance of the current theme (colours, fonts, logo, header image) belong in theme mods; plugin settings, site-wide configuration, and data that should survive theme switches belong in wp_options. Mixing the two — storing plugin settings as theme mods, or storing theme appearance settings as plugin options — creates either lost data or orphaned options.
Problem: A theme registers Customizer settings for header colour, footer text, and logo position. A plugin on the same site stores its API key and feature flags. Both need to read their stored values efficiently and provide defaults. The developer needs to decide which API is correct for each case and implement accordingly.
Solution: Use get_theme_mod() for all visual theme settings registered in the Customizer. Use get_option() for plugin settings. Provide defaults in get_theme_mod()'s second argument and via register_setting()'s default for options.
<?php
// ══════════════════════════════════════════
// THEME MODS — visual appearance settings
// ══════════════════════════════════════════
// Reading theme mods (in theme templates)
$header_color = get_theme_mod( 'header_bg_color', '#1e40af' ); // second arg = default
$footer_text = get_theme_mod( 'footer_text', get_bloginfo( 'name' ) );
$logo_position = get_theme_mod( 'logo_position', 'left' );
// Setting theme mods programmatically (e.g. in a migration or test)
set_theme_mod( 'header_bg_color', '#0a2342' );
// Removing a theme mod
remove_theme_mod( 'header_bg_color' );
// Get ALL theme mods for the current theme
$all_mods = get_theme_mods(); // ['header_bg_color' => '#1e40af', ...]
// ── Registering Customizer settings correctly ──────────────────────────
add_action( 'customize_register', function ( WP_Customize_Manager $wp_customize ) {
$wp_customize->add_section( 'my_theme_header', [
'title' => __( 'Header Settings', 'textdomain' ),
'priority' => 30,
] );
$wp_customize->add_setting( 'header_bg_color', [
'default' => '#1e40af',
'transport' => 'postMessage', // or 'refresh'
'sanitize_callback' => 'sanitize_hex_color',
] );
$wp_customize->add_control(
new WP_Customize_Color_Control( $wp_customize, 'header_bg_color', [
'label' => __( 'Header Background', 'textdomain' ),
'section' => 'my_theme_header',
] )
);
} );
// ══════════════════════════════════════════
// OPTIONS API — plugin and site-wide settings
// ══════════════════════════════════════════
// Reading plugin options
$api_key = get_option( 'my_plugin_api_key', '' );
$features = get_option( 'my_plugin_features', [ 'analytics' => true ] );
// Writing plugin options
update_option( 'my_plugin_api_key', sanitize_text_field( $key ) );
update_option( 'my_plugin_features', $features, false ); // false = don't autoload
// Deleting plugin option on uninstall
delete_option( 'my_plugin_api_key' );
// Register with default and sanitize callback (via Settings API)
register_setting( 'my_plugin_group', 'my_plugin_api_key', [
'type' => 'string',
'default' => '',
'sanitize_callback' => 'sanitize_text_field',
] );
NOTE: Theme mods are stored as a single serialised PHP array in one wp_options row — reading all theme mods with get_theme_mods() is one database query, while reading them individually with multiple get_theme_mod() calls is still one query (the full array is cached on the first call). Theme mods are not automatically deleted when a theme is uninstalled — only ignored. If you need to clean them up, use a theme deactivation hook or a switch_theme action. Plugin options should always be deleted in register_uninstall_hook() to clean up after removal — theme mods should be removed in after_switch_theme if the previous theme wants to clean its data.