How to deprecate code in Gutenberg editor blocks

I recently released an update to Atomic Blocks to improve the Accordion Block, which allowed you to add a title and text to a handy accordion toggle. I purposefully kept it simple because at the time I created the block, the Gutenberg editor was going through a lot of changes.
Now that the editor development has stabilized, lots of people are writing in wanting to expand the capabilities of the accordion to be able to add any kind of block into the accordion content area. This is a perfect use case for utilizing InnerBlocks
, but it’s not as simple as just changing the markup in the block.
Don’t break your blocks
If you’ve dug into Gutenberg block development, you’ve probably learned pretty quickly that changing the markup of your block after it’s in use will cause the block to break in the editor, as seen below.

This is because when you save a block and eventually reload the page, the editor is comparing the block markup that was saved initially to what the editor is receiving. If there is a mismatch between the two, the editor will show you the notice you see above.
If you have a thousand people using a custom block where you used a paragraph tag and you decide to change that do a div in your block markup, when those users go to revisit those blocks, their blocks are going to be broken.
Luckily, in most cases, you can recover the content of the block by converting it to another block or accessing the HTML. This is a nice fallback, but ultimately the user still has a broken block that they have to recreate if they want to use it again. And if they used that block in a lot of posts and pages, they are stuck with a lot of work recreating those blocks.
To get around this, Gutenberg has a way for developers to deprecate code, features, and attributes of blocks so that it is safe for the user to continue using the block without breaking it in an update.
As you can imagine, deprecating can get pretty messy if you are constantly changing the markup of your blocks. For this reason, I would advise planning your blocks accordingly and thinking hard about any future use cases that may come up. It would be more beneficial to spend an hour plotting out your block before building it than inconveniencing thousands of users later on down the road if/when you decide to modify the markup of your block.
What we need to do
Although the Gutenberg Handbook has a page about deprecations, I find it more helpful to go through a real world example. Here is the full block markup that we’ll be starting with in the following code snippets and here is the final block markup we’re working towards. This is a simple sample block with a single RichText
component that spits out a simple paragraph tag.
We’re going to do a few things to this block. First, we’re going to change the paragraph tag to a div tag. Sounds simple enough right? Then, we’re going to get a little tricky and convert the RichText component to the InnerBlocks
component without losing the content saved in the RichText
component.
Why might we convert block content? As I mentioned with the Accordion Block, it started with just a simple paragraph content area, but eventually we needed to expand that to allow for more blocks with the InnerBlocks component. By converting the paragraph content users already had, we can retain their content while also expanding the block’s capabilities.
Let’s get started!
The first thing we’re going to do is add the deprecated code function after our save function. See the final block code for exact placement.
deprecated: [
{
attributes: {
text: {
type: 'string',
default: 'This is default text.',
},
...blockAttributes
},
migrate( attributes, innerBlocks ) {
return [
omit( attributes, 'text' ),
[
createBlock( 'core/paragraph', {
content: attributes.text,
} ),
...innerBlocks,
],
];
},
save( props ) {
return (
<RichText.Content
class="ab-text"
tagName="p"
value={ props.attributes.text }
/>
);
},
}
]
We’re doing a few things here. First, we’re defining the attributes of the element we’re going to be changing, and then passing along the rest of the attributes of the block with the blockAttributes
variable I’ve set up near the top of the block.
Next, we have a migrate function to convert our single paragraph tag to the InnerBlocks
component and create a paragraph tag with our original content within the new InnerBlocks
component.
Next, we’re adding a save function that will safely process and deprecate the old markup of the block that we defined in the original save function.
Now that the old markup has been moved to the deprecated save function, we can change the markup in our original edit and save functions to add the InnerBlocks
component.
The code in our edit function will be changed from this:
<RichText
tagName="p"
value={ text }
className="ab-text"
onChange={ ( value ) => this.props.setAttributes( { text: value } ) }
/>
to this:
<div class="ab-text">
<InnerBlocks />
</div>
The code in our save function will be changed from this:
<RichText.Content
class="ab-text"
tagName="p"
value={ props.attributes.text }
/>
to this:
<div class="ab-text">
<InnerBlocks.Content />
</div>
With that, our main markup changes are complete. But there are a few more things we need to do before this block will work properly.
Notice how in the migrate function there are a few new elements introduced, such as omit
and createBlock
. These need to be imported and defined at the top of our block in order to use them. We also introduced the InnerBlocks
component in the new markup, which we’ll also need to define.
We can import omit
by adding the following snippet at the top of the block:
import omit from 'lodash/omit';
We can define createBlock
by adding the following snippet at the top of the block:
const {
createBlock
} = wp.blocks;
We can define the InnerBlocks
component by adding it to the wp.editor components at the top of the block:
const {
RichText,
InnerBlocks,
} = wp.editor;
That should be all the code changes we need to make! You’ll want to test your new block with a few different uses cases to ensure it’s working properly. If you are migrating content, you’ll also want to test to make sure your content makes it through the migration successfully.
To help better understand the changes between the blocks, you can review the full code of our original block here and the updated block here.
Wrapping up
Deprecating is the safest and most responsible way to update markup, attributes, and features in your Gutenberg editor blocks. This simple example should give you a good head start on working with your own blocks.
Keeping your blocks clean and simple to begin with will make updating them in the future much easier. Also, be sure to plan your blocks accordingly so that you don’t end up with more deprecated code than actual block code!
Hi,
Thank you so much for this post! There are not that much to read about the deprecated/migrate topic.
I tried to do it myself following your steps but for some reason it does not migrate the old structure.
What I did:
– Load the first version of the block
– Create a block with the old structure
– Load the new version of the block with the deprecated attributes
– Create a new block with the new structure.
=> The new block works well but the previous one is flagged with the “This block has been modified externally.” message. I’ve been trying everything for too many hours but it still doesn’t migrate. Does it work for you?
Thanks again
Hi Benjamin,
It did work for me, but it took quite a bit of trial and error to make sure all the code was just right. The examples above are to serve as an example, but should give you a good idea of the process needed to make it work on your end.
If done right, you won’t get that “modified externally” message, but it very well might take a bit to get it just right. I’m hoping they clean up the deprecation method to make this a lot simpler. Right now, it’s very difficult to get just right.
Which is why I think it a terrible idea to store markup in the DB—shouldn’t we rather use blocks in a similar way to how e.g. the old `caption` shortcode worked? Just with the difference to add it as a block and render it in the editor, as well …