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.
Table of Contents
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.Punted to WordPress 6.6.core/pattern-overrides
: The source for handling pattern overrides, another WordPress 6.5 feature.
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 ofnamespace/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 themetadata.bindings.$attribute.args
property from the block.$block_instance
: The current instance of the block the binding is connected to as aWP_Block
object.$attribute_name
: The current attribute set via themetadata.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 getkey
: 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'] ) || ! isset( $source_args['userId'] ) ) {
return null;
}
// Get the user ID.
$user_id = absint( $source_args['userId'] );
// Return null if there's no user ID at all.
if ( 0 >= $user_id ) {
return null;
}
// Return the data based on the key argument.
switch ( $source_args['key'] ) {
case 'name':
return esc_html( get_the_author_meta( 'display_name', $user_id ) );
case 'description':
return get_the_author_meta( 'description', $user_id );
case 'avatar':
return esc_url( get_avatar_url( $user_id ) );
default:
return null;
}
}
There are a few things this function is doing:
- It first checks if the
key
argument was passed in. - It then checks for a user ID via the
userId
argument. - Finally, it checks the
key
value against a predefined set of cases that the binding source supports (name
,description
, andavatar
).
If you’re returning data for an attribute that has a Rich Text type, you may not need to escape it. The Block Bindings API will pass source values through wp_kses_post()
before the content is rendered. For example, in the above code, the user description may contain Rich Text, so we let WordPress deal with it and preserve HTML tags.
The most important thing is that the function returns some type of data. This should either be a value based on the arguments or null
if nothing is found.
Using a custom binding source
For the block code, I’m going to keep this pretty simple by using a Group block with nested Heading, Image, and Paragraph blocks. Feel free to play around with the layout.
Open a new post or page in your WordPress admin. Then, copy and paste this block markup into the Code Editor:
<!-- wp:group {"style":{"spacing":{"blockGap":"1rem"}},"layout":{"type":"default"}} -->
<div class="wp-block-group">
<!-- wp:heading {
"level":3,
"metadata":{
"bindings":{
"content":{
"source":"projectslug/user-data",
"args":{
"userId":1,
"key":"name"
}
}
}
}
} -->
<h3 class="wp-block-heading"></h3>
<!-- /wp:heading -->
<!-- wp:image {
"width":"96px",
"height":"96px",
"scale":"cover",
"metadata":{
"bindings":{
"url":{
"source":"projectslug/user-data",
"args":{
"userId":1,
"key":"avatar"
}
}
}
},
"align":"left",
"style":{"layout":{"selfStretch":"fit","flexSize":null}}
} -->
<figure class="wp-block-image alignleft is-resized">
<img src="" alt="" style="object-fit:cover;width:96px;height:96px"/>
</figure>
<!-- /wp:image -->
<!-- wp:paragraph {
"metadata":{
"bindings":{
"content":{
"source":"projectslug/user-data",
"args":{
"userId":1,
"key":"description"
}
}
}
}
} -->
<p></p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
If you switch back to the Visual Editor, your output should look like this:
When selected, blocks that are bound will show a purple outline and have an indicator in the toolbar. In the longer term, WordPress will open the editor-side API for customizing the default output.
Just for fun
The Block Bindings API is not just another feature. It symbolizes the hope and enthusiasm of WordPress developers around the world. With that in mind, let’s have a little fun with this by connecting the Hello Dolly plugin to a Core block!
If you don’t already have Hello Dolly installed and activated—as any true WordPress enthusiast would—go ahead and do that now.
Open either your plugin file or your theme’s functions.php
file and add the following inside your existing projectslug_register_block_bindings()
function:
register_block_bindings_source( 'projectslug/fun-stuff', array(
'label' => __( 'Fun Stuff', 'projectslug' ),
'get_value_callback' => 'projectslug_fun_bindings'
));
Since this is a different binding source than the user data, it makes sense to register a separate source. In this case, it is projectslug/fun-stuff
.
Now add your source callback function in the same file:
function projectslug_fun_bindings( $source_args ) {
if ( ! isset( $source_args['key'] )) {
return null;
}
if ( 'hello' === $source_args['key'] && function_exists('hello_dolly_get_lyric')) {
return esc_html(sprintf(
// Translators: %s is a lyric from the Hello Dolly plugin.
__('🎺 🎶 %s', 'projectslug'),
hello_dolly_get_lyric()
));
}
return null;
}
To test how this works, create a new post, page, or template from the Block Editor. Then open the Code Editor view and paste this block markup into it:
<!-- wp:paragraph {
"metadata":{
"bindings":{
"content":{
"source":"projectslug/fun-stuff",
"args":{
"key":"hello"
}
}
}
}
} -->
<p>Displays a random lyric from the Hello Dolly plugin if installed.</p>
<!-- /wp:paragraph -->
When you switch back to the Visual Editor, you should see something similar to this:
As you can see, we used a similar technique as described earlier to output some default content in the editor.
Now save the post and view it on the front end, which should now show a random lyric from the Hello Dolly plugin:
Of course, you can use this technique to bind function output from your plugins and themes. I chose this example to show that you don’t necessarily need to think in terms of just grabbing data from a meta table. The goal is to—with any luck—get you to start thinking outside the box and experimenting with what’s possible in your own projects.
Conclusion
I wasn’t just joking around when I said that the Block Bindings API symbolizes hope for WordPress developers.
We’ve all heard the request of WordPress co-founder Matt Mullenweg to learn JavaScript deeply. And while that remains as true today as ever, I also feel like those of us who love writing PHP code have something to be really excited about again. The Block Bindings API feels like a return to the WordPress that many of us learned in the classic era.
While this is still “version 1.0” of the API, I have fallen head over heels with the possibilities it will offer not only in WordPress 6.5 but for years to come. This is one of the most revolutionary developer tools to land in WordPress in a long while.
It will change the way we work with WordPress.
Resources
The Block Bindings API is still undergoing some final touches at the time of publishing. WordPress 6.5 will only ship the initial version of the API, which is expected to have continual improvements in future releases. To follow along with current and future changes, please use these resources.
- Developer Note: The Block Bindings API
- Connecting block attributes and custom fields
- Block Bindings API
- Related tickets for WordPress 6.5:
- Polish the current UX when block attributes are bound
- Pressing Enter from connected block doesn’t move focus to the newly created block
- Metadata attribute is not preserved after transforming paragraph block into heading
- Fix disable bindings editing when source is undefined
- Add visual indicator if a block is connected to block binding source
Props to @santosguillamot and @czapla for technical feedback and @bph and @eidolonnight for editorial review.
Leave a Reply