WordPress.org

WordPress Developer Blog

Introducing Block Bindings, part 2: Working with custom binding sources

Introducing Block Bindings, part 2: Working with custom binding sources

What if I told you that you could bind custom data from any source to WordPress Core blocks? I’d like to think that it would excite you and get those mental juices flowing.

I know when I first heard that this was possible, I couldn’t wait to try it out. I’ve had so many cool ideas over the years since the introduction of the Block Editor, but no good way to implement them within the system.

WordPress 6.5 will introduce Block Bindings, a new API that will open up a world of possibilities for anyone who needs to dynamically output data without writing custom blocks. In this post, you will learn how to register your own binding source and attach whatever data you want to Core blocks (at least within the limits of what’s possible in 6.5).

In the past few weeks, I’ve built over a dozen features with this new API, and it’s only fair that I share how to do this with you.

This post is a follow-up to Introducing Block Bindings, part 1: Connecting custom fields. I encourage you to read that post first to understand how the Block Bindings API works at a foundational level.

Overview of custom block binding sources

As you learned in the previous post in this series, the Block Bindings API serves as the mechanism for binding attributes to any type of source. WordPress 6.5 will ship two built-in binding sources:

  • core/post-meta: The source for binding custom fields.
  • core/pattern-overrides: The source for handling pattern overrides, another WordPress 6.5 feature. Punted to WordPress 6.6.

For extenders, the core/post-meta source is obviously needed for a lot of projects. But there are many scenarios where you might need to bind data from a different source altogether. Some ideas that come to mind are:

  • Taxonomy term and user data
  • WordPress site data
  • Plugin/theme options
  • Custom database tables
  • A third-party API

There are plans to ship core/site-data, core/user-data, and other binding sources for handling Core data in future releases, but you can certainly start building them on your own in the meantime.

At the block level, custom binding sources work in the exact same way as the core/post-meta source. The big difference is that you have full control over how bindings work under the hood when you register a custom one. And you also have to use the Block Bindings API to register your source.

As discussed in the first post in this series, bindings are limited to the Image, Paragraph, Heading, and Button blocks in WordPress 6.5. Wider support is expected in future versions.

Getting to know the API functions

WordPress 6.5 provides a new register_block_bindings_source() function for registering custom binding sources. It is also used internally to register the custom field and future pattern override sources.

Take a look at the function signature:

register_block_bindings_source(
	string $source_name,
	array $source_properties
);

There are two parameters that you can set:

  • $source_name: A unique name for your custom binding source in the form of namespace/slug.
  • $source_properties: An array of properties to define your binding source:
    • label: An internationalized text string to represent the binding source. Note: this is not currently shown anywhere in the UI.
    • get_value_callback: A PHP callable (function, closure, etc.) that is called when a block’s attribute matches the $source_name parameter.
    • uses_context: (Optional) Extends the block instance with an array of contexts if needed for the callback. For example, if you need the current post ID, you’d set this to [ 'postId' ].

When registering a custom source, you do so on the init hook. So let’s look at an example of what this might look like (we’ll get to a real example in the next section):

add_action( 'init', 'projectslug_register_block_bindings' );

function projectslug_register_block_bindings() {
	register_block_bindings_source( 'projectslug/example-source', array(
		'label'              => __( 'My Custom Bindings', 'projectslug' ),
		'get_value_callback' => 'projectslug_bindings_callback',
		'uses_context'       => [ 'postId', 'postType' ]
	) );
}

That is all the code that is required for registering your binding source with WordPress. 

When WordPress comes across your projectslug/example-source binding source while parsing a block, it will run your callback function. Your function signature should look like this:

projectslug_bindings_callback(
	array $source_args,
	WP_Block $block_instance,
	string $attribute_name
);

It can accept up to three parameters, but you don’t need to define each if you do not need them:

  • $source_args: An array of arguments passed via the metadata.bindings.$attribute.args property from the block.
  • $block_instance: The current instance of the block the binding is connected to as a WP_Block object.
  • $attribute_name: The current attribute set via the metadata.bindings.$attribute property on the block.

To put all of this in the proper context, let’s jump into some real examples.

Decisions: defining the structure of custom bindings

You’ve already learned the basics of how bindings work in Part 1 of this series. Because custom bindings are pretty similar, let’s kick this up another notch and build something slightly more advanced. But don’t worry too much about the complexity—we’re still just covering the basics of what’s possible with the Block Bindings API.

Suppose that you wanted to build a block that showcased a user card. Maybe this is for a company’s team profiles or something along those lines. Depending on the complexity of your user card needs, you might not need a custom block at all. It’s possible you could build it with Core blocks by binding user data to them.

Here’s a screen grab of what we’ll build in the upcoming sections:

In the screenshot, there are three blocks with dynamic data:

  • User display name: Bound to the content of a Heading block
  • Avatar: Bound to the URL of an Image block
  • Bio/Description: Bound to the content of a Paragraph block

Knowing what type of data you need upfront is a crucial part of deciding how your custom binding source will work. Because this is user data, you know that you’ll need the user ID. You also need to access those user data fields individually, so you’ll need an argument for those.

That means the structure of your binding source should expect two arguments:

  • userId: To determine which user’s data to get
  • key: To bind individual user data fields

Those argument names can be anything you want them to be. I decided to use them because they made the most sense to me, but you should use what’s best for your project.

Here’s what the bindings structure will look like when we use it in the editor later:

{
	"bindings":{
		"attribute_name":{
			"source":"projectslug/user-data",
			"args": {
				"userId":1,
				"key":"a_user_data_field"
			}
		}
	}
}

Please don’t skip this step of deciding how you will structure your accepted arguments. This is to avoid management headaches down the road.

Registering a custom binding source

With the expected arguments figured out, it’s time to actually write the code for handling your custom binding source. 

You’ll use the register_block_bindings_source() function to register a new projectslug/user-data source. Add this code to a custom plugin file or your theme’s functions.php:

add_action( 'init', 'projectslug_register_block_bindings' );

function projectslug_register_block_bindings() {
	register_block_bindings_source( 'projectslug/user-data', array(
		'label'              => __( 'User Data', 'projectslug' ),
		'get_value_callback' => 'projectslug_user_data_bindings'
	) );
}

Note that we didn’t define the uses_context array for this example because the binding source is not dependent on a context.

Now, define your callback function in the same file:

function projectslug_user_data_bindings( $source_args ) {
	// If no key or user ID argument is set, bail early.
	if ( ! isset( $source_args['key'] )