You’ve been using SlotFills every time you’ve added a panel to the block settings sidebar or a button to the block toolbar. Here’s what’s actually happening, how it compares to the PHP hooks you already know, and how to drop your own controls into blocks you didn’t build.
Open any block in the editor, click into the settings sidebar on the right, glance at that little floating toolbar above the block. You’re looking at SlotFills. If you’ve built a custom block, you’ve already written Fills without necessarily knowing that’s what they were called.
SlotFill is one of those parts of the block editor that sounds advanced and abstract right up until you realize you’ve been using it the whole time. Let’s close the gap between using it and understanding it, and then do something practical with it.
What a SlotFill actually is
A SlotFill has two parts: a Slot and a Fill.
A Slot marks a spot in the editor’s interface where content is allowed to appear. A Fill is the content you put into a named Slot.
They’re connected by a shared name, and the magic is that they don’t have to live anywhere near each other in the React tree. You can render a Fill deep inside your block’s code, and its contents show up in a Slot that core rendered somewhere completely different (the sidebar, the toolbar, a menu). Under the hood that’s React portal rendering, but you rarely have to think about it.
The mental model is simple: core says “here’s a place you’re allowed to add UI” (the Slot), and you say “put this here” (the Fill).
SlotFills are similar to PHP hooks
If you’re coming from classic WordPress development, this should feel familiar, because it’s the same shape as do_action() and add_action(). Core exposes a location, you hook something into it, and WordPress renders your thing in that location.
// Core exposes a location:
do_action( 'edit_form_after_title', $post );
// You hook into it:
add_action( 'edit_form_after_title', function () {
echo '<p>Something extra under the title.</p>';
} );A Slot is the do_action of the editor UI. A Fill is your add_action. Same idea, different era.
That said, SlotFills and JavaScript hooks are not the same thing.
@wordpress/hooks is a tiny, standalone pub/sub library. It’s the JavaScript port of the PHP hooks API, and if you read its source there’s no React in it and nothing that references the SlotFill code at all. SlotFill lives in @wordpress/components and is pure React (context plus portals).
That distinction matters because it tells you where each one fits. The hooks API filters data and fires events. SlotFill moves rendered UI around. And the interesting part (we’ll get to it below) is the one spot where you reach for both at the same time.
The two SlotFills you use every day
You’ve probably used a couple of these already if you do block work::
InspectorControls: the block settings sidebar (the “Block” tab on the right).BlockControls: the floating toolbar that appears above a selected block.
Both come from @wordpress/block-editor. When you render them inside your block’s edit component, you’re not really rendering them “there.” You’re filling a Slot that the editor exposed elsewhere.
import {
InspectorControls,
BlockControls,
useBlockProps,
} from '@wordpress/block-editor';
import {
PanelBody,
ToggleControl,
ToolbarGroup,
ToolbarButton,
} from '@wordpress/components';
export default function Edit( { attributes, setAttributes } ) {
const { featured } = attributes;
const toggleFeatured = () => setAttributes( { featured: ! featured } );
return (
<>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon="star-filled"
label="Feature this"
isPressed={ featured }
onClick={ toggleFeatured }
/>
</ToolbarGroup>
</BlockControls>
<InspectorControls>
<PanelBody title="Display Settings">
<ToggleControl
label="Featured"
checked={ featured }
onChange={ toggleFeatured }
/>
</PanelBody>
</InspectorControls>
<div { ...useBlockProps() }>
{ /* your block's editable content */ }
</div>
</>
);
}A couple of things worth pointing out here:
The position of <InspectorControls> and <BlockControls> in your JSX doesn’t matter. You can put them first, last, or in the middle of your block’s markup, and the toggle still lands in the sidebar while the button still lands in the toolbar. That’s the Fill doing its job: the content gets portaled to wherever the matching Slot lives.
And notice there’s no wiring. You don’t register anything, you don’t import a provider, you don’t pass a name. The editor already rendered the InspectorControls and BlockControls Slots for you, so all you do is render the Fill.

Adding controls to a block you didn’t build
Here’s where things get interesting, and where the hooks API and SlotFill finally show up in the same place.
Rendering <InspectorControls> inside your own block is easy because you control that edit function. But what about a block you don’t own, like core’s Paragraph or Image? You can’t edit core’s edit component. This is a common spot for theme and plugin developers: you want to bolt a setting onto an existing block without rebuilding it. (A classic example is “hide this block on mobile,” the kind of per-block control visibility plugins add to every block in the editor.)
The answer is the editor.BlockEdit filter. It lets you wrap any block’s edit component with your own, and inside that wrapper you render your Fills.
import { addFilter } from '@wordpress/hooks';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';
jsximport { createHigherOrderComponent } from '@wordpress/compose';
const withVisibilityControl = createHigherOrderComponent( ( BlockEdit ) => ( props ) => {
// Only touch the block(s) you care about.
if ( props.name !== 'core/paragraph' ) {
return <BlockEdit { ...props } />;
}
const { attributes, setAttributes } = props;
return (
<>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody title="Visibility">
<ToggleControl
label="Hide on mobile"
checked={ !! attributes.hideOnMobile }
onChange={ ( value ) =>
setAttributes( { hideOnMobile: value } )
}
/>
</PanelBody>
</InspectorControls>
</>
);
}, 'withVisibilityControl' );
addFilter( 'editor.BlockEdit', 'lwptd/visibility-control', withVisibilityControl );The screenshot below is that snippet running against core’s Paragraph block. There’s now a “Visibility” panel with a “Hide on mobile” toggle in the sidebar, on a block we never wrote.

To make this do something you’d register the hideOnMobile attribute (via the blocks.registerBlockType filter) and output a class on the front end. A single Slot can receive Fills from lots of different places, which is what makes these controls so extensible.
Rolling your own SlotFill
Everything so far has been about filling Slots that core already exposes. You can also expose your own Slots, which is how a plugin lets other code (or a separate add-on) drop UI into its interface. The createSlotFill helper hands you a matched pair in one call.
import { createSlotFill } from '@wordpress/components';
const { Slot, Fill } = createSlotFill( 'MyPluginSettings' );
const MyPluginSettings = ( { children } ) => <Fill>{ children }</Fill>;
MyPluginSettings.Slot = Slot;
export default MyPluginSettings;You render <MyPluginSettings.Slot /> wherever you want the injected content to land, then anywhere else (even in a different plugin) you render <MyPluginSettings>...</MyPluginSettings> to fill it. That’s the whole idea behind “freemium” plugins that reveal extra panels once a license is active.
In practice you may not need to use these too often unless you’re making a plugin for the mass market, but I’ve linked a deeper walkthrough in the further reading in case you’re curious.
A few important tips for InspectorControls and BlockControls
Both Slots support groups. InspectorControls takes a group prop (for example group="styles" or group="advanced") to target a specific area of the sidebar, and BlockControls has groups too (block, inline, other) for positioning your button in the toolbar. Reach for these when your control feels like it belongs in a specific spot rather than the default panel.
SlotFill isn’t just for blocks. There’s a whole document-level layer powered by @wordpress/plugins and registerPlugin, which gives you Slots like PluginSidebar, PluginDocumentSettingPanel, and the newer PluginPreviewMenuItem (for adding your own entry to the editor’s Preview dropdown). That’s the path when you want plugin UI that isn’t tied to a single block.
Wrapping up
SlotFill stops being intimidating the moment you connect it to something you already do. A Slot is a place core lets you add UI; a Fill is the UI you add. InspectorControls and BlockControls are the two you’ll use most, and the editor.BlockEdit filter is how you bring them to blocks you didn’t write (the one spot where the hooks API and SlotFill team up).
Next time we’ll take that visibility toggle and make it real: registering the attribute so it saves, and outputting a class on the front end so “Hide on mobile” actually hides something.
Happy coding!
Further reading
- SlotFill component reference (Block Editor Handbook)
- SlotFills Reference: the full list of available Slots (Block Editor Handbook)
@wordpress/hookspackage reference (the JavaScript hooks API)- How to extend WordPress via the SlotFill system by Ryan Welcher (WordPress Developer Blog)
- Extending plugins using custom SlotFills (creating your own Slots, WordPress Developer Blog)
- How to add custom entries to the editor Preview dropdown (PluginPreviewMenuItem, WordPress Developer Blog)
- Lesson 8: SlotFill (10up Block Editor Best Practices)
- A primer on WordPress SlotFill technology by Nick Diego