Add Theme Settings With the WordPress Customizer API
The WordPress Customizer API gives theme developers a controlled way to expose design options without forcing site owners to edit PHP or rebuild templates. A setting can be registered in the Customizer, displayed in the admin panel, and connected to a live preview so changes appear as they are made.
This approach suits custom themes, starter themes and WooCommerce storefronts where branding requirements vary from one installation to another. A developer working on a Sydney café website, a Melbourne consultancy or a regional Australian retailer can provide flexible controls while keeping the theme’s markup, security model and performance under control.
How The Customizer API Fits Together
The Customizer is built around four related components: a panel, a section, a setting and a control. A panel groups broad configuration areas, a section organises related options, a setting stores the value, and a control determines how the administrator edits it. For a small theme, a section can be enough; larger themes may benefit from a dedicated panel containing typography, colours and header settings.
Most theme settings should use the theme_mod API through $wp_customize->add_setting(). Theme modifications are stored against the active theme, which prevents options from leaking into a new theme after a switch. The value can then be retrieved with get_theme_mod() in the theme templates or CSS output.
A basic registration function looks like this:
function acme_customize_register( $wp_customize ) {
$wp_customize->add_section(
'acme_branding',
array(
'title' => __( 'Branding', 'acme' ),
'priority' => 30,
'capability' => 'edit_theme_options',
'description' => __( 'Adjust the main brand colours.', 'acme' ),
)
);
$wp_customize->add_setting(
'acme_accent_colour',
array(
'default' => '#006b5f',
'type' => 'theme_mod',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'acme_accent_colour',
array(
'label' => __( 'Accent colour', 'acme' ),
'section' => 'acme_branding',
'settings' => 'acme_accent_colour',
)
)
);
}
add_action( 'customize_register', 'acme_customize_register' );
The customize_register hook is the correct place to register controls. Keep registration in a separate Customizer file where possible, then load it from functions.php. This makes the feature easier to test and avoids mixing administrative configuration with front-end template logic.
Building A Setting With Live Preview
The transport argument determines how a value reaches the preview. With the default refresh transport, WordPress refreshes the preview iframe after a value changes. This is reliable and straightforward, but the page may briefly reload. The postMessage transport sends the value to JavaScript, allowing the preview to update instantly without a full refresh.
For postMessage to work, the theme needs a preview script. Enqueue it only while the Customizer preview is active:
function acme_customize_preview_js() {
wp_enqueue_script(
'acme-customizer',
get_template_directory_uri() . '/assets/js/customizer.js',
array( 'customize-preview' ),
'1.0.0',
true
);
}
add_action( 'customize_preview_init', 'acme_customize_preview_js' );
The JavaScript subscribes to the setting and changes the relevant element:
( function( $ ) {
wp.customize( 'acme_accent_colour', function( value ) {
value.bind( function( newColour ) {
$( '.site-header a, .button, .wp-block-button__link' )
.css( 'background-color', newColour );
} );
} );
} )( jQuery );
The selector should match stable classes in the theme rather than fragile element paths. If a button colour is applied in several locations, consider updating a CSS custom property on the root element:
value.bind( function( newColour ) {
document.documentElement.style.setProperty(
'--accent-colour',
newColour
);
} );
The stylesheet can then use var(--accent-colour) throughout the site. This reduces duplicated JavaScript and keeps responsive states consistent. It is also useful for WooCommerce buttons, sale labels and checkout links, where a single brand colour may appear across several templates.
The live preview still needs a sensible fallback. If JavaScript fails, the saved value should be rendered by PHP or included in a generated style block. A preview enhancement should never be the only mechanism that applies a setting on the published website.
Adding Typography, Images And Layout Controls
The API includes specialised controls for common theme options. WP_Customize_Image_Control handles logos and background images, WP_Customize_Media_Control supports media uploads, and WP_Customize_Manager can be extended with radio buttons, selects, checkboxes and range controls. The control type should reflect the value being edited so that administrators receive useful validation and an appropriate interface.
For example, a layout option can use a select control:
$wp_customize->add_setting(
'acme_content_layout',
array(
'default' => 'wide',
'sanitize_callback' => 'acme_sanitize_layout',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'acme_content_layout',
array(
'label' => __( 'Content layout', 'acme' ),
'section' => 'acme_branding',
'type' => 'select',
'choices' => array(
'wide' => __( 'Wide', 'acme' ),
'boxed' => __( 'Boxed', 'acme' ),
),
)
);
The sanitisation callback should reject unexpected values:
function acme_sanitize_layout( $value ) {
$allowed = array( 'wide', 'boxed' );
return in_array( $value, $allowed, true ) ? $value : 'wide';
}
In the template, the selected value can become a body class:
<body <?php body_class( 'layout-' . get_theme_mod(
'acme_content_layout',
'wide'
) ); ?>>
For text settings, use sanitize_text_field() for short labels and wp_kses_post() only when a field genuinely needs restricted HTML. Do not store arbitrary CSS, JavaScript or unfiltered markup simply because the Customizer can display a text input. A user with the edit_theme_options capability may be trusted to configure the theme, but sanitisation still protects against accidental breakage and unsafe output.
Image settings need attention to accessibility. If a logo is configurable, provide a separate image or text field for the site name and ensure the output has a meaningful alt attribute. A retailer operating across Brisbane and the Gold Coast may use a seasonal campaign logo, yet the public markup should remain understandable to screen readers and search engines.
Securing And Organising Theme Options
Every setting should define an appropriate capability, default value and sanitisation callback. The usual capability is edit_theme_options, but a custom capability may be appropriate for a managed platform. The setting’s transport does not replace permission checks; it only determines how the preview receives updates.
Avoid creating a new option for every small visual detail without a clear naming convention. Prefix keys with the theme slug, such as acme_accent_colour, to reduce collisions with plugins and child themes. Group related settings into sections such as Branding, Header, Footer and Accessibility. Clear labels are especially important when a client’s content team, rather than a developer, will maintain the website.
A useful local workflow is to test settings on a staging copy that reflects the real Australian audience. Check the preview at common mobile widths, on slower connections used outside metropolitan areas, and with long business names or addresses. A theme that works perfectly in a compact Sydney header may wrap unexpectedly for a regional service with a much longer location name.
When diagnosing an issue, inspect whether the value is being saved, whether the preview script is loaded, and whether the selector exists in the iframe. Developers can also consult WordPress development answers when a hook, control class or preview event behaves differently across WordPress versions.
Testing And Publishing A Reliable Customizer Feature
Test the complete lifecycle rather than checking only the colour picker. Open the Customizer as an administrator, change the value, move between sections, save a draft, publish it, and reload the public page. Then switch themes and confirm that the original theme’s settings remain isolated. Check that an unpublished change appears in the preview but not on the live website.
Selective refresh is another option for server-rendered components. Instead of refreshing the full iframe, WordPress can request updated HTML for a particular partial. This is useful for a site title, navigation label or footer widget that is generated by PHP:
$wp_customize->selective_refresh->add_partial(
'blogname',
array(
'selector' => '.site-title',
'render_callback' => function() {
bloginfo( 'name' );
},
)
);
Selective refresh is preferable when the updated content depends on PHP, while postMessage is usually faster for simple CSS properties. A full refresh transport remains a practical choice for structural settings that affect many template elements.
Implementation Checklist
Before releasing a Customizer feature, verify the following:
- Each setting has a namespaced ID, a safe default and a sanitisation callback.
- Controls appear in a logical section with clear, client-friendly labels.
- Preview JavaScript loads only through
customize_preview_init. - The public theme renders the saved value without relying on JavaScript.
Test the interface across realistic content and devices:
- Check keyboard navigation, focus states and colour contrast.
- Test logos, headings and buttons with long Australian business names.
- Review WooCommerce cart and checkout templates after colour or layout changes.
- Confirm unpublished values never appear to ordinary visitors.
Performance also matters. Avoid printing a large block of inline CSS for every possible setting when a small set of CSS variables will do. Enqueue assets properly, limit preview scripts to the Customizer, and remove controls that are irrelevant to the current theme context. For example, a footer layout control can be registered only when the theme actually renders a configurable footer.
The WordPress Customizer API remains useful even as block themes and the Site Editor become more common. Classic themes, hybrid themes and many existing client installations still depend on it, and its hooks provide a predictable way to add theme options without editing core files. A well-structured implementation gives administrators immediate visual feedback while preserving sanitisation, accessibility, responsive behaviour and maintainable PHP.