Overview

In this recipe, we’ll craft a custom block binding source to connect a block variation to the post excerpt—think of it as pairing the perfect sauce with your dish. Just like a great chef knows how to balance flavors, you’ll learn how to bind dynamic data to your blocks for a seamless editing experience. Grab your utensils, and let’s get binding! 🔗👨‍🍳

Setup

You can choose to either use the repository which provides a development environment or to just download the standalone plugin

Standalone

Instructions

Run the following command in a terminal of your choice from inside the plugins directory of your local WordPress installation.

Zsh
npx @wordpress/create-block@latest bound-excerpt --template @block-developer-cookbook/bound-excerpt

Once the scaffold has completed completed, start the build process from inside the newly created plugin

Zsh
cd bound-excerpt && npm run start

Finally, make sure to activate the plugin.

Repository

Instructions

Checkout the repository (skip this step if already done)

Zsh
git clone git@github.com:ryanwelcher/block-developer-cookbook.git

Install the dependencies

Zsh
npm install

Start the development environment (make sure you have Docker installed )

Zsh
npm run env start

Run the following script from the root of the repository

Zsh
npm run prep:bound-excerpt

Once the scaffold has completed completed, start the build process from inside the newly created plugin

Zsh
cd plugins/bound-excerpt && npm run start

Step 1 – Defining a custom binding source

Block bindings at their core boil down to creating a connection between a block attribute and a binding source. As of WordPress 6.7, the only binding source available is core/post-meta which is used to connect block attributes with custom fields. In this recipe, you are going to set up a block to display and edit the post excerpt in the block editor.

To do this, you’ll need to define a custom block binding source both on the server side and for the block editor. Open bound-excerpt.php and add the highlighted code below to register the custom binding source:

bound-excerpt.php
<?php
/**
 * Plugin Name:       Bound Excerpt
 * Description:       A tutorial on how to use custom block bindings to bind the post excerpt to a block.
 * Requires at least: 6.1
 * Requires PHP:      7.0
 * Version:           1.0.0
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       bound-excerpt
 *
 * @package block-developers-cookbook
 */

namespace BlockDevelopersCookbook;

/**
 * Enqueue the block editor JavaScript file.
 * This loads our block binding implementation in the editor.
 */
add_action(
	'enqueue_block_editor_assets',
	function() {
		$bound_excerpt_file = plugin_dir_path( __FILE__ ) . '/build/bound-excerpt.asset.php';

		if ( file_exists( $bound_excerpt_file ) ) {
			$assets = include $bound_excerpt_file;
			wp_enqueue_script(
				'bound-excerpt',
				plugin_dir_url( __FILE__ ) . '/build/bound-excerpt.js',
				$assets['dependencies'],
				$assets['version'],
				true
			);
		}
	}
);

/**
 * Register a custom block bindings source for post excerpts.
 * This allows blocks to bind their content to a post's excerpt field.
 */
add_action(
	'init',
	function() {
		register_block_bindings_source(
			'block-developer-cookbook/excerpt',
			array(
				'label'              => __( 'Post Excerpt', 'block-developer-cookbook' ),
				'get_value_callback' => __NAMESPACE__ . '\retrieve_excerpt_binding',
				'uses_context'       => array( 'postId' ), // Required context from block.
			)
		);
	}
);

/**
 * Retrieves the post excerpt for use in block bindings.
 * This callback is used by blocks that bind to the post excerpt.
 *
 * @param array    $source_args    The source arguments.
 * @param WP_Block $block_instance The block instance.
 * @return string The post excerpt.
 */
function retrieve_excerpt_binding( $source_args, $block_instance ) {
	$post_id      = $block_instance->context['postId'];
	$current_post = get_post( $post_id );
	return $current_post->post_excerpt;
}

This code registers a block binding source called block-developer-cookbook/excerpt and defines some arguments.

  • label – Human readable label in the UI.
  • get_value_callback – The function to call that will retrieve the value to be displayed. In this case the excerpt.
  • uses_context – The context to use from that block. In this case we need the postId.
PHP
register_block_bindings_source(
	'block-developer-cookbook/excerpt', // The name of the binding source
	array(
		'label'              => __( 'Post Excerpt', 'block-developer-cookbook' )
		'get_value_callback' => __NAMESPACE__ . '\retrieve_excerpt_binding',
		'uses_context'       => array( 'postId' ), // Required context from block.
	)
);

The retrieve_excerpt_binding is passed two parameters:

  • $source_args – An array containing source arguments used to look up the override value
  • $block_instance – The block instance.

In this example, only the $block_instance is used to retrieve the postId from $block_instance->context that is the used to return the post excerpt.

PHP
function retrieve_excerpt_binding( $source_args, $block_instance ) {
	$post_id      = $block_instance->context['postId'];
	$current_post = get_post( $post_id );
	return $current_post->post_excerpt;
}

At this point, you have defined the custom binding source on the server side.

Step 2 – Creating a block variation for the excerpt

Now that you have the custom binding source in place, it is possible to connect a block that supports block bindings. As of WordPress 6.7 those are the Paragraph, Header, Image, and Button blocks.

Unfortunatley, unlike the core/post-meta source, there is no UI to do this and you will need to manually set the attributes in the Code editor view.

In a new post, switch to the Code editor and paste in this snippet that binds a Paragraph block to your custom binding source:

HTML
<!-- wp:paragraph {
	"metadata":{
		"bindings":{
			"content": {
				"source":"block-developer-cookbook/excerpt"
			}
		}
	}
} -->
<p></p>
<!-- /wp:paragraph -->

Switch back to the Visual editor and you should now see this

You can tell that the Paragraph block is bound correctly because the “Post Excerpt” label we defined is being displayed.

The process of manually creating the snippet to insert is tedious and extremely error prone and it’s usually a better idea to create a block variation to manage this.

Open the bound-excerpt.js file and add the highlighted code to register a new block variation for the Paragraph block call “Bound Excerpt”

bound-excerpt.js
/**
 * WordPress dependencies
 */
import {
	registerBlockVariation,
	registerBlockBindingsSource,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { postExcerpt as icon } from '@wordpress/icons';

/**
 * Register a block variation to make it easier to assign the custom binding.
 */
registerBlockVariation( 'core/paragraph', {
	name: 'block-developer-cookbook/excerpt',
	title: __( 'Bound Excerpt', 'block-developer-cookbook' ),
	icon,
	description: __(
		'Mange the post excerpt directly in a block using a custom binding.',
		'block-developer-cookbook'
	),
	isActive: [ 'metadata.bindings.content.source' ],
	attributes: {
		metadata: {
			bindings: {
				content: { source: 'block-developer-cookbook/excerpt' },
			},
		},
	},
	scope: [ 'inserter' ],
} );

You should have a new block available to be inserted

Perfect! You can now insert the block easily.

Step 3 – Reading and writing the values in the block editor

You might have noticed that when you insert the new block into the block editor that only the label “Post Excerpt” is displayed and even if you manually set the excerpt and can only see the actual excerpt on the front end once the post is published.

You can address this by defining the source on the client side as well with the registerBlockBindingsSource function.

Add the highlighted code to bound-excerpt.js

bound-excerpt.js
/**
 * WordPress dependencies
 */
import {
	registerBlockVariation,
	registerBlockBindingsSource,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { postExcerpt as icon } from '@wordpress/icons';

/**
 * Register a block variation to make it easier to assign the custom binding.
 */
registerBlockVariation( 'core/paragraph', {
	name: 'block-developer-cookbook/excerpt',
	title: __( 'Bound Excerpt', 'block-developer-cookbook' ),
	icon,
	description: __(
		'Mange the post excerpt directly in a block using a custom binding.',
		'block-developer-cookbook'
	),
	isActive: [ 'metadata.bindings.content.source' ],
	attributes: {
		metadata: {
			bindings: {
				content: { source: 'block-developer-cookbook/excerpt' },
			},
		},
	},
	scope: [ 'inserter' ],
} );

/**
 * Register the custom bindings so it can be edited in the block editor.
 */
registerBlockBindingsSource( {
	label: __( 'Post Excerpt' ), // Human readable name in the UI
	name: 'block-developer-cookbook/excerpt', // Source name. MUST match the name defined in PHP.
	getValues( { select } ) {
		return {
			content:
				select( 'core/editor' ).getEditedPostAttribute( 'excerpt' ),
		};
	},

	setValues( { dispatch, bindings } ) {
		dispatch( 'core/editor' ).editPost( {
			excerpt: bindings?.content?.newValue,
		} );
	},

	canUserEditValue( { select, context } ) {
		return true;
	},
} );

This function receives an object that supports the following properties:

  • label – Human readable name for the UI
  • name – The name of the source. It must match the name defined in PHP.
  • getValues – A function that retrieves the values connected to the source
  • setValues – A function that allows updating the values connected to the source.
  • canUserEditValue – A function to determine if the user can edit the value. The user won’t be able to edit by default.

getValues

The getValues function receives on object that contains the following items:

  • bindings returns the bindings object of the specific source.
  • clientId returns a string with the current block client ID.
  • context returns an object of the current block context, defined in the usesContext property.
  • select returns an object of a given store’s selectors.

It needs to return a object with the block attribute as the key and the data as the value.

In this block, you are only using the select parameter to facilitate retrieval of the excerpt

JavaScript
getValues( { select } ) {
	return {
			content:
				select( 'core/editor' ).getEditedPostAttribute( 'excerpt' ),
		};
}

getValues

The setValues function recieves an object with all of the same items as getValues but it also recieves a dispatch object to be able to set values in the datastore.

In your implementation, you are getting the new value that has been input into the block from bindings and then using dispatch to update the current post.

JavaScript
setValues( { dispatch, bindings } ) {
	dispatch( 'core/editor' ).editPost( {
		excerpt: bindings?.content?.newValue,
		}
	);
}

Update your code, save and refresh and you should now be able to read and edit the excerpt directly in the block editor.

Well done, Chef! 👨‍🍳