WordPress Developer Blog

Exploring the Block Hooks API in WordPress 6.5

Exploring the Block Hooks API in WordPress 6.5

Introduced in WordPress 6.4, the Block Hooks API is an extensibility mechanism that allows you to dynamically insert blocks into block themes. For example, a plugin could add an eCommerce shopping cart block to a site’s primary navigation menu without requiring users to add it themselves.

The API was designed to emulate hooks and filters you might have used to extend classic themes but with a few key differences.

  • As the name suggests, you can only insert blocks with the Block Hooks API and only in templates, template parts, and patterns.
  • The API works holistically with site editing. While front-end insertion happens automatically when a block is hooked, the user has the ultimate control. In the Site Editor, they can keep, remove, customize, or move the block, and those changes will be reflected on the front end.

WordPress 6.5 extends the API with many new community-requested features. You can insert blocks into navigation menus and user-modified templates, template parts, and patterns. Two new filters allow you to customize an inserted block’s attributes. Statically rendered blocks are now supported, and so much more. 

Through multiple practical examples, this article provides a comprehensive overview of how you can leverage Block Hooks API in your projects. Let’s get started.

How to create block hooks

The concept behind the Block Hooks API is straightforward. You choose a block type to insert into a block theme and another block type as the “anchor.” The hooked block will be inserted relative to the anchor block, often before or after. 

Perhaps you want to hook a Post Featured Image block before the Post Title block. In this case, the anchor is core/post-title, and the relative position is before.

The API includes two methods of implementing block hooks. You can use the blockHooks property in a block’s block.json file or the hooked_block_types filter. Then, the hooked_block filter allows you to modify any previously hooked block. 

While I’ll provide an overview of each method and filter, I encourage you to review the dev notes for a more technical look at the Block Hooks API and the tenets behind its creation. 

block.json

Block hooks specified in block.json are unconditional, meaning the block will be inserted relative to all instances of the anchor block in each template, template part, and pattern.

In a block’s block.json file, include the blockHooks property. This property takes an object where the key is the name of the anchor block, and the value specifies its position. Possible values include before, after, firstChild, and lastChild.

The following example would unconditionally insert the block after all instances of the Post Content block. You can specify more than one anchor.

"blockHooks": {
    "core/post-content": "after"
}

hooked_block_types

The hooked_block_types filter provides more flexibility and is what most examples in this article will use. It lets you hook blocks unconditionally and conditionally. 

The callback function for the filter accepts four parameters and returns the modified array of hooked blocks:

  • $hooked_block_types (array): An array of hooked blocks.
  • $relative_position (string): The relative position of the hooked block: before, after, first_child, or last_child.
  • $anchor_block_type (string): The name of the anchor block.
  • $context (WP_Block_Template|WP_Post|array): The block template, template part, wp_navigation post type, or pattern that the anchor block belongs to.

You will often use the $context parameter when creating conditional hooks. 

Here’s the general structure of the filter and callback function.

function add_example_block_after_post_content_block( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {

	// Unconditionally hook the Example block after the Post Content block.
	if ( 'after' === $relative_position && 'core/post-content' === $anchor_block_type ) {
		$hooked_block_types[] = 'example-block/example-block';
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_example_block_after_post_content_block', 10, 4 );

hooked_block

The hooked_block filter, and its block-specific variant hooked_block_{$hooked_block_type}, were introduced in WordPress 6.5. With these filters, you can modify the attributes of any hooked block or even wrap it in another block, such as a Group.

The callback function for both filters accepts five parameters and returns the modified parsed hooked block:

  • $parsed_hooked_block (array|null): The hooked block, in parsed block array format, or null, indicating that a previous filter has suppressed the injection of the given block.
  • $hooked_block_type (string): The hooked block type name.
  • $relative_position (string): The relative position of the hooked block (can be one of before, after, first_child, or last_child).
  • $parsed_anchor_block (array): The anchor block, in parsed block array format.
  • $context (WP_Block_Template|WP_Post|array): The block template, template part, wp_navigation post type, or pattern that the anchor block belongs to.

The $parsed_hooked_block and $parsed_anchor_block variables are arrays with the format: 

  • blockName (string): The block name.
  • attrs (array): An array of block attributes.
  • innerBlocks (array): An array of inner blocks, each presented in the same format as $parsed_hooked_block.
  • innerContent (array): An array of inner content.

You probably have never worked with this block representation before. While WordPress has used it internally for years, the Block Hooks API is one of the few tools that exposes the parsed block array format to extenders. 

The most effective way to explore these filters is through examples. The remainder of this article will focus on four different applications of the API that you can then adapt for your own projects.

The basics (Example 1)

For the first example, let’s use the Like Button block developed by Bernie Reiter, one of the lead developers behind Block Hooks. It adds a heart icon and the word “Like.” When clicked, the heart turns red. I will also use the Twenty Twenty-Four theme throughout this article, but feel free to experiment with any block theme. 

The Like Button block is “hooked” in two places by default. Its block.json file includes the following blockHooks property, which will unconditionally insert the block as the lastChild of all Comment Template blocks.

"blockHooks": {
    "core/comment-template": "lastChild"
}

Then, in the plugin’s main file like-button.php, the block is conditionally inserted after the Post Content block using the hooked_block_types filter. For reference, the block’s name is ockham/like-button.

function add_like_button_block_after_post_content_block( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {

	// Only hook the block on Single templates (posts).
	if ( ! $context instanceof WP_Block_Template || ! property_exists( $context, 'slug' ) || 'single' !== $context->slug ) {
		return $hooked_block_types;
	}
	
	// Hook the block after the Post Content block.
	if ( 'after' === $relative_position && 'core/post-content' === $anchor_block_type ) {
		$hooked_block_types[] = 'ockham/like-button';
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_like_button_block_after_post_content_block', 10, 4 );

In block themes, the Post Content block can appear in many contexts, such as in a Query loop that’s part of a pattern. The plugin’s conditional approach ensures that the Like Button is only inserted after the Post Content block on single.html templates. Nothing happens if $context is not an instance of the WP_Block_Template object or the slug is not single.

With this conditional, the Like Button will be inserted on custom post types unless the theme provides a dedicated single-$posttype-$slug.html template. Depending on your use case, you may need to create stronger conditionals. For a refresher on the template hierarchy, refer to the visual overview in the Theme Handbook.

You also might be tempted to add WordPress conditional tags like is_singlular() and is_user_logged_in() to refine where and when a block is inserted. However, you should avoid this approach. 

While these conditionals will likely work on the front end, they won’t in the Editor. This can lead to unexpected results. It’s best practice to only build conditionals using the parameters provided by the callback function.

In the following screenshot, you can see what this implementation looks like for posts. The Like Button is inserted after the Post Content block and at the bottom of each Comment Template block.

These blocks are also present in the Site Editor and can be moved or removed by the user. 

Creating a utility plugin

Let’s assume you want to change the location of the Like Button block. You could modify the Like Button plugin directly, but it’s more common not to have direct control over the block you want to insert. Instead, I recommend creating a utility plugin that contains the necessary filters. 

Navigate to the plugins folder within your local WordPress environment. Create a new folder called block-hooks-demo that contains the file block-hooks-demo.php. Add the following header to the file and activate the plugin.

<?php
/**
 * Plugin Name:       Block Hooks Demo
 * Description:       Block Hooks API experiments.
 * Version:           0.1.0
 * Requires at least: 6.5
 */

You can add the example code in this article to the demo plugin instead of modifying your theme’s functions.php file or any other plugin files. This step is not required, but keeping code separate is often useful. 

Inserting the block

In the Like Button plugin, the block is hooked after the Post Content block on posts. Let’s hook the block above the Post Title instead. 

First, you need to use the hooked_block_types to insert the block in the correct place. The code will look like this.

function add_like_button_block_before_post_title_block( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {
	
	// Only hook the block on Single templates (posts).
	if ( ! $context instanceof WP_Block_Template || ! property_exists( $context, 'slug' ) || 'single' !== $context->slug ) {
		return $hooked_block_types;
	}

	// Insert the Like Button block before the Post Title block.
	if ( 'before' === $relative_position && 'core/post-title' === $anchor_block_type ) {
		$hooked_block_types[] = 'ockham/like-button';
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_like_button_block_before_post_title_block', 12, 4 );

Similar to the code in the Like Button plugin above, you need to:

  • First check if the $context is a template and that the template slug is single
  • Then check that the anchor block is core/post-title, and the position is before.

As a general rule, you should always apply conditionals based on $context first.

After saving the file and refreshing the post, the block should be positioned above the Post Title and below the Post Content block (added by the Like Button plugin).

Removing a hooked block

There is no need for two Like Button blocks, so let’s remove the one that appears after Post Content.

There are multiple ways you could do this. In the code above, simply add a condition that unsets the ockham/like-button block if the anchor is Post Content. It would look like this.

function add_like_button_block_before_post_title_block( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {
	
	// Only apply this hook on the single template.
	if ( ! $context instanceof WP_Block_Template || ! property_exists( $context, 'slug' ) || 'single' !== $context->slug ) {
		return $hooked_block_types;
	}

	// Insert the Like Button block before the Post Title block.
	if ( 'before' === $relative_position && 'core/post-title' === $anchor_block_type ) {
		$hooked_block_types[] = 'ockham/like-button';
	}

	// Remove the Like Button block after the Post Content block.
	if ( 'after' === $relative_position && 'core/post-content' === $anchor_block_type ) {
		$hooked_block_types = array_filter( $hooked_block_types, function( $block ) {
			return 'ockham/like-button' !== $block;
		} );
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_like_button_block_before_post_title_block', 12, 4 );

Alternatively, you can use the new hooked_block or hooked_block_{$hooked_block_type} filters. I prefer the block-specific filter, which should be hooked_block_ockham/like-button.

Here’s the complete code.

function remove_hooked_like_button_block_after_post_content( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context  ) {
	
	// Has the hooked block been suppressed by a previous filter?
	if ( is_null( $parsed_hooked_block ) ) {
		return $parsed_hooked_block;
	}

	// Remove any Like Button blocks hooked after Post Content.
	if ( 'core/post-content' === $parsed_anchor_block['blockName'] ) {
		return null;
	}

	return $parsed_hooked_block;
}
// Priority is set to 15.
add_filter( 'hooked_block_ockham/like-button', 'remove_hooked_like_button_block_after_post_content', 15, 5 );

The first condition checks to see if the block has already been suppressed. While not technically needed in this situation, this is a good practice, especially when working with third-party plugins that might also use the Block Hooks API. 

The second condition checks that the Post Content block is the anchor. If it is, return null. This will “unhook” any Like Button block in this position, regardless of the template or pattern. You can use the $context property if you need more control, just like in the hooked_block_types filter. 

When removing blocks, you often need to increase the filter’s priority to ensure it runs after any other filter used to insert the block. Here, I have set the priority to 15. The default is 10

Save the file and refresh the post. The final result should look like this.

This example just scratches the surface of what the hooked_block and hooked_block_{$hooked_block_type} filters can do. 

If neither method removes the hooked Like Button, check to see if you have modified the Single Posts template in the Site Editor since the block was originally hooked. While the Block Hooks API allows you to hook blocks into user-modified templates, if additional modifications are made to the template after a block has been hooked, those changes take precedence. This is done to ensure the user has complete control over all hooked blocks. 

The hooks will be reapplied if you clear all customizations on the Single Posts template. 

Context and block attributes (Example 2)

It’s time to step it up a notch with another example. Let’s use the Copyright Date block from the Block Editor Handbook documentation. This block displays the copyright symbol (©), the current year, and an optional starting year. It also includes attributes and multiple block supports, which will demonstrate the flexibility of the new filters in WordPress 6.5.

Follow the instructions in the Quick Start Guide to scaffold the Copyright Date block in your local WordPress environment. 

Check out the Tutorial: Build your first block if you’re just starting with block development. This guide will walk you through creating the Copyright Date block using the create-block package and provide a solid foundation for building your own custom blocks.

This example aims to automatically insert a block when the plugin is activated. In this case, you will add the Copyright Date block to the site’s footer, a common place for most copyright notices. 

While every block theme’s footer layout will differ, many also include the Site Title block. Twenty Twenty-Four’s default footer shows the site title “Leia Acosta” on the left.

Let’s insert the Copyright Date block after the Site Title block in the theme’s footer template parts and any patterns designed as footers. The Site Title will be the anchor.

Inserting the block

Start by using the hooked_block_types filter to insert the block after the core/site-title block.

function add_copyright_date_block_to_footer( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {

	if ( 
		'core/site-title' === $anchor_block_type &&
		'after' === $relative_position
	) {
		$hooked_block_types[] = 'create-block/copyright-date-block';
	}

return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_copyright_date_block_to_footer', 10, 4 );

At this point, no conditional that includes $context has been set, which means this filter will add the Copyright Date block after every Site Title block on your site.

Understanding context: templates versus patterns

It’s important to understand how the $context variable changes when working with the Block Hooks API. 

Many block themes use the Pattern block inside theme-provided templates and template parts. The Twenty Twenty-Four theme is a good example. Here’s a look at the default footer.html template part.

<!-- wp:pattern {"slug":"twentytwentyfour/footer"} /-->

When the user has not modified a template or template part, anchor blocks within a Pattern block have the $context of the pattern they belong to. If a user modifies the template or part, the content (including the anchor block) is no longer provided by a pattern, so the $context becomes the template object. 

When inserting blocks where context matters, such as a theme’s footer, you must properly account for the value of the $context parameter. 

Setting context

In Twenty Twenty-Four, there is a single footer template part and multiple patterns designed for the footer template part. The template part has the area property set to footer. The patterns all include the property blockTypes set to core/template-part/footer. Refer to the Themes Handbook for more information on template parts and pattern registration.  

As discussed above, $context can be either a WP_Block_Template  or WP_Post object or an array if the anchor block is in a pattern. 

To hook the Copyright Date block in both footer template parts and any patterns designed as footers, you must create a conditional that checks for both. It should look something like this.

function add_copyright_date_block_to_footer( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {

	if ( 
		// Hook the block in footer patterns.
		( is_array( $context ) && isset( $context[ 'blockTypes' ] ) && in_array( 'core/template-part/footer', $context[ 'blockTypes' ] ) ) ||
		// Hook the block in footer template parts.
		( $context instanceof WP_Block_Template && property_exists( $context, 'slug' ) && 'footer' === $context->area )
	) {
		if ( 
			'core/site-title' === $anchor_block_type &&
			'after' === $relative_position
		) {
			$hooked_block_types[] = 'create-block/copyright-date-block';
		}
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_copyright_date_block_to_footer', 10, 4 );

When getting accustomed to the Block Hooks API, it’s helpful to echo print_r( $context ) and other variables. This will help you understand all the available properties you can use to create conditionals.  

With this conditional hook applied, you should see the default Copyright Date block in the footer directly below the Post Title.

Modifying the hooked block

The ability to alter hooked block attributes was one of the most requested features following the introduction of the Block Hooks API in WordPress 6.4.

Earlier in this article, you used the hooked_block_{$hooked_block_type} filter to remove a hooked block. Now, let’s use it to modify the attributes of the hooked Copyright Date block. 

The $parsed_hooked_block variable is an array that includes the attrs property, which you can use to set the attributes of the hooked block

For this example, configure the following: 

  • Enable a starting year by setting showStartingYear to true.
  • Set the startingYear to 2019.
  • Modify the text color by setting textColor to contrast-2 (one of the color presets in Twenty Twenty-Four).
  • Modify the font size by setting fontSize to small.

It can be difficult to know what attributes a block supports and how each attribute is structured, especially with third-party blocks. I recommend building a demo block in the Editor using the UI to apply settings and styles. This ensures the block looks exactly the way you want. Then, switch to the Code Editor to see how the modifications are represented as block attributes. 

The resulting filter should look like this.

function modify_hooked_copyright_date_block_in_footer( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context  ) {

	// Has the hooked block been suppressed by a previous filter?
	if ( is_null( $parsed_hooked_block ) ) {
		return $parsed_hooked_block;
	}

	// Only apply the updated attributes if the block is hooked after a Site Title block.
	if ( 
		'core/site-title' === $parsed_anchor_block['blockName'] &&
		'after' === $relative_position
	) {
		$parsed_hooked_block['attrs'] = array(
			'startingYear'     => '2019',
			'showStartingYear' => true,
			'fontSize'         => 'small',
			'textColor'        => 'contrast-2',
		);
	}

	return $parsed_hooked_block;
}
add_filter( 'hooked_block_create-block/copyright-date-block', 'modify_hooked_copyright_date_block_in_footer', 10, 5 );

You can use the $context variable to be more precise about which hooked blocks this modification applies to. However, since you know that the Copyright Date block has only been hooked after the Site Title in content designed for footers, the $context conditional is not necessary here. 

The modified attributes are also applied in the Site Editor. Notice the block’s color and font settings in the Styles panel in the image below.

innerContent and innerBlocks (Example 3)

Before WordPress 6.5, you could only hook blocks whose save function returned null. In other words, the Block Hooks API worked with dynamic blocks, not statically rendered ones. The introduction of the hooked_block filter now allows you to hook virtually any block using the innerContent and innerBlocks properties. 

Let’s add a static Paragraph block after the Post Content block on all posts to demonstrate how these properties work. In this Paragraph block, a link that reads “Back to top” will take the user to the top of the post when clicked.

Inserting the block

First, use the hooked_block_types filter to insert a Paragraph block in the correct spot. The block should only appear on single posts, so ensure that the template slug is set to single.

function add_paragraph_after_post_content_on_posts( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {

	if ( 
		// Only hook the block on Single templates (posts).
		( $context instanceof WP_Block_Template && property_exists( $context, 'slug' ) && 'single' === $context->slug )
	) {
		if ( 
			'core/post-content' === $anchor_block_type &&
			'after' === $relative_position
		) {
			$hooked_block_types[] = 'core/paragraph';
		}
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_paragraph_after_post_content_on_posts', 10, 4 );

Since Paragraph blocks are empty by default, nothing will display on the front end. You’ll need to use the hooked_block_core/paragraph filter to add the “Back to top” link.

Modifying the hooked block

The setup will look familiar to the previous example. Check that the hooked block has not already been suppressed, and then make sure you are only targeting Paragraph blocks hooked after Post Content.

The final step is to pass the desired HTML to the innerContent property of $parsed_hooked_block.

function modify_hooked_paragraph_block_after_post_content( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context  ) {

	// Has the hooked block been suppressed by a previous filter?
	if ( is_null( $parsed_hooked_block ) ) {
		return $parsed_hooked_block;
	}

	// Only apply the updated attributes if the block is hooked after a Post Content block.
	if ( 
		'core/post-content' === $parsed_anchor_block['blockName'] &&
		'after' === $relative_position
	) {

		$parsed_hooked_block['innerContent'] = array( 
			'<p><a href="#">' . __( 'Back to top' ) . '</a></p>' 
		);
	}

	return $parsed_hooked_block;
}
add_filter( 'hooked_block_core/paragraph', 'modify_hooked_paragraph_block_after_post_content', 10, 5 );

There are a couple of things to note here.

  • The innerContent property accepts an array. 
  • Even though the name of the property is innerContent, you also need to include the HTML for the block itself, which in this case is just a <p> tag. 

There is more to say about innerContent, but let’s look at the result of this filter as it currently stands.

You will notice that the Paragraph block is not properly aligned because it’s not wrapped in a container block with layout enabled. Often, you would use a Group block for this. 

Thankfully, you can use the hooked_block filter to wrap hooked blocks in other blocks. Let’s look at the code and then walk through it.

function modify_hooked_paragraph_block_after_post_content( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context  ) {

	// Has the hooked block been suppressed by a previous filter?
	if ( is_null( $parsed_hooked_block ) ) {
		return $parsed_hooked_block;
	}

	// Only apply the updated attributes if the block is hooked after a Post Content block.
	if ( 
		'core/post-content' === $parsed_anchor_block['blockName'] &&
		'after' === $relative_position
	) {

		$parsed_hooked_block['innerContent'] = array( 
			'<p><a href="#">' . __( 'Back to top' ) . '</a></p>' 
		);

		// Wrap the Paragraph block in a Group block with a contrained layout.
		return array(
			'blockName'    => 'core/group',
			'attrs'        => array(
				"layout" => array(
					"type" => "constrained"
				),
			),
			'innerBlocks'  => array( $parsed_hooked_block ),
			'innerContent' => array(
				'<div class="wp-block-group">',
				null,
				'</div>'
			),
		);
	}

	return $parsed_hooked_block;
}
add_filter( 'hooked_block_core/paragraph', 'modify_hooked_paragraph_block_after_post_content', 10, 5 );

The $parsed_hooked_block variable did not change. However, instead of directly returning this value, the code returns a Group block that includes $parsed_hooked_block in its innerBlocks array. The constrained layout attribute has also been added.

Now the tricky part is innerContent. The first and last elements of the array are the standard opening and closing HTML markup for a Group block. Any null values act as placeholders within the innerContent array for inner blocks. Through interpolation, they are dynamically replaced with corresponding items from the innerBlocks array through interpolation. The number of null entries in innerContent should always equal the number of blocks in innerBlocks, which is why there is a single null in this case. 

As a general principle, keep the HTML strings as long as possible; only create a new array item if you need to interpolate an inner block (by putting a null item into innerContent).

With this code applied, the Paragraph block is now contained in a Group block with the correct layout.

As a final step, let’s make the text smaller, align it to the right, and add some top margin to the Group block. The updated code should look like this. 

function modify_hooked_paragraph_block_after_post_content( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context  ) {

	// Has the hooked block been suppressed by a previous filter?
	if ( is_null( $parsed_hooked_block ) ) {
		return $parsed_hooked_block;
	}

	// Only apply the updated attributes if the block is hooked after a Post Content block.
	if ( 
		'core/post-content' === $parsed_anchor_block['blockName'] &&
		'after' === $relative_position
	) {

		// Set the font size and the alignment of the text in the Paragraph block.
		$parsed_hooked_block['attrs'] = array(
			'align'    => 'right',
			'fontSize' => 'small'
		);
		$parsed_hooked_block['innerContent'] = array( 
			'<p class="has-text-align-right has-small-font-size"><a href="#">' . __( 'Back to top' ) . '</a></p>' 
		);

		// Wrap the Paragraph block in a Group block with a contrained layout and top margin.
		return array(
			'blockName'    => 'core/group',
			'attrs'        => array(
				"layout" => array(
					"type" => "constrained"
				),
				'style'  => array(
					'spacing' => array(
						'margin' => array(
							'top' => 'var:preset|spacing|40'
						)
					)
				)
			),
			'innerBlocks'  => array( $parsed_hooked_block ),
			'innerContent' => array(
				'<div class="wp-block-group" style="margin-top:var(--wp--preset--spacing--40)">',
				null,
				'</div>'
			),
		);
	}

	return $parsed_hooked_block;
}
add_filter( 'hooked_block_core/paragraph', 'modify_hooked_paragraph_block_after_post_content', 10, 5 );

Looking carefully at the code, you will notice that class and style attributes have been added to the HTML of the <div> and <p>. These classes and styles are generated in the Editor and are necessary to ensure no parsing errors. It’s unlikely that you will know the correct structure offhand, so again, I recommend building a complete demo block in the Editor and then looking at the Code Editor to see how it’s structured.

You can bypass the process of destructuring the block manually by storing the wrapper block as an HTML string and then using parse_blocks to parse it. Then you would set the parsed block’s innerBlocks attribute to array( $parsed_hooked_block ) like in the code above. 

Here’s a look at the front end and the Single Post template in the Site Editor. Notice that the top margin is set as expected on the Group block.

The last new feature in WordPress 6.5 that I wanted to highlight is inserting blocks into navigation menus. Previously, you could only hook blocks before or after Navigation blocks. 

The Navigation block is quite complex under the hood. It’s not just a container that holds individual link blocks. Each menu is stored as a custom post type with the slug wp_navigation. In 6.5, this post object (WP_Post) is now passed to $context.

Therefore, to insert a block into an existing menu, you need to check that $context is an instance of WP_Post and that the post type is wp_navigation. Then, you can use the post object’s many properties to construct conditionals that determine whether a block should be inserted. 

Sticking with the basic implementation, the following code inserts the Login/out block as the last menu item in all menus.

function add_loginout_block_to_primary_navigation( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {

	// Is $context a Navigation menu?
	if ( ! $context instanceof WP_Post || 'wp_navigation' !== $context->post_type ) {
		return $hooked_block_types;
	}
	
	if ( 'last_child' === $relative_position && 'core/navigation' === $anchor_block_type ) {
		$hooked_block_types[] = 'core/loginout';
	}

	return $hooked_block_types;
}
add_filter( 'hooked_block_types', 'add_loginout_block_to_primary_navigation', 10, 4 );

The final result will look something like this. 

Next steps

The Block Hooks API has come a long way since its introduction in WordPress 6.4, and it will continue to evolve as it has in 6.5. If you haven’t tried block hooks yet, I hope the examples in this article encourage you to do so. This is one of the most important extensibility features for block themes. 

Finally, more real-world testing and feedback is needed. If you run into a bug or there is a feature you would like to see supported, please open an issue in the Gutenberg GitHub repository. You can also follow the continued development of the Block Hooks API in this tracking issue.

Props to @bernhard-reiter for collaboration and technical review as well as @bph and @greenshady for editorial review.

Categories: , ,

Leave a Reply