Once again, I find myself noting down a code technique because the online documentation elsewhere is lacking. This time, I’m adding a custom “sidebar” panel to the WordPress Block Editor.
In a regular WordPress installation—without plugins and complex options which the Theme can provide—there are two sidebars: one for the site (or post) you’re editing, and one for the specific block you’re editing. Adding a new PanelBody
1 to one of them can be the most obvious approach, but there are already a number of panels available and the view can quickly become swamped if you’re not careful.
A better approach can be to add a new sidebar, in which you can gather all of your options and fields, and lay them out in a considered way so that the user finds what they’re looking for more easily. This can also be a much better approach if you’re only providing the options to a user with sufficient user capabilities.
The code in this post is in React JSX syntax and so it’ll need to be compiled before loading it in the editor.
Adding the “plugin sidebar”
This sidebar is usually added by a plugin, hence the component name PluginSidebar
. This component is the current version of the deprecated PluginDocumentSettingPanel
, which is referenced in quite a lot of online code examples: hence this post.
The PluginSidebar
is provided by the WordPress “Editor” Package, so make sure you’re using @wordpress/editor
and not the outdated @wordpress/edit-post
. (For the sake of easy writing, the code here deals specifically with posts of type Page, and not (for example) Posts or the Site Editor.).
import { TextareaControl, PanelBody } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { PluginSidebar } from '@wordpress/editor';
import { _x } from '@wordpress/i18n';
import { registerPlugin } from '@wordpress/plugins';
const sidebarKey = 'sht-technical-admin';
const TechnicalAdminSidebar = () => {
const postType = useSelect(select => select('core/editor').getCurrentPostType(), []);
const meta = useSelect(select => select('core/editor').getEditedPostAttribute('meta'), []);
const user = useSelect(select => select('core').getCurrentUser(), []);
const { editPost } = useDispatch('core/editor');
const isAdmin = user?.capabilities?.includes('manage_options');
if (postType !== 'page' || !isAdmin) {
return null;
}
return (
<PluginSidebar
name={sidebarKey}
title={_x('Technical administration', 'Plugin sidebar title', 'sht')}
icon='admin-generic'
>
<PanelBody title={_x('SEA Code', 'Panel body title', 'sht')} initialOpen={true}>
<TextareaControl
label={_x('SEA Code', 'Textarea label', 'sht')}
value={meta?.sea_code || ''}
onChange={value => editPost({ meta: { ...meta, sea_code: value } })}
rows={10}
help={_x('Help text to explain the use of the field.', 'Textarea help', 'sht')}
/>
</PanelBody>
</PluginSidebar>
);
};
registerPlugin(sidebarKey, { render: TechnicalAdminSidebar });
This code will add a new sidebar panel if the current user has the sufficient “capability” assigned. The editorial interface will not be changed in any other way and the pre-existing sidebars for page and block will remain unaffected.

Controlling access to the sidebar
In this instance, the PluginSidebar
will only be displayed if the current user has the “manage_options” capability assigned. (Usually only those with the administrator role have this capability. An explanation of when to use roles and when to use capabilities is a separate topic, perhaps for another time.)
I couldn’t find a suitable reference on how to use canUser
to get this specific capability, so I added some custom server-side REST API code to extend the user data response. As you can see, this data is only output as part of the REST API response if the user who is requesting the data has the “manage_option” capability. This is the case if the user who is using the editor is an administrator.
register_rest_field('user', 'capabilities', [
'get_callback' => function ($user) {
if (!current_user_can('manage_options')) {
return [];
}
$user_object = get_userdata($user['id'] ?? null);
return is_object($user_object) ? array_keys(array_filter($user_object->allcaps)) : [];
},
'schema' => [
'type' => 'array',
'items' => ['type' => 'string'],
],
]);
Saving and using the data
If you take a look at the React code, you should be able to see that it uses useSelect
and useDispatch
to get and set the value of a post meta field. This is a clean approach and allows me to use the REST API as it’s intended in the Block Editor, and also allows uncomplicated access to the database value through all of the usual means. In this specific case, I use the wp_head
hook to output the value in the frontend of the website.
(This does, of course, require that the administrator of the site is responsible enough not to output random malicious code through this feature. If you have a good way of sanitising this kind of output, then I’m all ears.)
add_action('wp_head',function (){
$sea_code = trim(strip_tags(get_post_meta(get_the_ID(), 'sea_code', true)));
if (!empty($sea_code)) {
echo '<!-- SEA CODE START -->' . chr(10) . '<script>' . $sea_code . '</script>' . chr(10) . '<!-- SEA CODE END -->' . chr(10);
}
});
Footnotes
- My code uses the
PanelBody
component directly, but the official documentation shows that it should be used inside aPanel
component. This causes layout problems whenever I’ve tried to use the wrapping component, so I just omit it. This seems to work fine. ↩︎
Leave a Reply