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'] ) || ! 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, and avatar).

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.

Props to @santosguillamot and @czapla for technical feedback and @bph and @eidolonnight for editorial review.

18 responses to “Introducing Block Bindings, part 2: Working with custom binding sources”

  1. Elliott Richmond Avatar

    Love this ❤️ thanks for sharing Justin 🙏

  2. Grégoire Noyelle Avatar

    Thank you very much, Justin.
    It indeed opens many doors.

    With this approach, is it possible to call term meta in an FSE archive template like taxonomy-mytax-myterm.html ?

    1. Justin Tadlock Avatar

      Absolutely. And that old post is one of the primary use cases I have in mind for some theme work I want to accomplish.

      I should note that the Cover block (example from the post) does not yet support the Block Bindings API, but you can definitely use the Image block in WordPress 6.5 to do this.

      As a side note: I’m currently outlining a more in-depth tutorial that explores the various ways that you can integrate Block Bindings into a theme (a sort of real-world use case). I just needed to work through these first two introductory posts first.

      1. Grégoire Noyelle Avatar

        Hello Justin,
        What great news. I have been following your articles since 2008 (CPT, custom taxonomy). Thank you again for the quality of your contributions, which remain steadfast over time.

  3. NICHOLAS AMOL GOMES Avatar
    NICHOLAS AMOL GOMES

    Hello justin Great job to hear n give up finaly step i waiting long time for that

  4. Rose Avatar
    Rose

    Block binding is great! Is there a way to add static text in front or after the dynamic value?
    For example, I have a CPT called “Books” and a ‘ISBN’ custom field.
    I have added a value to the ‘ISBN’ custom field (000001) and added the block binding code to the block editor.
    It’s displaying the ISBN number on the front-end, but I want to display the static text ‘ISBN’ before the dynamic value like this:
    ISBN: 000001

    Is there a way to do that?

    Thank you!

    1. Justin Tadlock Avatar

      You can absolutely do this with a custom binding source. The Hello Dolly example in the post above shows static text that prefixes the dynamic text.

      The other alternative is to use a Row block to wrap your static text in a Paragraph block and your binding in a separate Paragraph.

      1. rose18 Avatar

        Thank you Justin! I’ve used the Hello Dolly example for displaying the ISBN and added a prefix text. It works, however, if the ISBN custom field is empty, it shows the placeholder text instead of returning nothing.
        This is my code:

        Added to functions.php file:

        register_block_bindings_source( ‘sp/book-isbn’, array(
        ‘label’ => __( ‘Book ISBN’, ‘twentytwentyfour’ ),
        ‘get_value_callback’ => ‘sp_book_isbn_binding’
        ));

        function sp_book_isbn_binding( $source_args ) {
        if ( ! isset( $source_args[‘key’] )) {
        return null;
        }
        $key = ‘book_isbn’;
        if ( $key === $source_args[‘key’] ) {

        $value = get_field($key);
        if($value){
        return sprintf(
        esc_html__(‘%s’, ‘twentytwentyfour’),
        ISBN: ‘.$value
        );
        }else{
        return null;
        }

        }

        return null;
        }

        block editor code:

        Book ISBN placeholder

        When the ISBN custom field is empty, it’s shows the placeholder text ‘Book ISBN placeholder’. I don’t want that, nor an empty . I want it to display nothing.

        Below is my previous code without any custom coding:

        The value of this paragraph comes from a block binding and will be replaced at render.

        The custom field is created using ACF, and if the ISBN custom field is empty, it returns nothing, which I want, but there isn’t a way to display prefix text.

        Is there a way to return nothing (nor empty p tag) when no value is added to the ISBN custom field, as well, can add prefix text?

        Thank you!

      2. rose18 Avatar

        On the post that I posted a couple of minutes ago (comment is still awaiting moderation.), it seems that the block editor code isn’t showing up. Below are the screenshots of my 2 examples:

        Block editor code for displaying the ISBN number, following the Hello Dolly example:
        https://snipboard.io/K4ubji.jpg

        Previous block editor code without custom codes:
        https://snipboard.io/tNGuPa.jpg

        The issue is posted on my previous comment.

        thank you

  5. Mateus Machado Luna Avatar
    Mateus Machado Luna

    Man this is really what we’ve been dreaming for years since the first days of Gutenberg!

    One technical question: say I’m to display a lot – I mean a lot – of fields, not just three, as in the example that you brought for the user. Hopw should I draw a line for deciding if I should define different Block Binding Sources or keep one that handles things via $source_args?

    Also, considering that uses_context… does that context comes from the current editor only? I assume it says which post you are editing for example, but could I for example, get information from parent blocks?

    1. Justin Tadlock Avatar

      Man this is really what we’ve been dreaming for years since the first days of Gutenberg!

      Me too! I haven’t been this excited about a feature in a while.

      One technical question: say I’m to display a lot – I mean a lot – of fields, not just three, as in the example that you brought for the user. Hopw should I draw a line for deciding if I should define different Block Binding Sources or keep one that handles things via $source_args?

      Personally, I break the sources down by location. So, if it’s user data, I might do something like a namespace/user-data source. Or, if I’m pulling theme-specific data, I would do namespace/theme-data, for example.

      For me, that makes the most sense. But I’m also open to hearing how others would approach this, which could be different based on the project.

      Also, considering that uses_context… does that context comes from the current editor only? I assume it says which post you are editing for example, but could I for example, get information from parent blocks?

      This was added late in the process and was something I didn’t really get to explore much. I’m assuming any context passed down from parent blocks would be accessible if you specifically add it to uses_context.

      I’ll ping some folks who worked on this to see if they could go into more detail.

  6. Mario Santos Avatar

    Also, considering that uses_context… does that context comes from the current editor only? I assume it says which post you are editing for example, but could I for example, get information from parent blocks?

    uses_context basically extends the block context if needed by the source: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-context/

    For example, for core/post-meta source, the post ID is needed. However, blocks like image don’t have it defined by default in the block.json usesContext property: https://github.com/WordPress/gutenberg/blob/6ba2c4e8d938ddf6905ec5f0ee911f5b7726a397/packages/block-library/src/image/block.json#L7

    This way, it extends that part to ensure the post ID is available for the block bindings source.

    In the case of parent blocks, if they provide any context with providesContext, you could consume that from your block bindings source.

  7. Carsten Avatar

    It was new to me, that *we* don’t have a native way to show the name of the current post type using blocks. Maybe I just haven’t found it.

    But I made one, which you can find at:
    https://gist.github.com/carstingaxion/f98c213aad40d6413fe50eac1cc92228

    1. Carsten Avatar

      … using the Block Bindings API (that’s why I wrote this comment)

    2. Justin Tadlock Avatar

      Neat! Another thing you could do to extend this is to add a key argument. If it’s passed, pull a specific argument from the registered post type object, such as a label.

      1. Carsten Avatar

        Good idea!

        I just thought about `post_type_supports`, which seems to be a similar direction.

      2. Carsten Avatar

        Implemented. The block can now also show labels and post_type_supports.

        https://gist.github.com/carstingaxion/f98c213aad40d6413fe50eac1cc92228

Leave a Reply