Gutenberg: custom validation / how to prevent post from being saved

This script will disable the Publish/Update button:

wp.data.dispatch( 'core/editor' ).lockPostSaving( 'my-lock' );

To make the post publishable again, you can do:

wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'my-lock' );
Post save locking / unlocking

Very nice. But how is this useful? Here are a couple of use cases.

Force users to enter a post title.

Let’s say you want to force your users to at least enter a title before they can save their posts.

// Force title

let locked = false;

wp.data.subscribe( () => {
  // get the current title
  const postTitle = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'title' );

  // Lock the post if the title is empty.
  if ( ! postTitle ) {
    if ( ! locked ) {
      locked = true;
      wp.data.dispatch( 'core/editor' ).lockPostSaving( 'title-lock' );
    }
  } else if ( locked ) {
    locked = false;
    wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'title-lock' );
  }
} );

Show an error message

Simply disabling the Publish/update button is probably not the best user experience. We should also let the user know why they can’t save the post. We can do this by dispatching an action:

wp.data.dispatch( 'core/notices' ).createNotice(
  'error',
  'Please enter a title',
  { id: 'title-lock', isDismissible: false }
);

We can get rid of the warning like this:

wp.data.dispatch( 'core/notices' ).removeNotice( 'title-lock' );

Putting it all together, this results in:

// Force title

let locked = false;

wp.data.subscribe( () => {
  // get the current title
  const postTitle = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'title' );

  // Lock the post if the title is empty.
  if ( ! postTitle ) {
    if ( ! locked ) {
      locked = true;
      wp.data.dispatch( 'core/editor' ).lockPostSaving( 'title-lock' );
      wp.data.dispatch( 'core/notices' ).createNotice(
  'error',
  'Please enter a title',
  { id: 'title-lock', isDismissible: false }
);
    }
  } else if ( locked ) {
    locked = false;
    wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'title-lock' );
    wp.data.dispatch( 'core/notices' ).removeNotice( 'title-lock' );
  }
} );

That was easy. Now let’s try something more complex.

Force users to enter a title and select at least one category and a featured image

If we only need to lock a post for one case, the above example is sufficient. But what if we have several conditions that need to be met before a user can save post? We could duplicate the above code and modify the parts we need, but that’s not very DRY. So let’s create a reusable function function first:

// Keep track of our locks
const locks = [];

function lock( lockIt, handle, message ) {
  if ( lockIt ) {
    if ( ! locks[ handle ] ) {
      locks[ handle ] = true;
      wp.data.dispatch( 'core/editor' ).lockPostSaving( handle );
      wp.data.dispatch( 'core/notices' ).createNotice(
        'error',
        message,
        { id: handle, isDismissible: false }
      );
    }
  } else if ( locks[ handle ] ) {
    locks[ handle ] = false;
    wp.data.dispatch( 'core/editor' ).unlockPostSaving( handle );
    wp.data.dispatch( 'core/notices' ).removeNotice( handle );
  }
}

With this in place, we can simply enable the “title lock” like this:

wp.data.subscribe( () => {

  // get the current title
  const postTitle = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'title' );

  // Lock the post if the title is empty.
  lock(
    ! postTitle,
    'title-lock',
    'Please enter a title',
  );
)};

We can also add additional locks:

wp.data.subscribe( () => {

  // get the current title
  const postTitle = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'title' );

  // Lock the post if the title is empty.
  lock(
    ! postTitle,
    'title-lock',
    'Please enter a title',
  );


  // get categories
  const categories = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'categories' );

  // Lock post if there are no categories selected
  lock(
    categories && ! categories.length,
    'categories',
    'Please select at least one category',
  );


  // get custom taxonomy
  const myCustomTaxonomy = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'my-custom-taxonomy' );

  // Lock post if there are no custom taxonomy terms selected
  lock(
    myCustomTaxonomy && ! myCustomTaxonomy.length,
    'my-custom-taxonomy-lock',
    'Please select at least one from My Custom Taxonomy',
  );


  // get the Featured Image
  const featuredImage = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'featured_media' );

  // Lock post if there is no Featured Image selected
  lock(
    featuredImage === 0,
    'featured-image-lock',
    'Please add a Featured Image',
  );

} );