If you’re trying to make your plugin compatible with Gutenberg, you have a couple of paths that you can/should take – depending on what your plugin does and how it delivers its features to the user.
In the first part of this mini-series, we explored Gutenberg’s Block API – which is what most plugins need in terms of Gutenberg-compatibility. This time, we will be exploring another piece of the Gutenberg puzzle – Gutenberg’s Sidebar API.
Just to make sure we’re on the same page, a quick explanation:
- Gutenberg’s Block API is very extensive and allows you to build almost everything as blocks, but, at times, it is not enough.
- Sidebar API, on the other hand, allows plugins to register a sidebar where they can take their functionality beyond blocks. The Sidebar is similar to Block Inspector.
For example, Gutenberg Sidebar API in use as demonstrated by Yoast SEO:
Where can you use Sidebar API?
We recently worked on making our plugin – WP Product Review – ready for Gutenberg. WP Product Review used meta fields for adding review data to the post, and we couldn’t turn into a block for various backward-compatibility reasons. Hence, we used Sidebar API.
When users indicate that the post is a review with a sidebar toggle, they’ll be able to configure everything about their review box from the sidebar options.
WP Product Review is one of the examples where Sidebar API can be useful. Another great example is the Drop It plugin, which allows users to insert stock images from Unsplash and Giphy using the sidebar.
In this post, I’ll walk you through the process of implementing something similar and making your plugin Gutenberg-compatible through Sidebar API.
Meta boxes are kind of ready for Gutenberg by default, but not quite
Let’s start with meta boxes. Let’s take a plugin that uses a simple meta box to deliver some functionality to the user on the editor screen.
This can look like the following in the Classic Editor:
You can use the following code to create such a meta box – it is also available in the Hello Gutenberg repository:
/** * Register Hello Gutenbert Meta Box */ function hello_gutenberg_add_meta_box() { add_meta_box( 'hello_gutenberg_meta_box', __( 'Hello Gutenberg Meta Box', 'hello-gutenberg' ), 'hello_gutenberg_metabox_callback', 'post' ); } add_action( 'add_meta_boxes', 'hello_gutenberg_add_meta_box' ); /** * Hello Gutenberg Metabox Callback */ function hello_gutenberg_metabox_callback( $post ) { $value = get_post_meta( $post->ID, '_hello_gutenberg_field', true ); ?> <label for="hello_gutenberg_field"><?php _e( 'What\'s your name?', 'hello-gutenberg' ) ?></label> <input type="text" name="hello_gutenberg_field" id="hello_gutenberg_field" value="<?php echo $value ?>" /> <?php } /** * Save Hello Gutenberg Metabox */ function hello_gutenberg_save_postdata( $post_id ) { if ( array_key_exists( 'hello_gutenberg_field', $_POST ) ) { update_post_meta( $post_id, '_hello_gutenberg_field', $_POST['hello_gutenberg_field'] ); } } add_action( 'save_post', 'hello_gutenberg_save_postdata' );
So the question to ask here is:
Do we need to make this Gutenberg-compatible in the first place?
At this point, we should consider asking ourselves what Gutenberg-compatible is. For instance, if you use this very meta box in Gutenberg without making any changes to it, it will still work.
However, when we talk about Gutenberg compatibility, we are not talking merely about the plugin working in the Gutenberg environment, but the plugin working *like Gutenberg*.
“Why?” – you’d ask. Quite simply, users who will start using WordPress after Gutenberg is the default editor will find the old ways to be counter-intuitive. Furthermore, they will expect a more Gutenberg-like style for everything in the editor.
If you don’t provide users with a native feel, they will certainly look for alternatives that will work better with Gutenberg.
Finally, let’s get our hands dirty with some code!
Getting started with Sidebar API
We will start coding our Sidebar in Gutenberg to include our meta field. You can continue using the Gutenberg Boilerplate repository as a starting point.
Getting your meta box ready for Gutenberg
Before we start, we first need to tell Gutenberg that we won’t be using our meta box in Gutenberg. You can do it by passing the __back_compat_meta_box
argument to the add_meta_box
function, like so:
/** * Register Hello Gutenberg Metabox */ function hello_gutenberg_add_meta_box() { add_meta_box( 'hello_gutenberg_meta_box', __( 'Hello Gutenberg Meta Box', 'hello-gutenberg' ), 'hello_gutenberg_metabox_callback', 'post', array( '__back_compat_meta_box' => false, ) ); } add_action( 'add_meta_boxes', 'hello_gutenberg_add_meta_box' );
Gutenberg Handbook has more details on how you can handle porting PHP meta boxes to Gutenberg (here).
This will make sure that our old PHP meta boxes don’t appear in Gutenberg anymore. Now we also need to make our meta field ready for Gutenberg by adding it to WordPress REST API. You can add the following code to register the meta field with REST API:
/** * Register Hello Gutenberg Meta Field to Rest API */ function hello_gutenberg_register_meta() { register_meta( 'post', '_hello_gutenberg_field', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, ) ); } add_action( 'init', 'hello_gutenberg_register_meta' );
This will add the _hello_gutenberg_field
to the meta object in REST API.
This function will only do the job of displaying our value in the REST API. Now we also need to add a method to update our meta field using REST API.
The following code will add our custom route to WordPress REST API, which will be /hello-gutenberg/v1/update-meta/:
/** * Register Hello Gutenberg Metabox to Rest API */ function hello_gutenberg_api_posts_meta_field() { register_rest_route( 'hello-gutenberg/v1', '/update-meta', array( 'methods' => 'POST', 'callback' => 'hello_gutenberg_update_callback', 'args' => array( 'id' => array( 'sanitize_callback' => 'absint', ), ), ) ); } add_action( 'rest_api_init', 'hello_gutenberg_api_posts_meta_field' ); /** * Hello Gutenberg REST API Callback for Gutenberg */ function hello_gutenberg_update_callback( $data ) { return update_post_meta( $data['id'], $data['key'], $data['value'] ); }
This REST API route will be used to modify our meta field from the Gutenberg sidebar. You can learn more about WordPress REST API and how to register custom routes from here.
Similarly, if you want to know what WordPress REST API is and how to get started, you can read our blog post: WordPress REST API: What it is and how to get started using it.
Adding a sidebar block to Gutenberg
Let’s start with the Sidebar code from Gutenberg Boilerplate:
/** * Internal block libraries */ const { __ } = wp.i18n; const { Fragment } = wp.element; const { PluginSidebar, PluginSidebarMoreMenuItem, } = wp.editPost; const { registerPlugin } = wp.plugins; const Component = () => ( <Fragment> <PluginSidebarMoreMenuItem target="gutenberg-boilerplate-sidebar" > { __( 'Gutenberg Boilerplate' ) } </PluginSidebarMoreMenuItem> <PluginSidebar name="gutenberg-boilerplate-sidebar" title={ __( 'Gutenberg Boilerplate' ) } > <h2>{ __( 'Hello World!' ) }</h2> </PluginSidebar> </Fragment> ); registerPlugin( 'gutenberg-boilerplate', { icon: 'admin-site', render: Component, } );
This will insert a very simple sidebar to the Gutenberg editor. We will take this as a starting point and build our project upon it.
The example code is from our Gutenberg Boilerplate repository, and as a result, we need to replace all occurrences of “Gutenberg Boilerplate” with “Hello Gutenberg”.
Components Used
While our sidebar already imports many components, we will need more. You can replace the top part of the code with the following:
/** * Internal block libraries */ const { __ } = wp.i18n; const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; const { PanelBody, TextControl } = wp.components; const { Component, Fragment } = wp.element; const { withSelect } = wp.data; const { registerPlugin } = wp.plugins;
Let’s learn quickly about all the components that we have imported.
- As discussed in the previous article, the __ component is used to make our text translatable.
- The PluginSidebar component is used to add our sidebar to Gutenberg.
- PluginSidebarMoreMenuItem is used to add a button to Gutenberg menu, which toggles sidebar’s visibility.
- The PanelBody component is used to add a panel to our sidebar.
- TextControl is used for our input field.
- After that, we imported the Component and Fragment components. Both of them are React components that we use in our sidebar.
- The Fragment component is used to group a list of children without adding extra nodes to the DOM. We need to use the Fragment component in our code as JSX doesn’t allow us to have more than one parent node.
- withSelect is a higher-order component. To learn what higher-order components are, I recommend that you read this documentation.
- And finally, we import registerPlugin, which is similar to registerBlockType that we used in the previous article. Instead of registering a block, registerPlugin registers your plugin.
Adding Controls
So far our sidebar is just a Component function, but since we will be using React’s lifecycle methods, we will turn it into a React component, like so:
class Hello_Gutenberg extends Component { render() { return ( <Fragment> <PluginSidebarMoreMenuItem target="hello-gutenberg-sidebar" > { __( 'Hello Gutenberg' ) } </PluginSidebarMoreMenuItem> <PluginSidebar name="hello-gutenberg-sidebar" title={ __( 'Hello Gutenberg' ) } > <h2>{ __( 'Hello World!' ) }</h2> </PluginSidebar> </Fragment> ) } } registerPlugin( 'hello-gutenberg', { icon: 'admin-site', render: Hello_Gutenberg, } );
This should make your sidebar work with plain text.
Now let’s add our field to the sidebar. It should make our component look like this:
class Hello_Gutenberg extends Component { render() { return ( <Fragment> <PluginSidebarMoreMenuItem target="hello-gutenberg-sidebar" > { __( 'Hello Gutenberg' ) } </PluginSidebarMoreMenuItem> <PluginSidebar name="hello-gutenberg-sidebar" title={ __( 'Hello Gutenberg' ) } > <PanelBody> <TextControl label={ __( 'What\'s your name?' ) } // value={} // onChange={} /> </PanelBody> </PluginSidebar> </Fragment> ) } }
This will add a simple input field to the sidebar that will do absolutely nothing at this point.
Now we have two tasks left:
- Display the value of our meta field.
- Allow updating the value of our meta field from the sidebar.
Display the meta value
To fetch the meta data, we will be using wp.apiFetch. apiFetch is a utility library that allows us to make REST API requests.
We will use apiFetch in our React component’s constructor, like this:
class Hello_Gutenberg extends Component { constructor() { super( ...arguments ); this.state = { key: '_hello_gutenberg_field', value: '', } wp.apiFetch( { path: `/wp/v2/posts/${this.props.postId}`, method: 'GET' } ).then( ( data ) => { this.setState( { value: data.meta._hello_gutenberg_field } ); return data; }, ( err ) => { return err; } ); } render() { return ( <Fragment> <PluginSidebarMoreMenuItem target="hello-gutenberg-sidebar" > { __( 'Hello Gutenberg' ) } </PluginSidebarMoreMenuItem> <PluginSidebar name="hello-gutenberg-sidebar" title={ __( 'Hello Gutenberg' ) } > <PanelBody> <TextControl label={ __( 'What\'s your name?' ) } value={ this.state.value } // onChange={} /> </PanelBody> </PluginSidebar> </Fragment> ) } }
First, we have defined an initial state, which is basically the key and the value of our meta field. After that, we are using apiFetch to fetch the data from our current post.
We are passing the ID of our current post with the ${this.props.postId}
variable. We will get back to this point later.
Once the data is fetched, we update our state with the value from our REST API.
Now, let’s get back to the postId variable. We currently don’t know the ID of our current post, so for that we use the withSelect
higher-order component, like this:
const HOC = withSelect( ( select ) => { const { getCurrentPostId } = select( 'core/editor' ); return { postId: getCurrentPostId(), }; } )( Hello_Gutenberg ); registerPlugin( 'hello-gutenberg', { icon: 'admin-site', render: HOC, } );
This will pass the ID of our current post as a postId
variable. Now you can run an older post, and our Gutenberg sidebar will display the value of your meta field.
Update the meta value
Now we need to allow our sidebar to also update the meta value. Similar to fetching the details, we will use the wp.apiRequest utility.
Every time the value of our state changes, we will be updating it with REST API. For this, we first need to add an onChange
event to our TextControl
, like this:
<TextControl label={ __( 'What\'s your name?' ) } value={ this.state.value } onChange={ ( value ) => { this.setState( { value } ); } } />
And then we will be using the getDerivedStateFromProps lifecycle method to send our REST request.
You can add the following code below your constructor:
static getDerivedStateFromProps( nextProps, state ) { wp.apiRequest( { path: `/hello-gutenberg/v1/update-meta?id=${nextProps.postId}`, method: 'POST', data: state } ).then( ( data ) => { return data; }, ( err ) => { return err; } ); }
This will update our meta field every time we change it from the field. Now you should see an issue with this approach.
The meta values will be updated every time you change the meta value, there are two issues with this approach:
- Your data will be saved even if you decide not to update the post.
- It will make an HTTP call too often, which is not ideal.
The data should only be saved when you save or publish the post. For this, we can make use of our higher-order component to find out when the post is being saved.
You can replace our HOC function with the following code:
const HOC = withSelect( ( select, { forceIsSaving } ) => { const { getCurrentPostId, isSavingPost, isPublishingPost, isAutosavingPost, } = select( 'core/editor' ); return { postId: getCurrentPostId(), isSaving: forceIsSaving || isSavingPost(), isAutoSaving: isAutosavingPost(), isPublishing: isPublishingPost(), }; } )( Hello_Gutenberg );
This will give us a few variables that we can use to check if our post is being saved or published.
We need to add this condition to our post request function:
static getDerivedStateFromProps( nextProps, state ) { if ( ( nextProps.isPublishing || nextProps.isSaving ) && !nextProps.isAutoSaving ) { wp.apiRequest( { path: `/hello-gutenberg/v1/update-meta?id=${nextProps.postId}`, method: 'POST', data: state } ).then( ( data ) => { return data; }, ( err ) => { return err; } ); } }
Now meta values will only update when we save, update or publish the post.
That’s it! Your sidebar is complete now, and you’ve just learned how to make your plugin compatible with Gutenberg!
You can test your block to make sure it works. If not then please leave your response in the comments section below, and I’ll try to help you.
What’s next when you’re done with Sidebar API?
You can find the finished example in the Hello Gutenberg repository. If you find any problems then feel free to open an issue.
Once again, this is a small example of what’s possible with Gutenberg’s Sidebar API. There are many amazing things you can create with Gutenberg and its APIs.
Further reading:
- A Gutenberg Tutorial for Beginner Developers
- How to Use the New WordPress Block Editor – WordPress Gutenberg Guide
- Making a “Plugin Options Page” With Gutenberg Components
- Fetch API for beginners
- Google Maps JavaScript API
What are you trying to achieve with Gutenberg? Let us know.
…
Don’t forget to join our crash course on speeding up your WordPress site. Learn more below:
[…] am new to React but using this tutorial as a basis I put together this code to make a request to a REST endpoint I defined in my plugin […]