How to add controls to InspectorControls in a block (JS) in WordPress

Contents

Overview

This article explains, in complete detail, how to add controls to InspectorControls in a WordPress block (JavaScript). It covers required imports, block attributes, the edit UI, common control components, grouping controls into panels, using advanced inspector controls, saving the result for the front-end, dynamic-rendered blocks, accessibility and best practices, and troubleshooting. Multiple working examples are included (editor and front-end) and each code example is provided using the required pre tag format.

Prerequisites

  • WordPress 5.0 (for modern block editor APIs). For best compatibility use a recent WP version.
  • Node.js and npm when developing using @wordpress/scripts and modern ESNext with JSX.
  • Basic knowledge of registerBlockType and React hooks is recommended.
  • Familiarity with block attributes and the difference between editor-only controls and saved output.

Concepts and key points

  • InspectorControls is a component from @wordpress/block-editor. Controls placed inside it appear in the block inspector (sidebar) when the block is selected.
  • Controls inside InspectorControls affect block attributes through setAttributes and therefore the editable block content or saved markup.
  • InspectorControls are editor-only. They do not render on the front-end. If you need visual changes on the front-end, you must use attributes and reflect them in the save() or the PHP render callback.
  • Useful control components are in @wordpress/components and include: PanelBody, ToggleControl, TextControl, RangeControl, SelectControl, ColorPalette, ColorPicker, FontSizePicker, BaseControl, PanelRow, and QueryControls.
  • Use InspectorAdvancedControls for placement under the Advanced panel (where additional fields like HTML anchor often go).
  • Always use useBlockProps to attach block classes and attributes in edit and save.

Basic imports and skeleton

A typical block using InspectorControls will import these essentials:

  • registerBlockType (or use block.json register automatically)
  • InspectorControls and useBlockProps from @wordpress/block-editor
  • PanelBody, ToggleControl, TextControl, RangeControl, etc. from @wordpress/components
  • __ from @wordpress/i18n for translations

Example skeleton of an edit component with InspectorControls:

import { __ } from @wordpress/i18n
import { useBlockProps, InspectorControls } from @wordpress/block-editor
import { PanelBody, ToggleControl, TextControl } from @wordpress/components

export default function Edit( { attributes, setAttributes } ) {
    const blockProps = useBlockProps()

    return (
        <>
            
                ltPanelBody title=Settingsgt
                    ltToggleControl
                        label=Show title
                        checked={ attributes.showTitle }
                        onChange={ ( value ) =gt setAttributes( { showTitle: value } ) }
                    /gt
                    ltTextControl
                        label=Subtitle
                        value={ attributes.subtitle }
                        onChange={ ( value ) =gt setAttributes( { subtitle: value } ) }
                    /gt
                lt/PanelBodygt
            

            ltdiv { ...blockProps } gt
                { attributes.showTitle  lth2gtExample Titlelt/h2gt }
                ltpgt{ attributes.subtitle }lt/pgt
            lt/divgt
        
    )
}

Important notes about the skeleton

  • InspectorControls can contain anything from the component library they must not appear in the save() output.
  • Use a Fragment (<>…) to include both InspectorControls and the visible editor UI in the edit() output.
  • Attributes must be registered for your block so changes persist.

Registering attributes

Attributes define the data a block stores. Define attributes for every control you put in InspectorControls. Typical attribute shapes include string, boolean, number, object. Store values either inline in comment attributes or in markup (source: text, selector). For many simple controls, use comment attributes (default).

registerBlockType( my-plugin/inspector-example, {
    title: Inspector Example,
    category: widgets,
    attributes: {
        subtitle: {
            type: string,
            default: A short subtitle,
        },
        showTitle: {
            type: boolean,
            default: true,
        },
        fontSize: {
            type: number,
            default: 16,
        },
        backgroundColor: {
            type: string,
            default: #ffffff,
        },
    },
    edit: Edit,
    save: Save,
} )

Basic example: ToggleControl and TextControl

This example creates two controls in InspectorControls: a ToggleControl toggling the title and a TextControl for a subtitle. The save function outputs HTML reflecting the attributes.

/
  Editor file: src/edit.js
 /
import { __ } from @wordpress/i18n
import { useBlockProps, InspectorControls } from @wordpress/block-editor
import { PanelBody, ToggleControl, TextControl } from @wordpress/components

export default function Edit( { attributes, setAttributes } ) {
    const { showTitle, subtitle } = attributes
    const blockProps = useBlockProps()

    return (
        <>
            
                ltPanelBody title={ __( Block Settings, text-domain ) } initialOpen={ true }gt
                    ltToggleControl
                        label={ __( Show title, text-domain ) }
                        checked={ showTitle }
                        onChange={ ( value ) =gt setAttributes( { showTitle: value } ) }
                    /gt
                    ltTextControl
                        label={ __( Subtitle, text-domain ) }
                        value={ subtitle }
                        onChange={ ( value ) =gt setAttributes( { subtitle: value } ) }
                    /gt
                lt/PanelBodygt
            

            ltdiv { ...blockProps } gt
                { showTitle  lth2gt{ __( My Block Title, text-domain ) }lt/h2gt }
                ltpgt{ subtitle }lt/pgt
            lt/divgt
        
    )
}

/
  Save file: src/save.js
 /
import { useBlockProps } from @wordpress/block-editor

export default function Save( { attributes } ) {
    const { showTitle, subtitle } = attributes
    const blockProps = useBlockProps.save()

    return (
        ltdiv { ...blockProps } gt
            { showTitle  lth2gtMy Block Titlelt/h2gt }
            ltpgt{ subtitle }lt/pgt
        lt/divgt
    )
}

Advanced controls and layout

When you need multiple controls and logical grouping, use PanelBody and PanelRow. Add panels for styles, colors, and advanced options. Use InspectorAdvancedControls for Advanced panel placement (below the main inspector sections).

import { InspectorControls, InspectorAdvancedControls } from @wordpress/block-editor
import {
    PanelBody,
    PanelRow,
    RangeControl,
    SelectControl,
    ColorPalette,
    ColorPicker,
    ToggleControl,
} from @wordpress/components

function Edit( { attributes, setAttributes } ) {
    const { fontSize, alignment, backgroundColor, showBorder } = attributes

    return (
        <>
            ltInspectorControlsgt
                ltPanelBody title=Typographygt
                    ltPanelRowgt
                        ltRangeControl
                            label=Font size
                            value={ fontSize }
                            onChange={ ( value ) =gt setAttributes( { fontSize: value } ) }
                            min={ 10 }
                            max={ 72 }
                        /gt
                    lt/PanelRowgt

                    ltPanelRowgt
                        ltSelectControl
                            label=Text alignment
                            value={ alignment }
                            options={ [
                                { label: Left, value: left },
                                { label: Center, value: center },
                                { label: Right, value: right },
                            ] }
                            onChange={ ( value ) =gt setAttributes( { alignment: value } ) }
                        /gt
                    lt/PanelRowgt
                lt/PanelBodygt

                ltPanelBody title=Colors initialOpen={ false }gt
                    ltPanelRowgt
                        ltColorPalette
                            value={ backgroundColor }
                            onChange={ ( color ) =gt setAttributes( { backgroundColor: color } ) }
                        /gt
                    lt/PanelRowgt
                lt/PanelBodygt
            lt/InspectorControlsgt

            ltInspectorAdvancedControlsgt
                ltToggleControl
                    label=Show border
                    checked={ showBorder }
                    onChange={ ( value ) =gt setAttributes( { showBorder: value } ) }
                /gt
            lt/InspectorAdvancedControlsgt

            ltdiv style={ {
                fontSize: fontSize   px,
                textAlign: alignment,
                backgroundColor: backgroundColor,
                border: showBorder ? 1px solid #ddd : none,
            } } gt
                Editable content here
            lt/divgt
        
    )
}

Dynamic rendering vs static save()

InspectorControls only appear in the editor. If your block is static, reflect attributes in save(). If you require dynamic server-side output (PHP rendering), register a render callback in PHP and pass attributes through the InspectorControls remain in the editor, but the final markup is produced by PHP.

/
  PHP registration example for dynamic rendering
 /
function my_plugin_register_dynamic_block() {
    register_block_type( __DIR__ . /build/, array(
        render_callback => my_plugin_render_dynamic_block,
    ) )
}
add_action( init, my_plugin_register_dynamic_block )

function my_plugin_render_dynamic_block( attributes, content ) {
    subtitle = isset( attributes[subtitle] ) ? esc_html( attributes[subtitle] ) : 
    show_title = isset( attributes[showTitle] )  attributes[showTitle] ? true : false
    font_size = isset( attributes[fontSize] ) ? intval( attributes[fontSize] ) : 16
    bg = isset( attributes[backgroundColor] ) ? esc_attr( attributes[backgroundColor] ) : #fff

    style = font-size: {font_size}px background-color: {bg}

    html = ltdiv class=my-plugin-block style=. style .>lt
    if ( show_title ) {
        html .= lth2gt. esc_html__( My Block Title, text-domain ) .lt/h2gt
    }
    html .= ltpgt . subtitle . lt/pgt
    html .= lt/divgt

    return html
}

Example: Using ColorPicker, RangeControl, and SelectControl

This sample demonstrates several controls together and how to apply the selected styles to both the editor preview and the saved markup.

/
  src/edit.js
 /
import { __ } from @wordpress/i18n
import { useBlockProps, InspectorControls } from @wordpress/block-editor
import { PanelBody, RangeControl, SelectControl, ColorPicker } from @wordpress/components

export default function Edit( { attributes, setAttributes } ) {
    const { fontSize, alignment, backgroundColor } = attributes
    const blockProps = useBlockProps( {
        style: {
            fontSize: fontSize ? fontSize   px : undefined,
            textAlign: alignment,
            backgroundColor: backgroundColor,
            padding: 12px,
        },
    } )

    return (
        <>
            ltInspectorControlsgt
                ltPanelBody title={ __( Style, text-domain ) } gt
                    ltRangeControl
                        label={ __( Font Size, text-domain ) }
                        value={ fontSize }
                        onChange={ ( value ) =gt setAttributes( { fontSize: value } ) }
                        min={ 10 }
                        max={ 72 }
                    /gt

                    ltSelectControl
                        label={ __( Alignment, text-domain ) }
                        value={ alignment }
                        onChange={ ( value ) =gt setAttributes( { alignment: value } ) }
                        options={ [
                            { label: Left, value: left },
                            { label: Center, value: center },
                            { label: Right, value: right },
                        ] }
                    /gt

                    ltColorPicker
                        color={ backgroundColor }
                        onChangeComplete={ ( color ) =gt setAttributes( { backgroundColor: color.hex } ) }
                        disableAlpha
                    /gt
                lt/PanelBodygt
            lt/InspectorControlsgt

            ltdiv { ...blockProps } gt
                lth3gtPreview Textlt/h3gt
                ltpgtThis area reflects your InspectorControls selection.lt/pgt
            lt/divgt
        
    )
}

/
  src/save.js
 /
import { useBlockProps } from @wordpress/block-editor

export default function Save( { attributes } ) {
    const { fontSize, alignment, backgroundColor } = attributes
    const blockProps = useBlockProps.save( {
        style: {
            fontSize: fontSize ? fontSize   px : undefined,
            textAlign: alignment,
            backgroundColor: backgroundColor,
            padding: 12px,
        },
    } )

    return (
        ltdiv { ...blockProps } gt
            lth3gtSaved Block Titlelt/h3gt
            ltpgtSaved content reflecting inspector settings.lt/pgt
        lt/divgt
    )
}

Inspector controls that require asynchronous data: QueryControls and special cases

Some controls interact with the REST API or dynamic data. For example, QueryControls allow selecting posts or categories. Use useSelect or the higher-order withSelect where needed. Keep performance in mind and avoid heavy requests on every render. Use useEffect to fetch only when necessary.

import { useSelect } from @wordpress/data
import { QueryControls } from @wordpress/components
import { useEntityRecords } from @wordpress/core-data

function PostSelectorControl( { value, onChange } ) {
    const posts = useSelect( ( select ) =gt select( core ).getEntityRecords( postType, post, { per_page: 10 } ), [] )

    return (
        ltQueryControls
            numberOfItems={ posts ? posts.length : 0 }
            isLoading={ ! posts }
            onQueryChange={ ( query ) =gt {
                // Implement filtering logic or request more items
            } }
        /gt
    )
}

Best practices

  1. Group logically: place related settings into PanelBody sections (e.g., Typography, Colors, Layout).
  2. Use sensible defaults: attributes should have defaults so the UI is predictable.
  3. Persist only what you need: avoid storing derived state in attributes — compute it where possible.
  4. Use useBlockProps: this ensures block classes and wrappers are consistent in both editor and front-end.
  5. Don’t put editor-only content into save(): InspectorControls belong only in the editor. Save should output markup based on attributes.
  6. Accessibility: use labels on controls and ensure contrast for color pickers or previews.
  7. Performance: avoid heavy fetches in edit() without memoization. Use useSelect/useEffect carefully.
  8. Internationalization: wrap admin-facing strings with __() or sprintf() as appropriate.

Common mistakes and troubleshooting

  • InspectorControls not showing: ensure your edit() returns a Fragment with InspectorControls and that the block is selected. Also check imports: it must come from @wordpress/block-editor.
  • Attribute changes not persisting: confirm attributes are registered in registerBlockType and that you call setAttributes with the correct keys.
  • Editor preview not styled like saved markup: ensure you pass inline styles or classes from attributes to both edit and save using useBlockProps and consistent rendered HTML.
  • Control UI not updating: check that the controls value prop is wired to attributes and that onChange calls setAttributes with the appropriate type (cast numbers from strings when needed).
  • Using InspectorControls in save(): it will not display in the front-end and should not be used there add UI only in edit().

Extra tips

  • Use InspectorAdvancedControls when you want items in the Advanced panel (below the main panels).
  • Preview changes visually in the editor by applying inline styles or classes bound to attributes if using dynamic PHP rendering, mirror the same logic there.
  • When working with color values, consider offering presets as well as free color picker choices.
  • For complex controls, create small subcomponents inside your block to keep edit() tidy and re-usable.
  • Test mobile and responsive behaviors since some controls like font size may need breakpoints.

Complete practical example: Registering a block with InspectorControls and PHP render

This final example demonstrates a full workflow: block registration (JS), editor (InspectorControls with multiple controls), and server-side rendering via PHP.

/
  src/index.js
 /
import { registerBlockType } from @wordpress/blocks
import Edit from ./edit
import ./style.scss

registerBlockType( my-plugin/advanced-inspector-block, {
    apiVersion: 2,
    title: Advanced Inspector Block,
    category: design,
    attributes: {
        subtitle: { type: string, default: Editable subtitle },
        showTitle: { type: boolean, default: true },
        fontSize: { type: number, default: 18 },
        backgroundColor: { type: string, default: #ffffff },
        align: { type: string, default: left },
    },
    edit: Edit,
    save: () =gt null, // use server-side render
} )
/
  src/edit.js
 /
import { __ } from @wordpress/i18n
import { useBlockProps, InspectorControls, InspectorAdvancedControls } from @wordpress/block-editor
import { PanelBody, ToggleControl, TextControl, RangeControl, SelectControl, ColorPalette } from @wordpress/components

export default function Edit( { attributes, setAttributes } ) {
    const { subtitle, showTitle, fontSize, backgroundColor, align } = attributes
    const blockProps = useBlockProps( {
        style: {
            fontSize: fontSize   px,
            backgroundColor,
            textAlign: align,
            padding: 12px,
        },
    } )

    const colors = [
        { name: White, color: #ffffff },
        { name: Light Gray, color: #f0f0f0 },
        { name: Beige, color: #f5f0e1 },
        { name: Blue, color: #e6f0ff },
    ]

    return (
        <>
            ltInspectorControlsgt
                ltPanelBody title={ __( Content, text-domain ) } initialOpen={ true }gt
                    ltTextControl
                        label={ __( Subtitle, text-domain ) }
                        value={ subtitle }
                        onChange={ ( val ) =gt setAttributes( { subtitle: val } ) }
                    /gt
                    ltToggleControl
                        label={ __( Show Title, text-domain ) }
                        checked={ showTitle }
                        onChange={ ( val ) =gt setAttributes( { showTitle: val } ) }
                    /gt
                lt/PanelBodygt

                ltPanelBody title={ __( Style, text-domain ) } initialOpen={ false }gt
                    ltRangeControl
                        label={ __( Font size, text-domain ) }
                        value={ fontSize }
                        onChange={ ( val ) =gt setAttributes( { fontSize: val } ) }
                        min={ 12 }
                        max={ 48 }
                    /gt>

                    ltSelectControl
                        label={ __( Text Align, text-domain ) }
                        value={ align }
                        options={ [
                            { label: Left, value: left },
                            { label: Center, value: center },
                            { label: Right, value: right },
                        ] }
                        onChange={ ( val ) =gt setAttributes( { align: val } ) }
                    /gt

                    ltdiv style={ { marginTop: 12px } }gt
                        ltlabel style={ { display: block, marginBottom: 8px } }gtBackgroundlt/labelgt
                        ltColorPalette colors={ colors } value={ backgroundColor } onChange={ ( val ) =gt setAttributes( { backgroundColor: val } ) } /gt
                    lt/divgt
                lt/PanelBodygt
            lt/InspectorControlsgt

            ltInspectorAdvancedControlsgt
                ltdivgt
                    ltsmallgtAdvanced options could go herelt/smallgt
                lt/divgt
            lt/InspectorAdvancedControlsgt

            ltdiv { ...blockProps } gt
                { showTitle  lth2gt{ __( Block Title, text-domain ) }lt/h2gt }
                ltpgt{ subtitle }lt/pgt
            lt/divgt
        
    )
}
/
  server-side render in PHP: my-plugin.php
 /
function my_plugin_register_block() {
    register_block_type( __DIR__ . /build, array(
        render_callback =gt my_plugin_render_block,
    ) )
}
add_action( init, my_plugin_register_block )

function my_plugin_render_block( attributes ) {
    subtitle = isset( attributes[subtitle] ) ? wp_kses_post( attributes[subtitle] ) : 
    show_title = ! empty( attributes[showTitle] ) ? true : false
    font_size = ! empty( attributes[fontSize] ) ? intval( attributes[fontSize] ) : 18
    bg = ! empty( attributes[backgroundColor] ) ? esc_attr( attributes[backgroundColor] ) : #ffffff
    align = ! empty( attributes[align] ) ? esc_attr( attributes[align] ) : left

    style = sprintf( font-size:%spxbackground-color:%stext-align:%spadding:12px, font_size, bg, align )

    ob_start()
    ?>
    
>

Useful links and references

Summary

InspectorControls are the correct place for settings that affect the block but are not part of the inline editable content. Use the components in @wordpress/components to build a friendly settings UI. Keep attributes well-defined, use useBlockProps, group controls into PanelBody blocks, and remember that InspectorControls are editor-only. For front-end output, either reflect attributes in save() or implement server-side rendering in PHP.



Acepto donaciones de BAT's mediante el navegador Brave :)



Leave a Reply

Your email address will not be published. Required fields are marked *