Classic theme customizer
Skill Lonsdale201/wp-agent-skills/theme-development/classic-theme-customizer
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill classic-theme-customizerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Add or audit WordPress Customizer options for classic PHP themes on WP 7.0. Covers `customize_register`, `WP_Customize_Manager`, settings, sections, controls, `theme_mod` vs `option`, option-backed Customizer autoload, multidimensional setting IDs, `sanitize_callback`, `validate_callback`, `WP_Customize_Color_Control`, allowlisted select values, `postMessage`, `customize_preview_init`, selective refresh partials, escaping `get_theme_mod()` output in templates, and common bugs such as raw Customizer values, missing sanitization, or `postMessage` without preview JavaScript.
SKILL.md
9.4 KB, as published. Nobody here has run it
Classic Theme Customizer
Use this when adding or reviewing Customizer settings for a classic PHP theme. This is for theme presentation choices such as colors, layout toggles, logo-adjacent display settings, and small design options.
Do not use theme Customizer settings for plugin-like business data, payment credentials, complex content models, or cross-theme application state.
When to Use This Skill
- Adding
customize_registercode to a classic theme. - Creating a color, select, checkbox, text, image, or layout option.
- Reviewing
get_theme_mod()output in templates. - Adding live preview with
postMessageor selective refresh. - Fixing unsanitized Customizer settings or controls registered outside the correct hook.
Registration Hook
Register settings, sections, and controls on customize_register.
add_action( 'customize_register', 'mytheme_customize_register' );
function mytheme_customize_register( WP_Customize_Manager $wp_customize ) {
$wp_customize->add_section(
'mytheme_layout',
array(
'title' => __( 'Layout', 'textdomain' ),
'priority' => 160,
)
);
}
Rules:
- Use the
WP_Customize_Managerinstance passed to the hook. - Prefix setting IDs with the theme slug.
- Keep Customizer setup in
inc/customizer.phpor a similarly scoped include, loaded fromfunctions.php. - Do not register controls during normal template rendering.
Settings Must Sanitize
Every custom setting needs a sanitize_callback.
$wp_customize->add_setting(
'mytheme_accent_color',
array(
'default' => '#0a7a75',
'type' => 'theme_mod',
'capability' => 'edit_theme_options',
'transport' => 'refresh',
'sanitize_callback' => 'sanitize_hex_color',
)
);
Rules:
- Prefer
type => 'theme_mod'for theme design settings. - Use
type => 'option'only when the value is intentionally shared beyond the active theme. - When using
type => 'option', setautoload => falsefor values that are not needed on most requests; core's multidimensional option save defaults to autoloading unless an autoload arg is supplied. - The default capability is
edit_theme_options; set it explicitly when clarity helps. - Use
transport => 'refresh'unless live preview is actually implemented. - Add
validate_callbackwhen a value can be syntactically valid but semantically unacceptable.
Common sanitizers:
| Value | Sanitizer |
|---|---|
| Hex color | sanitize_hex_color |
| Plain text | sanitize_text_field |
| Textarea | sanitize_textarea_field |
| URL | esc_url_raw |
| Checkbox | Custom boolean sanitizer |
| Select/radio | Custom allowlist sanitizer |
| Integer | absint |
Controls
Use core controls where they fit.
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'mytheme_accent_color',
array(
'label' => __( 'Accent color', 'textdomain' ),
'section' => 'mytheme_layout',
)
)
);
Select controls must pair with an allowlist sanitizer:
function mytheme_sanitize_layout( $value ) {
$allowed = array( 'full', 'boxed', 'narrow' );
if ( in_array( $value, $allowed, true ) ) {
return $value;
}
return 'full';
}
$wp_customize->add_setting(
'mytheme_site_layout',
array(
'default' => 'full',
'sanitize_callback' => 'mytheme_sanitize_layout',
)
);
$wp_customize->add_control(
'mytheme_site_layout',
array(
'label' => __( 'Site layout', 'textdomain' ),
'section' => 'mytheme_layout',
'type' => 'select',
'choices' => array(
'full' => __( 'Full width', 'textdomain' ),
'boxed' => __( 'Boxed', 'textdomain' ),
'narrow' => __( 'Narrow', 'textdomain' ),
),
)
);
Reading Values in Templates
Customizer values are stored data. Sanitize on save and escape on output.
$layout = get_theme_mod( 'mytheme_site_layout', 'full' );
if ( in_array( $layout, array( 'full', 'boxed', 'narrow' ), true ) ) {
printf(
'<div class="site-layout site-layout-%s">',
esc_attr( $layout )
);
}
Rules:
- Always pass a default to
get_theme_mod(). - Escape by output context:
esc_html(),esc_attr(),esc_url(), or controlled KSES. - Do not echo raw Customizer values into HTML, attributes, CSS, or JavaScript.
- For CSS variables or inline CSS, validate allowed formats tightly before output.
- Multidimensional setting IDs such as
mytheme_options[color]are one root value plus subkeys. Keep the root option array small and typed; do not use it as a dumping ground for unrelated data.
Live Preview with postMessage
Use postMessage only when preview JavaScript is registered.
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage';
add_action( 'customize_preview_init', 'mytheme_customize_preview_js' );
function mytheme_customize_preview_js() {
wp_enqueue_script(
'mytheme-customize-preview',
get_theme_file_uri( 'assets/js/customize-preview.js' ),
array( 'customize-preview' ),
mytheme_asset_version( 'assets/js/customize-preview.js' ),
array( 'in_footer' => true )
);
}
Preview script:
( function ( wp ) {
wp.customize( 'blogname', function ( value ) {
value.bind( function ( nextValue ) {
var siteTitle = document.querySelector( '.site-title a' );
if ( siteTitle ) {
siteTitle.textContent = nextValue;
}
} );
} );
}( window.wp ) );
Rules:
- Preview JS depends on
customize-preview. - Controls-pane JS belongs on
customize_controls_enqueue_scripts. - If there is no preview JS, leave
transportasrefresh.
Selective Refresh
Use selective refresh when a small server-rendered fragment can be updated without refreshing the whole preview.
if ( isset( $wp_customize->selective_refresh ) ) {
$wp_customize->selective_refresh->add_partial(
'blogname',
array(
'selector' => '.site-title a',
'render_callback' => 'mytheme_render_site_title',
)
);
}
function mytheme_render_site_title() {
echo esc_html( get_bloginfo( 'name' ) );
}
Rules:
- The
selectormust match stable frontend markup. - The render callback must output or return safe, escaped markup.
- Do not use selective refresh for large, fragile page sections unless the markup is stable and tested.
Theme Boundary
Good Customizer settings:
- Accent color.
- Header layout.
- Sidebar position.
- Footer text when it is purely theme presentation.
- Archive excerpt length when it is theme display behavior.
Bad Customizer settings:
- API keys and secrets.
- Checkout/business rules.
- CPT data models.
- Email templates owned by a plugin.
- Anything that should survive theme switching as application state.
Review Checklist
- Settings are registered on
customize_register. - Every custom setting has
sanitize_callback. - Select/radio values are allowlisted.
theme_modis used for theme-specific presentation settings.- Templates escape
get_theme_mod()output by context. postMessagesettings have preview JS.- Preview JS uses
customize_preview_initand depends oncustomize-preview. - Controls JS/CSS uses
customize_controls_enqueue_scripts. - Selective refresh partials use stable selectors and safe render callbacks.
- Theme does not store plugin-like data in Customizer settings.
- Option-backed settings have an intentional autoload value.
Common Mistakes
- Adding controls without sanitizers because the UI "only has safe choices".
- Using
postMessagebut not enqueueing preview JavaScript. - Echoing
get_theme_mod()raw inside a class attribute or style attribute. - Registering Customizer objects on
init. - Using
optionstorage for values that should be theme-specific. - Letting a Customizer control write arbitrary CSS without validation.
Cross-References
- Use
wp-settings-storage-auditto review the broader persistence contract: theme_mod vs option, array shape, defaults, autoload, REST exposure, and migration. - Use
wp-plugin-options-storagewhen the value is plugin-owned or should survive theme switching.
References
- Official documentation: https://developer.wordpress.org/themes/customize-api/
- Official documentation: https://developer.wordpress.org/themes/customize-api/customizer-objects/
- Official documentation: https://developer.wordpress.org/themes/customize-api/the-customizer-javascript-api/
- Verified source paths:
wp-includes/class-wp-customize-manager.phpwp-includes/class-wp-customize-setting.phpwp-includes/class-wp-customize-control.phpwp-includes/customize/class-wp-customize-selective-refresh.phpwp-includes/theme.phpwp-content/themes/storefront/inc/customizer/wp-content/themes/generatepress/inc/customizer.php