WordPress.org

WordPress Developer Blog

How to extend WordPress via the SlotFill system

WordPress developers have relied on Hooks API since version 2.0 to extend the functionality of WordPress and until WordPress 5.0, it was the only way. 

In WordPress 5.0, the WordPress SlotFill system was introduced as part of the Gutenberg project and is the extension paradigm that allows developers to extend the UIs introduced with the Gutenberg project such as the Post Editor and the Site Editor.

At a high level, the SlotFill system is an extension paradigm that allows developers to register plugins containing content, or Fills, to be displayed in a specific location, or Slot, in the UI. 

How does it compare to Hooks?

Historically, if a developer wanted to add UI elements to the WordPress admin, there would be an Action exposed that they could “hook” into.

For example, if the edit_form_after_title action was leveraged, it was possible to add a secondary title to the existing post-edit screen:

add_action(
   edit_form_after_title',
   function() {
       echo ''<input type="text" class="widefat" placeholder="Add secondary title" />';
   }
);

Which would display as:

This was possible due to the action being exposed by calling do_action() in a specific location in the files that are loaded to render the edit post screen.

do_action( 'edit_form_after_title', $post );

Based on this, an Action can be viewed as having three steps:

  1. A location is exposed for extension via a do_action() call in WordPress core.
  2. A developer creates a plugin ( or theme ) that contains a corresponding add_action() call.
  3. WordPress loads the page and when the do_action is run, the custom code from the plugin is run in the location where do_action was called.

SlotFill is similar to actions in the sense that they are also location-based. The actual implementation is much different, but the steps are very similar in concept

  1. A Slot is exposed somewhere in WordPress core.
  2. A developer registers a plugin with wp.plugins.registerPlugin() which contains a Fill for a corresponding Slot.
  3. WordPress loads the page where the Slot is exposed and The SlotFill system renders the Fill content in the Slot location.

The pieces of the puzzle

The SlotFill system consists of five pieces that work together to render Fill content in Slot locations:

  1. Slot component
  2. Fill component
  3. SlotFillProvider component
  4. registerPlugin()
  5. PluginArea component.

Slot

The Slot component is used to determine where in the UI the extension point will be exposed. Wherever this component is rendered is where the associated Fill components content will be displayed.

Slot accepts the following props:

  • name:  The name of the Slot.
  • fillProps:  Object that is passed to the Fill.
  • bubblesVirtually:  Changes event bubbling behavior
import { Slot } from '@wordpress/components';
<Slot
    name="my-slot-name"
    fillProps={ { key: 'value' } }
    bubblesVirtually
/>

Fill

The Fill component is used to provide content to a Slot with the same name property.

This component can be rendered anywhere inside the UI – even in a completely different element tree. The contents of the Fill will be rendered in the Slot with the same name property. Regardless of where the Fill is rendered.

Fill accepts a single prop:

  • name: The name of the Slot that this Fill is targeting.
import { Fill } from '@wordpress/components';
 
<Fill name="my-slot-name">
    Fill Contents
</Fill>

SlotFillProvider

The SlotFillProvider component is the magical glue that connects Fills to their Slots.

It wraps the UI and its job is to detect any Slots or Fills anywhere inside of it or any of its child components and then render the Fill content in the associated Slot location. This component does not accept any props.

registerPlugin()

The only item in the list that is not a React component, this function is used to register a plugin that contains one for more fills. It is part of the @wordpress/plugins package and it accepts two parameters:

  • name A string identifying the plugin
  • settings: Object containing settings for the plugin
    • render: The component to render
    • icon: A visual asset to be associated with the plugin
    • scope: The scope this plugin belongs to. This is only used for custom implementations and should be left undefined

For more information on registerPlugin and the other functions provided by this package, please refer to the official documentation on the plugins module.

import { registerPlugin } from '@wordpress/plugins';

registerPlugin( 'example-plugin', {
    render: ComponentToRender,
    icon: 'smiley',
} );

PluginArea

Finally, there is the PluginArea component. Its job is to retrieve all of the registered plugins from the plugins API and render them internally inside a hidden div element. 

The PluginArea component accepts the following props:

  • scope: Scope for the plugin area. Registered plugins must match the scope to be rendered. This is currently not defined in existing implementations
  • onError: Function to handle errors
import { PluginArea } from '@wordpress/plugins';
 
<PluginArea
    scope="custom-scope"
    onError={ onErrorHandler }
/>;

Putting it together

Both the Post Editor and Site Editor screens implement a SlotFill system. At a high level, it looks like this:

  • A SlotFillProvider wraps the EditorProvider component.
  • Various Slot components are exposed in the Layout component.
  • Fill components are added using the registerPlugin() function.
  • The Fill components are rendered in hidden div by the PluginArea component.
  • Fill content is rendered in the associated Slot location.
// Pseudo code for edit-post
<SlotFillProvider>
    <EditorProvider>
        <Layout>
            { /* Slots are exposed in various locations */ }
            <PluginArea />
        </Layout>
    </EditorProvider>
</SlotFillProvider>;

A visual representation

In the visual representations below, it can be seen how the system works together. Plugins are registered using registerPlugin() and added to the list of plugins that have been registered. The PluginArea component retrieves the the plugins and renders each plugins render property that contains Fill components into a hidden div.

Once the PluginArea renders the plugins, the SlotFillProvider then detects that there are FIlls available and renders their content in the association Slot location.

How SlotFills are built

So far in this post, examples have focused on the basic Slot component.

However, the available SlotFills are not just simple components. They are named components that usually contain other functionality and inner components. 

In this example, you can view how the PluginPostStatusInfo SlotFill is structured. 

const PluginPostStatusInfo = ( { children, className } ) => (
    <Fill>
        <PanelRow className={ className }>{ children }</PanelRow>
    </Fill>
);

PluginPostStatusInfo.Slot = Slot;

export default PluginPostStatusInfo;

Notice that the exported SlotFill component contains both the Slot and the Fill. Hence the name of the component!

Exposing a Slot

Each SlotFIll is exposed in the UI using its dot-Slot property.

In this simplified example, we see how the PluginPostStatusInfo Slot is exposed inside the PostStatus component. The full source is available in the WordPress Gutenberg repository on GitHub.

function PostStatus( { isOpened, onTogglePanel } ) {
    return (
        <PanelBody
            className="edit-post-post-status"
            title={ __( 'Summary' ) }
            opened={ isOpened }
            onToggle={ onTogglePanel }
        >
            <PluginPostStatusInfo.Slot/>
        </PanelBody>
    );
}

Registering a Fill

Now that the Slot has been exposed, we can use registerPlugin to add a Fill. 

In the example below, the PluginPostStatusInfo SlotFill is imported from the wordpress/edit-post package and is used in our render property. 

import { registerPlugin } from '@wordpress/plugins';
import { PluginPostStatusInfo } from '@wordpress/edit-post';

registerPlugin('example-plugin', {
    render: () => (
        <PluginPostStatusInfo>
            <p>Post Status info SlotFill</p>
        </PluginPostStatusInfo>
    ),
    icon: 'smiley'
})

Any children of the PluginPostStatusInfo component will be rendered in the associated Slot location.

Currently available SlotFills

As of WordPress 6.1, there are a total of 12 available slots, nine in the Edit Post screen and three in the Site Editor screen. These are well-documented in the block editor handbook and include examples.

Edit Post Screen

These Slots are part of the @wordpress/edit-post package.

import {
    PluginPostStatusInfo
    PluginPrePublishPanel
    PluginPostPublishPanel
    PluginMoreMenuItem
    PluginBlockSettingsMenuItem
    PluginSidebar
    PluginSidebarMoreMenuItem
    PluginDocumentSettingPanel
    __experimentalMainDashboardButton as MainDashboardButton,
} from '@wordpress/edit-post';

Site Editor screen

These Slots are part of the @wordpress/edit-site package. These slots link to the same documentation as their counterparts in the Edit Post screen. The difference is where the slot is exposed.

import {
    PluginMoreMenuItem,
    PluginSidebar,
    __experimentalMainDashboardButton as MainDashboardButton,
} from '@wordpress/edit-site';

Resources to learn more

Thanks to @bph, @bcworkz and @webcommsat for reviewing this article.

Leave a Reply

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