Gutenberg: Prevent specific block from being removable

Ideally, we should use template_lock as much as we possibly can. But with the problems it introduces currently, and because there might be special use cases that template locking cannot handle, I wanted to explore a way to prevent a specific block from being deletable.

If you’re comming from a jQuery background (which is likely if you have been developing wordpress websites for a while), you are probably thinking that there must be some events you need to listen to.

So there should be some kind of 'onDelete' event that we can listen to. And then we could do some checks and finally return false or e.preventDefault, right?

Well, no. In Gutenberg there are no events. I finally realized the implications of this after reading this comment by Riad Benguella:

There are no events in Gutenberg. The idea is that there are selectors that can give you at anytime the data in Gutenberg (for example the list of blocks in the post) and there’s a unique event emitter you can use to track whether the state (entire state) change or not.

Welcome to the React/Redux world, where all that we need is State.

Prevent removing all blocks

The code below will make sure that no blocks can be removed:

const getBlockList = () => wp.data.select( 'core/block-editor' ).getBlocks();
let blockList = getBlockList();
wp.data.subscribe( () => {
  const newBlockList = getBlockList();
  if ( newBlockList.length < blockList.length ) {
    wp.data.dispatch( 'core/block-editor' ).resetBlocks( blockList );
  }
  blockList = newBlockList;
} );

Prevent removing a specific block

After we know that newBlockList.length < blockList.length is true, we could do some additional checks. It depens on your use case, but let’s say you want to check if the myplugin/book-settings block was removed. To prevent it from being deleted you could add this code:

newBlockList.every( block => block.name !== 'myplugin/book-settings' )

This code will return true if newBlockList does not include a block named myplugin/book-settings. We can add it to the if statement like so.

const getBlockList = () => wp.data.select( 'core/block-editor' ).getBlocks();
let blockList = getBlockList();
wp.data.subscribe( () => {
  const newBlockList = getBlockList();
  if (
    newBlockList.length < blockList.length &&
    newBlockList.every( block => block.name !== 'myplugin/book-settings' )
  ) {
    wp.data.dispatch( 'core/block-editor' ).resetBlocks( blockList );
  }
  blockList = newBlockList;
} );

The result is that we can now delete any block, except the book-settings.