Block Hooks API: Understanding ignoredHookedBlocks logic

The following post describes the process as of WordPress 6.5 and above. The initial release of the Block Hooks API in 6.4 works slightly differently.

Currently I’m working on a project which involves understanding the new Block Hooks API and its internals.

For those that aren’t already familiar I’ll pull an excerpt from the release post to help understand what it is, and why it’s used.

WordPress 6.4 introduces Block Hooks (#53987), a feature that provides an extensibility mechanism for Block Themes. This is the first step in emulating WordPress’ Hooks concept that allows developers to extend Classic Themes using filters and actions.

Specifically, the Block Hooks API allows a block to automatically insert itself relative to instances of other block types. For example, a “Like” button block can ask to be inserted before the Post Content block, or an eCommerce shopping cart block can ask to be inserted after the Navigation block.

Nick Diego

One aspect of the API I was struggling to wrap my head around was how it knows when a user has removed a hooked block.

How it works

Let’s assume you add the following code to your plugin to insert a Login block to appear after the Navigation block.

function register_logout_block_after_navigation( $hooked_blocks, $position, $anchor_block, $context ) {
	if ( $anchor_block === 'core/navigation' && $position === 'after' ) {
		$hooked_blocks[] = 'core/loginout';
	}

	return $hooked_blocks;
}

add_filter( 'hooked_block_types', 'register_logout_block_after_navigation', 10, 4 );
PHP

In the Editor, if I load my themes (twentytwentyfour) header template part this is how it’s now represented

<!-- wp:navigation {"ref":4,"layout":{"type":"flex","justifyContent":"right","orientation":"horizontal"},"style":{"spacing":{"margin":{"top":"0"},"blockGap":"var:preset|spacing|20"},"layout":{"selfStretch":"fit","flexSize":null}}} /-->

<!-- wp:loginout /-->
HTML

Now if I click “Save” the template and refresh the Editor this is what I see.

<!-- wp:navigation {"ref":4,"metadata":{"ignoredHookedBlocks":["core/loginout"]},"layout":{"type":"flex","justifyContent":"right","orientation":"horizontal"},"style":{"spacing":{"margin":{"top":"0"},"blockGap":"var:preset|spacing|20"},"layout":{"selfStretch":"fit","flexSize":null}}} /-->

<!-- wp:loginout /-->
HTML

As you can see there is an attribute ignoredHookedBlocks now present in my Navigation blocks markup, with a value containing my core/loginout block. There is also my hooked block mark up being inserted after the Navigation block as expected.

But wait…

I thought to myself “Why is my block getting inserted, but at the same time being marked as ignored?”.

If I make a customisation to the template part and remove my Login block from the header. The attribute is still present and now the Login block correctly does not render, as expected. So what is the purpose of it?

The missing part to my mental model

The problem was that I was thinking about it all wrong. The ignoreHookedBlocks attribute isn’t telling the renderer to not insert my hooked block. It’s telling it to not insert another hooked block.

Now, I think of it this way:

  1. The hooked block gets inserted into the template markup via the Block Hooks API.
  2. Now if customisations are made the template markup with the hooked block is saved to the database. During the saving process the ignoredHookedBlocks attribute gets added to the anchor block within the markup.
  3. If I make further customisations and remove my hooked block manually from the editor, the template gets saved again without my hooked block and the ignoredHookedBlocks attribute still remains on my anchor block preventing another insertion from happening.

When dealing with uncustomised templates, my hooked block is appropriately inserted (at points 1) as the template is loaded and read from the file system. When customising templates (at point 2) the markup which includes my hooked block is saved to the database and its during this process ignoredHookedBlocks attribute gets added to the anchor block. Therefore, there is no need to insert an additional hooked block when the hooked_block_types filter callback is executed subsequent times (at point 3). This is the purpose of the ignoredHookedBlocks attribute.

Leave a Reply

Your email address will not be published. Required fields are marked *