Add custom settings to the WordPress Theme Customizer

The WordPress Theme Customizer — accessed at Appearance → Customize — is the live preview panel where site owners adjust theme settings without touching code. As a developer, you can extend it with your own panels, sections, and controls so editors can change colours, fonts, layouts, and custom content with instant live preview. The Customizer API uses four main objects: panels group related sections together; sections hold individual settings and their controls; settings store the value (either in the database or as a JavaScript preview); and controls are the form elements (text, colour, image, select, checkbox) the user interacts with. All registration happens inside the customize_register action, which receives the WP_Customize_Manager instance as its argument. The value saved by a control is retrieved anywhere in your theme with get_theme_mod( 'setting_name', 'default_value' ). For live preview without a page reload, you add a transport => 'postMessage' argument to the setting and write a small JavaScript snippet that applies the change instantly. Customizer settings are ideal for things that change the visual design of your theme — a hero section headline, a footer tagline, a primary button colour — while functionality toggles belong in a settings page. Pair this with CSS custom properties so the value saved in the Customizer feeds directly into a --css-variable that propagates across your entire stylesheet. This guide covers the most common control types with full registration and retrieval examples.

Problem: You want to let site editors change theme settings (hero text, colours, social links) through the WordPress Customizer with live preview, without exposing them to code.

Solution: Add the following code to your functions.php file:

add_action( 'customize_register', 'helloadmin_customizer_settings' );
function helloadmin_customizer_settings( WP_Customize_Manager $wp_customize ) {

    // Add a custom section
    $wp_customize->add_section( 'helloadmin_hero', [
        'title'    => __( 'Hero Section' ),
        'priority' => 30,
    ] );

    // Text setting + control
    $wp_customize->add_setting( 'helloadmin_hero_heading', [
        'default'           => 'Welcome to our site',
        'sanitize_callback' => 'sanitize_text_field',
        'transport'         => 'postMessage', // live preview without reload
    ] );
    $wp_customize->add_control( 'helloadmin_hero_heading', [
        'label'   => __( 'Hero Heading' ),
        'section' => 'helloadmin_hero',
        'type'    => 'text',
    ] );

    // Colour picker
    $wp_customize->add_setting( 'helloadmin_primary_color', [
        'default'           => '#2563eb',
        'sanitize_callback' => 'sanitize_hex_color',
        'transport'         => 'postMessage',
    ] );
    $wp_customize->add_control( new WP_Customize_Color_Control(
        $wp_customize,
        'helloadmin_primary_color',
        [ 'label' => __( 'Primary Colour' ), 'section' => 'helloadmin_hero' ]
    ) );

    // Image upload
    $wp_customize->add_setting( 'helloadmin_hero_image', [
        'default'           => '',
        'sanitize_callback' => 'esc_url_raw',
    ] );
    $wp_customize->add_control( new WP_Customize_Image_Control(
        $wp_customize,
        'helloadmin_hero_image',
        [ 'label' => __( 'Hero Background Image' ), 'section' => 'helloadmin_hero' ]
    ) );
}

// Retrieve values in your template
// $heading = get_theme_mod( 'helloadmin_hero_heading', 'Welcome to our site' );
// $color   = get_theme_mod( 'helloadmin_primary_color', '#2563eb' );
// $image   = get_theme_mod( 'helloadmin_hero_image', '' );

// Output dynamic CSS for live preview (postMessage transport)
add_action( 'wp_head', 'helloadmin_customizer_inline_css' );
function helloadmin_customizer_inline_css() {
    $color = sanitize_hex_color( get_theme_mod( 'helloadmin_primary_color', '#2563eb' ) );
    echo '<style>:root{--color-primary:' . esc_attr( $color ) . ';}</style>';
}

NOTE: Every setting must have a sanitize_callback — without it, WordPress will not save the value in newer versions. For the postMessage live-preview transport to work you also need a matching wp.customize() JavaScript snippet enqueued on the customize_preview_init action. Settings saved via the Customizer are stored as theme mods and are lost if you switch themes — if you need the data to survive a theme change, store it in a plugin option using update_option() instead.