Add a Custom Inspector Sidebar in WordPress Gutenberg with Post Meta

In this tutorial we’ll look into how to add a custom sidebar to the Inspector (right sidebar) in WordPress Gutenberg. Inside we’ll implement a field connected to a post meta. Everything is implemented with Javascript, inside the Gutenberg editor, and is an alternative to adding metaboxes the traditional way.

What we will create

WordPress Gutenberg allows developers to create custom sidebars by using creating a so-called Javascript plugin. (Not to be confused with traditional WordPress plugins). By using a Javascript plugin we can create a new sidebar, and also plug into the “Tools and Options” menu.

We will add a new menu item in the “Tools” which will open up our custom sidebar. The content of the sidebar is entirely up to you, but I’ll go through some examples, including how to connect a post meta field inside it.

Setting up your Javascript

For this tutorial I will write ES6/ESNext Javascript with JSX which means we need to set up a compiler. If you want to follow along and not write ES5 Javascript you will need to set up a development environment that compiles your Javascript as you go. If you are not familiar with this, follow my post explaining how to set this up, and then come back here.

I have set up my webpack.config.js to compile my source Javascript file into my theme-folder’s /assets/js/sidebar-plugin.js. This compiled file is what we need to enqueue to the Gutenberg editor. While I write this code I have started the run-time compiler that recompiles the Javascript everytime I save changes in the source code (npm run start). All of this is explained in-depth in the previously mentioned tutorial.

I am writing this in a theme, but it works the same in a plugin. Just remember to adjust the paths when adding the script to the editor.

Adding the script to the editor

For adding our script to the Gutenberg editor we need to write some PHP. If you are in a theme, functions.php is a good place to start, or somewhere in your plugin files. Note that we need to add the final compiled script, not the source code. In my example the compiled Javascript is located in my theme-folder’s /assets/js/sidebar-plugin.js.

We create a function hooked onto enqueue_block_editor_assets. Inside we do the usual wp_enqueue_script(). As dependencies to the script we add two; wp-plugins, and wp-edit-post. These two packages must be loaded before our script as we use functions from them.

add_action('enqueue_block_editor_assets', function() {
	wp_enqueue_script('awp-sidebar-plugin', get_stylesheet_directory_uri() . '/assets/js/sidebar-plugin.js', ['wp-plugins', 'wp-edit-post']);
});

Because we enqueue the script in the hook enqueue_block_editor_assets our script will only be loaded when the Gutenberg editor is active. Great! Now everything is ready to write our Javascript plugin.

Register a Javascript plugin

The first step is registering a plugin. We do this with the registerPlugin function in wp.plugins from inside the wp.plugins package.

As parameter to the registerPlugin we provide a name (make sure it’s unique), and an object with settings. As minimum you need to provide a component for the render property. You can also optionally add an icon from WordPress’ dashicons. Keep in mind that if you don’t specify an icon it will fall back to the plugin icon. For demonstration purposes I’m using the 'carrot' icon.

As for the component we start by defining a basic component that returns a simple div with some random text in it. And before all the code we destructure the functions from the packages we want to use.

const { registerPlugin } = wp.plugins;

const CustomSidebarComponent = () => {
	return(
		<div>Hey!</div>
	);
}

registerPlugin('awp-customsidebar', {
	render: CustomSidebarComponent,
	icon: 'carrot'
});

If nothing happens (not even errors) when you refresh Gutenberg we are all good to go. We don’t see our component because we haven’t told WordPress where to render it. The next step is hooking onto the custom sidebar menu and tools menu components for rendering our stuff.

Register a custom sidebar

But first an explanation in how Gutenberg handles custom sidebars and why we need to do what we will do. When we successfully register a custom sidebar one thing will automatically happen initially. Gutenberg will add a shortcut to our sidebar in the top toolbar, next to the Tools menu. This happens because our sidebar automatically gets “pinned”.

The problem of just adding a custom sidebar is that any user can “unpin” this shortcut. When they do that, there is no access point to open the sidebar again! This is why we need to also add a menu item to the Tools menu.

Registering a custom sidebar is done with the component PluginSidebar from the wp.editPost package. Adding a menu item to the Tools menu is done with the aptly named component PluginSidebarMoreMenuItem (also in the wp.editPost package).

For the PluginSidebar component we need to provide some props. You should provide as minimum name and title. The prop title is self-explanatory, this is the name that will appear in the top of the sidebar. In the prop name provide an unique slug. When you add the menu item you need to refer to this slug.

Adding a PluginSidebarMoreMenuItem (tools menu) component requires as minimum one prop; target. Here you provide the same name as you gave in the sidebar’s name prop. As content of the component you write the text that will be displayed as menu item. Usually this would be the same as the sidebar’s title.

Because React requires a single wrapping node around the component’s return, we wrap everything inside a Fragment component (from the wp.element package). I am also wrapping my strings in __() from the wp.i18n package to allow for translation.

const { registerPlugin } = wp.plugins;
const { __ } = wp.i18n;
const { Fragment } = wp.element;
const { PluginSidebarMoreMenuItem, PluginSidebar } = wp.editPost;

const CustomSidebarComponent = () => {
    return(
        <Fragment>
            <PluginSidebar 
                name="awp-custom-sidebar" 
                title={__('My Custom sidebar', 'awp')}
            >Hello there.</PluginSidebar>
            <PluginSidebarMoreMenuItem 
                target='awp-custom-sidebar'
                >{__('My Custom sidebar', 'awp')}</PluginSidebarMoreMenuItem>
        </Fragment>
    );
}

registerPlugin('awp-customsidebar', {
	render: CustomSidebarComponent,
	icon: 'carrot'
});

With the above code we will get the following two (most likely three) things happening. You will get a carrot icon shortcut in the toolbar (if you haven’t unpinned it yet). Clicking on this will open up the sidebar. You will also have got a new menu item to the sidebar in the Tools Menu, under the headline “Plugins”.

Adding content to the sidebar

The actual content of the sidebar is entirely up to you. You just need to add HTML or components inside the PluginSidebar component with whatever you want (in place of the “Hello there.” text).

You might notice that the sidebar content is missing padding. This is intentional because the idea is that you will create panels inside this sidebar. Panels are the collapsible sections in the normal Gutenberg sidebar. Those are components that you can very easily add inside your custom sidebar. You can add as many panels as you want to and they are great for grouping different things up.

Adding panels into our sidebar

Let’s quickly look at how we can add panels to our sidebar. If you have created your own custom block types you might be familiar with these components. We use PanelBody and optionally PanelRow from the wp.components package.

For a PanelBody component you provide as minimum the prop title. You can optionally provide true or false to the initialOpen prop to decide if the panel should be expanded as default or not. As content inside the component is everything inside the panel.

Inside a PanelBody you can optionally use PanelRow components. They are not really necessary but can help you get a good styling for your panel content.

For example’s sake let’s add two panels to our custom sidebar.

const { registerPlugin } = wp.plugins;
const { __ } = wp.i18n;
const { Fragment } = wp.element;
const { PluginSidebarMoreMenuItem, PluginSidebar } = wp.editPost;
const { PanelBody, PanelRow } = wp.components;

const CustomSidebarComponent = () => {
	return(
		<Fragment>
			<PluginSidebarMoreMenuItem 
				target='awp-custom-sidebar'
				>{__('My Custom sidebar', 'awp')}</PluginSidebarMoreMenuItem>
			<PluginSidebar 
				name="awp-custom-sidebar" 
				title={__('My Custom sidebar', 'awp')}
			>                
				<PanelBody
					title={__('This is a panel section', 'awp')}
					initialOpen={true}
				>
					<PanelRow>
						Put any component or content here.
					</PanelRow>
				</PanelBody>
				<PanelBody
					title={__('Another section', 'awp')}
					initialOpen={false}
				>
					<PanelRow>
						This is a collapsed section by default.
					</PanelRow>
				</PanelBody>
			</PluginSidebar>
		</Fragment>
	);
}

registerPlugin('awp-customsidebar', {
	render: CustomSidebarComponent,
	icon: 'carrot'
});

With this code our custom sidebar starts to really look like it belongs in Gutenberg!

Adding post meta fields to the custom sidebar

Now we’re really getting into the meat of the useful stuff for a custom sidebar; adding settings and storing their values. But first we need to understand how.

It’s really easy to add setting fields (inputs, checkboxes, toggle fields, etc) in the sidebar by using the ready-to-use components in WordPress’ packages. However we need to consider how to store the values. The obvious option is storing them as post meta. But connecting a setting to a post meta field requires some extra code. More specifically we need to use so-called higher-order-components in order to fetch and update post meta inside Javascript. We also need to register each post meta using PHP and make them available to the REST API.

To close of this tutorial I will demonstrate how to add a post meta with key awp_my_custom_meta that should be stored as either true or false. In the sidebar it will be displayed as a toggle field (a true/false component).

Register post meta for REST API

The first step to adding post meta in our Javascript file is registering them and defining them as accessible in REST API. To do this we return to PHP again.

Inside a function hooked to the init action we use the register_meta() function. As parameters we need to define the object type as ‘post‘ because we’re using it as post meta (it doesn’t exclude e.g. page post type). We provide the meta key, and then an array of arguments. The critical thing to add in the arguments array is setting ‘show_in_rest‘ to true. Post meta is commonly used as ‘single‘ (think how you use get_post_meta()). We also define the type. As for our case we set it to ‘boolean‘.

add_action('init', function() {
	register_meta('post', 'awp_my_custom_meta', [
		'show_in_rest' => true,
		'single' => true,
		'type' => 'boolean'
	]);
});

With this code the ‘awp_my_custom_meta‘ post meta field is accessible from Gutenberg. You need to repeat the register_meta() for each post meta you wish to add in your sidebar.

Now let’s return to our Javascript.

Adding a setting to our sidebar

Let’s start with the easy part: Adding the input to our sidebar. Later on we will add more code to actually connect the setting field to post meta. Because of the way we need to do this, we define a separate new component for our setting. So inside the panel you want to add your settings to you simply call this new component.

Let’s call the component CustomSidebarMetaComponent (you might call it something relevant to your project). Inside this component we want to render a toggle field. To do this we use the ToggleControl component from the wp.components package. As props to the ToggleControl we provide a fitting label in the title prop. A ToggleControl also need the props onChange for updating the value and checked for the current value. We will omit these two for now until the next step.

...
const { PanelBody, PanelRow, ToggleControl } = wp.components;

const CustomSidebarMetaComponent = () => {
	return(
		<ToggleControl
	        label={__('My custom post meta', 'awp')}
	    />
	);
}

const CustomSidebarComponent = () => {
	return(
		...       
		<PanelBody
			title={__('This is a panel section', 'awp')}
			initialOpen={true}
		>
			<PanelRow>
				<CustomSidebarMetaComponent />
			</PanelRow>
		</PanelBody>
		...

With the above code you should get a toggle field in the sidebar. It is unchecked and nothing happens when you click on it. That’s the next step: connecting it to our post meta.

Accessing post meta with withSelect and withDispatch

In order to access as well as update a post meta value we need to use higher-order-components (HOC) to access WordPress’ version of stores (similar to Redux). WordPress provides us with some useful HOC with functions we can use in the wp.data package.

The higher-order component withSelect is provide our component with props that are, in our case, fetched from post meta. We use this to get the value of our post meta. Inside withSelect we can use select('core/editor').getEditedPostAttribute('meta') to fetch the current post’s meta.

On the other hand, withDispatch is a higher-order component that can perform actions. In our case we want to update the post meta when the corresponding field setting is changed in our sidebar. Inside this component we use dispatch('core/editor').editPost() to inform WordPress to dispatch an action. Inside the object we provide we tell WordPress that it’s meta we want to update.

Finally we need to combine withSelect and withDispatch with our component that handles the post meta field (CustomSidebarMetaComponent). To do this we use WordPress’ compose from the wp.compose package. The idea is that withSelect and withDispatch provides our CustomSidebarMetaComponent component with props. withSelect provides the value of the post meta as prop, and withDispatch provides a function that we can call to update the value as prop. We set these props to our ToggleField checked and onChange, correspondingly.

That’s a lot of explaining. Let’s look at the actual code:

...
const { compose } = wp.compose;
const { withDispatch, withSelect } = wp.data;

const CustomSidebarMetaComponent = (props) => {
	return(
		<ToggleControl
	        label={__('My custom post meta', 'awp')}
	        checked={props.customPostMetaValue}
	        onChange={props.setCustomPostMeta}
	    />
	);
}

const CustomSidebarMeta = compose([
	withSelect(select => {
		return { customPostMetaValue: select('core/editor').getEditedPostAttribute('meta')['awp_my_custom_meta'] }
	}),
	withDispatch(dispatch => {
		return { 
			setCustomPostMeta: function(value) {
				dispatch('core/editor').editPost({ meta: { awp_my_custom_meta: value } });
			}
		}
	})
])(CustomSidebarMetaComponent);

const CustomSidebarComponent = () => {
	return(
		...
		<PanelBody
			title={__('This is a panel section', 'awp')}
			initialOpen={true}
		>
			<PanelRow>
				<CustomSidebarMeta />
			</PanelRow>
		</PanelBody>
		...

Let’s start at the beginning. Which is at the bottom. At line #36 we change the component we render in our sidebar to the component we created with compose (at line #15). The CustomSidebarMeta component will combine the withSelect and withDispatch components and return the CustomSidebarMetaComponent.

The CustomSidebarMetaComponent will have access to the customPostMetaValue prop from the withSelect, and the setCustomPostMeta prop from withDispatch These two we we use for the checked and onChange props in the ToggleField.

Note that in line #5 we add props as parameters to the components as to make props accessible in the component.

Conclusion and final words

I do hope this tutorial was of some use to you. This is what I managed to comprehend on the topic during a lot of trial and error. There’s barely any documentation on this topic out there yet. I am still struggling with this, especially when it comes to efficiently handle multiple post meta. If I manage to learn some good tricks I’ll update this tutorial for sure!

Hopefully by following this tutorial you should have code that successfully adds a new custom sidebar to the WordPress Gutenberg editor, and hopefully with some meaningful content and settings. Here is the final code for the custom sidebar with post meta field.

sidebar-plugin.js
const { registerPlugin } = wp.plugins;
const { __ } = wp.i18n;
const { Fragment } = wp.element;
const { PluginSidebarMoreMenuItem, PluginSidebar } = wp.editPost;
const { PanelBody, PanelRow, ToggleControl } = wp.components;
const { compose } = wp.compose;
const { withDispatch, withSelect } = wp.data;

const CustomSidebarMetaComponent = (props) => {
	return(
		<ToggleControl
	        label={__('My custom post meta', 'awp')}
	        checked={props.customPostMetaValue}
	        onChange={props.setCustomPostMeta}
	    />
	);
}

const CustomSidebarMeta = compose([
	withSelect(select => {
		return { customPostMetaValue: select('core/editor').getEditedPostAttribute('meta')['awp_my_custom_meta'] }
	}),
	withDispatch(dispatch => {
		return { 
			setCustomPostMeta: function(value) {
				dispatch('core/editor').editPost({ meta: { awp_my_custom_meta: value } });
			}
		}
	})
])(CustomSidebarMetaComponent);

const CustomSidebarComponent = () => {
	return(
		<Fragment>
			<PluginSidebarMoreMenuItem 
				target='awp-custom-sidebar'
				icon='carrot'
			>{__('My Custom sidebar', 'awp')}</PluginSidebarMoreMenuItem>
			<PluginSidebar 
				name="awp-custom-sidebar" 
				title={__('My Custom sidebar', 'awp')}
			>                
				<PanelBody
					title={__('This is a panel section', 'awp')}
					initialOpen={true}
				>
					<PanelRow>
						<CustomSidebarMeta />
					</PanelRow>
				</PanelBody>
				<PanelBody
					title={__('Another section', 'awp')}
					initialOpen={false}
				>
					<PanelRow>
						This is a collapsed section by default.
					</PanelRow>
				</PanelBody>
			</PluginSidebar>
		</Fragment>
	);
}

registerPlugin('awp-customsidebar', {
	render: CustomSidebarComponent,
	icon: 'carrot'
});
functions.php
add_action('enqueue_block_editor_assets', function() {
	wp_enqueue_script('awp-sidebar-plugin', get_stylesheet_directory_uri() . '/assets/js/sidebar-plugin.js', ['wp-plugins', 'wp-edit-post']);
});

add_action('init', function() {
	register_meta('post', 'awp_my_custom_meta', [
		'show_in_rest' => true,
		'single' => true,
		'type' => 'boolean'
	]);
});

Leave a comment