With the Block Bindings API, you can use blocks to display content and dynamic data from custom fields and other sources. This feature requires WordPress version 6.5 or newer.
In short, the Block Bindings API works by registering a source and referencing the source in an attribute on a block. The block then displays the content from the source.
You can register a custom source with PHP or use a custom field (a post meta) that you either register yourself, or create with a plugin like Meta Box or Advanced Custom Fields.
As a whole, the feature is looking very promising. In the first version of the API, there are some known limitations:
- It can only be used with the Heading, paragraph, button, and image blocks.
- There is no user interface for binding (connecting) a block to a source. You must manually add the attribute, the JSON object, to the block markup.
- Dynamic data from custom sources displays correctly on the front but uses a placeholder in the editor.
This lesson is explorative since I am learning about the feature myself. Once more documentation and examples are available, I will update this page, which will mature as a resource.
When you follow the examples, I strongly recommend starting with the basics and not skipping directly to the example with the custom source.
Why the Block Bindings API is so important for WordPress
The new API solves a major problem for developers and site creators. It makes using blocks and block themes more viable for custom site projects. It is no wonder that it is one of the most anticipated WordPress features in 2024.
Before the Block Bindings API, you needed to code a custom block or use ACF Blocks from Advanced Custom Fields PRO to display this type of data in a block theme.
Or, if you have ever tried to add dynamic data to a block pattern using different WordPress PHP functions, you know it doesn’t work. Once the pattern is saved to the template or content, it’s static. The Block Bindings API solves this as well. It can be used for both basic features and complex integrations.
From requiring advanced coding to being a no-code solution
The Block Bindings API brings a massive time saver and reduced complexity. In WordPress 6.5, the required skills for using the Block Bindings API are PHP and knowing how to edit block markup manually.
The PHP step is optional if you would rather use a plugin with an interface to create the post meta. For example, if you are already using ACF, it works with the new API, and there is no need to make major changes to your workflow. I recommend the videos linked in the resource section for more info about block bindings with ACF.
This is only the first version; contributors are working on adding the interface and more built-in sources. The announcement post has more information about the future of the Block Bindings API and links to issues in the Gutenberg GitHub repository. If you want to try the latest changes, you can install the development version of Gutenberg.
Using post meta as a block binding source
This basic example registers a new custom field for a post that accepts a single text value and displays the text using a paragraph block.
First, go to your WordPress test installation and create a new post (Make sure you have WordPress version 6.5 or newer, depending on when you read this). Enable the custom fields UI in the block editor from Options > Preferences:
By the way, it doesn’t matter if you are using a classic theme or a block theme; the Block Bindings API works with both. You could also register the source in a plugin.
How to register the post meta
Open your plugin or theme files in your code editor. -It is not enough to add new fields and values in the Custom Fields UI: you do need to register the post meta.
If you are using a block theme and don’t already have a functions.php file, create one now.
To register the post meta, you can use register_meta() with these two parameters and an array of arguments:
- object _type: In this example, we are working posts, so this will be ‘post’.
- meta_key: A unique identifier and the
name
for the key in the custom fields UI. Remember to use a unique prefix, for example, the theme slug.
For all the details about using register_meta(), please see the WordPress code reference.
<?php
register_meta(
'post',
'themeslug_example_text', // Your meta key
array(
'show_in_rest' => true, // Required
'single' => true, // Required
'type' => 'string',
'sanitize_callback' => 'wp_strip_all_tags' // The name of the sanitization function
)
);
According to the official documentation for block bindings, you must set show_in_rest
to true. During testing, I also found that single
has to be true, or the binding will not work.
Do not forget to sanitize the input that the user is entering into the meta field; you can not trust that the user input is safe. In this example, the expected format is plain text.
In the custom fields UI for your post, click on the button that says “Enter new”, and set the name to prefix_example_text and the value to “Hello World”. Then click on the button with the text “Add Custom Field”.
Nothing will show until you bind the meta data to the block.
How to bind the source to a paragraph
Switch to the code editor mode in the block editor. At the top of your new post, you should see an empty paragraph. If not, just add one.
<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->
The new attribute that you need to add to the paragraph is called a metadata binding:
<!-- wp:paragraph {
"metadata":{
"bindings":{
...
}
}
} -->
<p></p>
<!-- /wp:paragraph -->
Don’t forget the opening and closing curly brackets. You can leave the <p></p> empty. WordPress will replace anything you add there with the content from your source.
The visible text in the editor and the front will be the value of the post meta.
What you add inside a binding depends on the properties of the block, and it helps to be familiar with how the different blocks work. You want the value of your binding to show as the content of the paragraph, so unsurprisingly, you need to add a property called content
:
<!-- wp:paragraph {
"metadata":{
"bindings":{
"content":{
...
}
}
}
}
} -->
<p></p>
<!-- /wp:paragraph -->
And inside the content, add core/post-meta
as your source, and the name of your meta key:
<!-- wp:paragraph {
"metadata":{
"bindings":{
"content":{
"source":"core/post-meta",
"args":{
"key":"themeslug_example_text"
}
}
}
}
} -->
<p></p>
<!-- /wp:paragraph -->
The same example without the line breaks:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"prefix_example_text"}}}}} -->
<p></p>
<!-- /wp:paragraph -->
Save the post and view your text on the front. -That’s all for the basic example; on to the next.
Using a custom block binding source
For this section, I planned to show how to use a PHP date function to display the copyright date, but then that exact example was used in the introduction post for the feature. So, I needed to pick something else, but I will ensure the tutorial shows how to bind both paragraphs, buttons, and images.
When registering a custom source, you must use the function register_block_bindings_source() attached to an init
hook. These are the parameters:
- source_name: A unique identifier with a prefix followed by a slug, eg
themeslug/custom-source-example
. - source_properties: An array of properties:
- label: A label that describes the source. Required.
- get_value_callback: A reference to your callback, for example, a function name, which will be run when the bound block is parsed.
- uses_context: An array of block contexts that extends the WP_Block class so that the contexts can be passed to the callback. Optional.
A block context is a concept used in block development, so don’t worry if this feels unfamiliar. Basically, it lets blocks pass data to an inner block. A block can either provide or use a context, or both. Like a query block passing the query to the post template, that passes the post id to the post title block. Read about block contexts in the block editor handbook.
Among the currently supported blocks: Heading, paragraph, button and image, only the paragraph uses a context, and that single context is the post id.
<?php
function themeslug_register_block_bindings() {
register_block_bindings_source(
'themeslug/custom-source-example',
array(
'label' => __( 'Example', 'themeslug' ),
'get_value_callback' => 'themeslug_bindings_callback',
'uses_context' => ['postId']
)
);
}
add_action( 'init', 'themeslug_register_block_bindings' );
Understanding the optional parameters of the callback
The callback can accept three optional parameters from register_block_bindings_source(). From the introduction post:
- $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 key on the block.
themeslug_bindings_callback( $source_args, $block_instance, $attribute_name ) {
...
}
Confused? So was I, so as part of the learning process, I wanted to try to describe them in my own words:
block_instance: An instance of the WP_Block class, an Object, which is the parsed block, including the block name, context, inner content, inner blocks, and more. See WP_Block in the code reference.
attribute_name: The easiest way for me to think about this value is that it is the name of the block property that uses the source.
In the first example, I showed how to add your text to the content of the paragraph block:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"prefix_example_text"}}}}} -->
For the other supported core blocks, the attribute names would be:
- Heading block: content
- Image block: url, alt, title
- Button block: url, text, linkTarget, rel.
source_args: So if the attribute_name is the property that uses the source, then the source args is the array with key and value pairs that are inside content.args, right? In the example with the post meta, that would be the key and the name of the post meta key:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"prefix_example_text"}}}}} -->
So, if you are only using one binding, you won’t need any of these parameters. You only need to return a single value.
If you are binding a single value to a block that has more than one attribute, WordPress will use it with the first attribute.
Image block example 1: URL
Try this example to enjoy a wonderful photo of my colleague at Yoast, Buffy.
Add this code to functions.php:
<?php
function themeslug_register_block_bindings() {
register_block_bindings_source(
'themeslug/custom-source-example',
array(
'label' => __( 'Example', 'themeslug' ),
'get_value_callback' => 'themeslug_bindings_callback'
)
);
}
add_action( 'init', 'themeslug_register_block_bindings' );
function themeslug_bindings_callback() {
return 'https://pd.w.org/2023/04/893642d3da2481166.65757956.jpg';
}
Add the image block with the binding to your post in the code editor mode:
<!-- wp:image {"metadata":{"bindings":{"url":{"source":"themeslug/custom-source-example"}}}} -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image -->
Paragraph block example
Now, let’s credit Carole for the photo she shared in the WordPress photo directory. The image block caption can’t be bound yet, so add a new paragraph to the post after the image:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"themeslug/custom-source-example"}}}} -->
<p></p>
<!-- /wp:paragraph -->
Now, the callback function must return two values, one for the image URL and one for the paragraph content.
To do this, you could use the block_instance Object to determine if the current block is the image or paragraph:
function themeslug_bindings_callback( $source_args, $block_instance ) {
if ( 'core/image' === $block_instance->parsed_block['blockName'] ) {
return 'https://pd.w.org/2023/04/893642d3da2481166.65757956.jpg';
} elseif ( 'core/paragraph' === $block_instance->parsed_block['blockName'] ) {
return __( 'Photo by: Carole', 'themeslug' );
}
}
But since you only use one value from the Object in this case, it is not ideal. Here is a simpler way to use the attribute_name to determine if it is the content or the URL.
function themeslug_bindings_callback( $source_args, $block_instance, $attribute_name ) {
if ( 'url' === $attribute_name ) {
return 'https://pd.w.org/2023/04/893642d3da2481166.65757956.jpg';
} elseif ( 'content' === $attribute_name ) {
return __( 'Photo by: Carole', 'themeslug' );
}
}
Image block example 2: Alt text
But wait, we forgot something: The image needs an alternative text. Update the block markup for the image in the code editor mode and add a second binding for the alt. Use the same source:
<!-- wp:image {"metadata":{"bindings":{"url":{"source":"themeslug/custom-source-example"}, "alt":{"source":"themeslug/custom-source-example"}}}} -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image -->
Return the alt text using the callback:
function themeslug_bindings_callback( $source_args, $block_instance, $attribute_name ) {
if ( 'url' === $attribute_name ) {
return 'https://pd.w.org/2023/04/893642d3da2481166.65757956.jpg';
} elseif ( 'content' === $attribute_name ) {
return __( 'Photo by: Carole', 'themeslug' );
} elseif ( 'alt' === $attribute_name ) {
return __( 'A small black and brown dog is swimming in a pool with clear turquoise water.', 'themeslug' );
}
}
Button block example
Now, we only have the button block left to try out (because the heading is identical to the paragraph). Let’s add a button block directing visitors to the WordPress photo directory.
As a reminder, the button block uses url, text, linkTarget, and rel.
I know that you have probably already anticipated this, but adding the button would mean you would have two URLs, and the condition in the callback would no longer work.
This is solved by adding unique identifiers in metadata.bindings.url.args:
The updated image block:
<!-- wp:image {
"metadata":{
"bindings":{
"url":{
"source":"themeslug/custom-source-example",
"args":{
"key":"themeslug_image_url"
}
},
"alt":{
"source":"themeslug/custom-source-example"
}
}
}
} -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image -->
In the button block below, you can see that I have added the temporary text “Custom Source”. This makes it easier to see that the block has a binding. The source will replace the text, but only on the front.
To make the URL binding work, ensure that the button block markup includes the <a>
tag.
<!-- wp:buttons -->
<div class="wp-block-buttons">
<!-- wp:button {
"metadata":{
"bindings":{
"url":{
"source":"themeslug/custom-source-example",
"args":{
"key":"themeslug_button_url"
}
},
"text":{
"source":"themeslug/custom-source-example"
}
}
}
} -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Custom Source</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
Now, take advantage of the third and final parameter for the callback, source_args, using the keys from the two blocks: themeslug_image_url and themeslug_button_url:
function themeslug_bindings_callback( $source_args, $block_instance, $attribute_name ) {
// Image
if ( isset( $source_args['key'] ) && 'themeslug_image_url' === $source_args['key'] ) {
return 'https://pd.w.org/2023/04/893642d3da2481166.65757956.jpg';
} elseif ( 'alt' === $attribute_name ) {
return __( 'A small black and brown dog is swimming in a pool with clear turquoise water.', 'themeslug' );
// Paragraph
} elseif ( 'content' === $attribute_name ) {
return __( 'Photo by: Carole', 'themeslug' );
// Button
} elseif ( isset($source_args['key']) && 'themeslug_button_url' === $source_args['key'] ) {
return 'https://wordpress.org/photos/?s=dogs';
} elseif ( 'text' === $attribute_name ) {
return __( 'See more photos', 'themeslug' );
}
}
Result
Block bindings in the user interface
Here is how you can identify if a block has a binding:
- Block bindings are synced across your site, and use the same purple accent color as synced patterns.
- The image block placeholder text is updated to show information about the binding.
From WordPress version 6.6, the block settings sidebar also shows information about the source of the binding in a panel called Attributes:
This interface is being update in future versions of WordPress, and the changes can already be tested in the Gutenberg plugin.
Editing bindings
In the introduction of this lesson I mentioned that there is no user interface for adding a new block binding source. Editing and removing block bindings using the control in the sidebar is under development and is planned to be included in WordPress 6.7.
From WordPress 6.6, users with the correct permission can edit some block bindings directly in the content in the editor, without needing to open the code editor mode:
- Heading
- Paragraph
- The source must be a post meta, not a custom dynamic source
Selecting and editing a bound block updates the value in the binding.
If you want to edit the value of the binding directly in the block, make sure that the meta boxes are not open! If the meta box is open, the new value will not be saved.
Resources
New Feature: The Block Bindings API
Introducing Block Bindings, part 1: connecting custom fields
Introducing Block Bindings, part 2: Working with custom binding sources
Under development
If you want to learn about planned updates to block bindings I recommend this issue in the WordPress Gutenberg repository on GitHub:
Block Bindings iteration for WordPress 6.7