Creating Custom Sidebars with PluginSidebar
Creating Gutenberg sidebars and panels with fields inside of them is a brand new way of working with WordPress metadata. I am sure that the default way of creating meta boxes will stay with us for a long time as a backward compatibility, but believe me that working with the content using blocks and sidebars is a much more pleasant way than trying to configure everything in old-fashioned meta boxes.
So, what we are going to do in this tutorial?
Usually I like to show practical examples in my tutorials. And here is the one – I don’t use any SEO plugins on my projects, because I think they are too heavy and bloated with code. But if you’re familiar with SEO, you know that it is important to add unique titles and meta descriptions for your website pages. And probably you would like to hide some of them from indexing with robots meta tag.

Please also stay away from tutorials where it is recommended to work directly with WordPress REST API and use wp.apiRequest
etc. That’s not a correct way of creating Gutenberg sidebars.
Below is a step by step guide and a lot of JSX code. If you’re not sure what to do with it, maybe it is worth considering a way of creating sidebars with PHP?
1. Register Meta in REST API
I am going to use these 3 meta keys:
-
misha_plugin_seo_title
– for SEO title text field, misha_plugin_seo_description
– for SEO description textarea field,misha_plugin_seo_robots
– for robots meta tag checkbox (or toggle in our case).
Nothing super especial right now, you just have to register all of the keys in REST API using register_meta() function.
// you have to use it within the 'init' hook
add_action( 'init', function(){
register_meta(
'post', // object type, can be 'post', 'comment', 'term', 'user'
'misha_plugin_seo_title', // meta key
array(
'object_subtype' => 'page', // you can specify a post type here
'type' => 'string', // 'string', 'boolean', 'integer', 'number', 'array', and 'object'
'single' => true, // one value per object or an array of values
'show_in_rest' => true, // accessible in REST
)
);
register_meta(
'post',
'misha_plugin_seo_description',
array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
)
);
register_meta(
'post',
'misha_plugin_seo_robots',
array(
'type' => 'boolean', // because it is a checkbox
'single' => true,
'show_in_rest' => true,
)
);
});
Why I used boolean type for robots meta key? Because I do not use anything except “noindex,follow”, so, the checkbox above will add only this value once checked.
One more thing – when registering a post type your supports
parameter should contain custom-fields
value, example:
register_post_type(
'rudr_post_type_slug',
array(
...
'supports' => array( 'title', 'editor', 'custom-fields' ),
...
)
);
2. Enqueue Scripts
I want to highlight this step too, because I think you have to pay attention to dependencies:
add_action( 'enqueue_block_editor_assets', function(){
wp_enqueue_script(
'misha-sidebar',
get_stylesheet_directory_uri() . '/sidebar.js',
array( 'wp-edit-post', 'wp-element', 'wp-components', 'wp-plugins', 'wp-data' ),
filemtime( get_stylesheet_directory() . '/sidebar.js' )
);
});
At you can see, comparing to registering a Gutenberg block, here is a lot of dependencies. The above code can be placed in your theme / child theme functions.php
or in a custom plugin. You can see filemtime()
function on line 7 – we need it to provide the new version of the file to wp_enqueue_script()
function each time it was changed.
And I already know that I have to mention that too – at this moment sidebar.js
is just an empty file 😁
In case you want to allow / disallow your sidebar depending on a custom post type, you can use get_current_screen()
-based condition at the beginning of the action hook.
add_action( 'enqueue_block_editor_assets', function(){
$screen = get_current_screen();
if( 'page' === $screen->post_type ) {
return; // disabled for Pages
}
wp_enqueue_script(
...
3. registerPlugin()
The first time this tutorial was published in May 2019, the whole code was in raw JavaScript ES6, but now I work only with JSX and understand that there is no reason to support that 🙃 JSX is much simpler to use and read.
Let’s begin with something simple:
( function( wp ) {
const { registerPlugin } = wp.plugins;
const { PluginSidebar } = wp.editPost;
registerPlugin( 'misha-seo', {
render: function(){
return (
<PluginSidebar name="misha-seo" title="SEO">
hello world
</PluginSidebar>
)
}
} );
} )( window.wp );

Set a Custom Icon
Now let’s change a default icon to a custom one. There are two options how you can set an icon:
- Use any icon from Dashicons set.
- Provide icon SVG code.
( function( wp ) {
const { registerPlugin } = wp.plugins;
const { PluginSidebar } = wp.editPost;
// Custom SVG icon
// const icon = <svg width="20" height="20"> ... </svg>;
// Icon from Dashicons
const icon = 'chart-area';
registerPlugin( 'misha-seo', {
render: function(){
return (
<PluginSidebar name="misha-seo" title="SEO" icon={ icon }>

PluginSidebarMoreMenuItem
This component allows you to change how your sidebar plugin will look in “more” menu. By default it has the same title and icon as <PluginSidebar>
.

Let’s change it!
( function( wp ) {
const { registerPlugin } = wp.plugins;
const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost;
const { Fragment } = wp.element;
const icon = 'chart-area';
registerPlugin( 'misha-seo', {
render: function(){
return (
<Fragment>
<PluginSidebarMoreMenuItem target="misha-seo" icon="palmtree">
My super plugin
</PluginSidebarMoreMenuItem>
<PluginSidebar name="misha-seo" title="SEO" icon={ icon }>
hello world
</PluginSidebar>
</Fragment>
)
}
} );
} )( window.wp );

But I think it is better to use the same title and icon… well, at least the same icon.
PanelBody
Sometimes you probable would like to separate fields in your Gutenberg Sidebars by groups.
Like this:

It is possible to implement with <PanelBody>
component, the complete code is below:
( function( wp ) {
const { registerPlugin } = wp.plugins;
const { PluginSidebar } = wp.editPost;
const { PanelBody } = wp.components;
registerPlugin( 'misha-seo', {
render: function(){
return (
<PluginSidebar name="misha-seo" title="SEO" icon="chart-area">
<PanelBody title="Group 1" initialOpen={ true }>
hello world
</PanelBody>
<PanelBody title="Group 2" initialOpen={ false }>
some fields will be here
</PanelBody>
</PluginSidebar>
)
}
} );
} )( window.wp );
I also have a tutorial where I describe how to hide some panels for specific post types.
4. Fields
Include necessary components
( function( wp ) {
const { registerPlugin } = wp.plugins;
const { PluginSidebar } = wp.editPost;
const {
PanelBody,
TextControl, // text field
TextareaControl, // textarea field
ToggleControl // toggle field
} = wp.components;
const { withSelect, withDispatch } = wp.data;
const { compose } = wp.compose;
Create a HOC (higher order component)
The long story short – using regular Gutenberg control components like TextControl
or ToggleControl
etc is not enough, because they don’t allow us go get or set values in post meta.
That’s why we compose them into a HOC (Higher Order Component):
- After wrapping
TextControl
inwithSelect()
we have the possibility to accessmetaValue
property on line 30, - After wrapping it in
withDispatch()
we receive the possibility to usesetMetaValue()
on line 32 which allows to update the meta values correctly.
const MishaTextControl = compose(
withDispatch( function( dispatch, props ) {
return {
setMetaValue: function( value ) {
dispatch( 'core/editor' ).editPost( { meta: { [props.metaKey]: value } } );
}
}
} ),
withSelect( function( select, props ) {
return {
metaValue: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ props.metaKey ],
}
} )
)( function( props ) {
return (
<TextControl
type="text"
label={ props.label }
value={ props.metaValue }
onChange={ ( content ) => { props.setMetaValue( content ) } }
/>
);
} );
If we are going to create all 3 type of fields – seo title, description and toggle, we have to create higher order components for every inspector control – MishaTextControl
, MishaTextareaControl
and MishaCheckboxControl
, but the components code is very similar, so for some situations it would be better to create just one component, let’s say MishaControl
and pass control type in it.
Maybe it is a little bit different for <ToggleControl>
, but that’s all.
<ToggleControl
label={ props.label }
checked={ props.metaValue }
onChange={ ( content ) => { props.setMetaValue( content ) } }
/>
Using useSelect/useDispatch instead of withSelect/withDispatch
In the official WordPress documentation is is said that there are no plans to make withSelect
and withDispatch
deprecated in favor of new useSelect
and useDispatch
hooks.
But anyways, it is up to decide which way to use, I decided to explain both of them.
First things first you need to use const { useSelect, useDispatch } = wp.data
instead of const { withSelect, withDispatch } = wp.data
and you don’t need to import compose
anymore.
Second, let’s just update the code of MishaTextControl
component.
const MishaTextControl = ( props ) => {
const { metaValue } = useSelect( ( select ) => {
return {
metaValue: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ props.metaKey ],
}
}, [ props.metaKey ] );
const { editPost } = useDispatch( 'core/editor' );
return (
<TextControl
type="text"
label={ props.label }
value={ metaValue }
onChange={ ( value ) => editPost( { meta: { [props.metaKey]: value } } ) }
/>
)
}
Add fields to sidebar
The last step is to add our component fields to <PluginSidebar>
and wrap them into <PanelBody>
.
<PluginSidebar name="misha-seo" title="SEO" icon="chart-area">
<PanelBody>
<MishaTextControl label="Title" metaKey="misha_plugin_seo_title" />
<MishaTextareaControl label="Description" metaKey="misha_plugin_seo_description" />
<MishaToggleControl label="Hide from search engines" metaKey="misha_plugin_seo_robots" />
</PanelBody>
</PluginSidebar>
Creating Gutenberg Sidebars with PHP
I completely understand that sometimes there is no time to configure Webpack, Babel and all that stuff for your WordPress project and maybe you’re not so good at React.js.
So I create the plugin that allows to create sidebars and fields for them with pure PHP. For now let’s create the same SEO fields we created above in this tutorial.
- Install and activate my Simple Fields plugin on your WordPress website.
- Copy and paste the following code into your
functions.php
file or to a custom plugin:
add_filter( 'simple_register_sidebars', 'rudr_seo_fields' );
function rudr_seo_fields( $sidebars ) {
$sidebars[] = array(
'id' => 'misha-seo',
'name' => 'SEO',
'icon' => 'chart-area',
'fields' => array(
array(
'id' => 'seo_title',
'label' => 'Title',
'type' => 'text'
),
array(
'id' => 'seo_description',
'label' => 'Description',
'type' => 'textarea',
'rows' => 3
),
array(
'id' => 'seo_robots',
'type' => 'checkbox',
'label' => 'Hide from search engines',
),
),
);
return $sidebars;
}
That’s really all. Do you believe me? Here is our result:


Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
Does the code on this page require your plugin?
Only this part.
Some cool fields to add would be a taxonomy select, both select and multi-select, a post select field, and it would be awesome if an input can be changed to “number” format and it gets saved to the database as an integer.
Last but not least; maybe a way to remove the “unpin from toolbar” star from specific sidebars? I have had a few users so far that accidentally clicked it.
Plus one on the post select field
Misha, I’m going to use your plugin specifically for this functionality!
I wonder why almost nobody (ACF, Metabox.io, Carbon Fields, etc. or even new players like Block Lab) saw the need for those panels when they are real replacement for meta boxes to store post meta data nowadays.
I’m only missing repeaters and nesting (field groups).
That’s a good question 🙃
Field groups will be supported in my plugin soon.
Great tutorial Misha!
Having spent a few hours getting this working, here is a little tip to stop the sidebar meta fields ( or meta blocks ) clashing with custom fields on the post.
As documented ( here and elsewhere ) registering the meta fields will also add them as custom fields on your posts.
When you update the sidebar metabox, save the post and reopen it, the custom field ( if they are activated / visible ) will also contain the value of the sidebar meta field. Then when you try and update the sidebar meta field, and save the post for a second time, the value will be over written by the previous value which is still held in the custom field.
To stop this happening, protect the sidebar ( or block ) meta fields by prefixing the names with an underscore and providing a suitable authorization function.
For example:-
Details here: https://developer.wordpress.org/block-editor/tutorials/metabox/meta-block-2-register-meta/
Using a protected meta field stops it being listed in the post’s custom fields and solves the problem. Don’t forget to update the meta field name in the JS code too!
Thanks! 🙃
Can I add a Colorpicker in this way? So the user can choose a color for the post, which a group of elements will be affected by it.
Cheers
Brilliant! At least up to the point of someone checking the box. Where is the code that processes a form like this in the sidebar? I would like to have a checked box create a row in wp_postmeta, which I would normally do with Ajax in a regular form on the site. Where would I put my code to detect and process the check event?
Thanks for any insight.
Can we access meta value using meta key in other pages where all the post will be shown?
Sure
Hi, I copy and paste the code, and I getting this error. Any advice? Thanks
Uncaught SyntaxError: Unexpected token ‘<'
Never mind. I solve it. Thanks
I have the same problem. May I ask how you solved it?
Hi Juan,
Could you please share what was the cause of that issue?
Thanks!
Guys, you’re inserting the code into
functions.php
, aren’t you?Your plugin works also on wp_template and wp_template_parts=