WordPress 6.7 is expected to ship on November 12, 2024, and one of the biggest features is the ability to bind block attributes to custom fields directly from the Editor itself. If that doesn’t get you excited about this release, I don’t know what will.
But if you’re one of those developers who has been registering custom binding sources since WordPress 6.5, you may be wondering if WordPress will give the same treatment to your custom sources.
Yes. If you’re willing to put in a little legwork.
WordPress 6.7 will ship a public API for manipulating your blocks connected to custom binding sources in the Editor. Well, at least a partial API that gives you a whole lot of new power to build some interesting features.
In this tutorial, I’ll walk you through making the data from your custom binding sources appear in the Editor and also letting users edit that data from the connected blocks.
For more information on every Block Bindings API update coming to WordPress 6.7, check out the dev note on the Make Core blog.
Table of Contents
Setting up your plugin
Let’s get the basics out of the way first. Create a new folder named devblog-editor-bindings
in your /wp-content/plugins
directory with the following files and subfolders:
/public
/resources
/js
/editor.js
/plugin.php
/package.json
/webpack.config.js
You can, of course, do this same thing from within a theme, but keep in mind that you’ll need to change any plugin-specific functions used in the code examples to use functions appropriate for a theme.
As always when creating a plugin, you must set up your main plugin’s file header so that WordPress recognizes it as a valid plugin. Go ahead and add this code to your plugin.php
file:
<?php
/**
* Plugin Name: Dev Blog Editor Bindings
* Plugin URI: https://developer.wordpress.org/news
* Description: Exploring the Block Bindings API in the editor.
* Version: 1.0.0
* Requires at least: 6.7
* Requires PHP: 7.4
* Author: Your Name
* Author URI: https://developer.wordpres.org/news
* Text Domain: devblog
*/
// Custom code goes here.
Now open package.json
and add the start
and build
scripts, which will point to the /resources
and /public
folders you created earlier:
{
"name": "devblog-editor-bindings",
"scripts": {
"start": "wp-scripts start --webpack-src-dir=resources --output-path=public",
"build": "wp-scripts build --webpack-src-dir=resources --output-path=public"
}
}
Then open your computer’s command line program and type this command to install the @wordpress/scripts
and path
packages:
npm install @wordpress/scripts path --save-dev
The final setup step is extending WordPress’s webpack config so that it knows how to process your custom editor.js
file (this step may not be needed in the future if this pull request is merged). Add this code to your webpack.config.js
file:
// WordPress webpack config.
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
// Utilities.
const path = require( 'path' );
// Add any a new entry point by extending the webpack config.
module.exports = {
...defaultConfig,
...{
entry: {
'js/editor': path.resolve( process.cwd(), 'resources/js', 'editor.js' )
}
}
};
With the foundation in place, let’s jump into the fun bits.
Registering a custom block binding source
Registering custom block bindings is well-trodden ground, so I won’t walk through every detail of the process. If this is your first experience with the Block Bindings API, stop here and read the two introductory tutorials:
- Introducing Block Bindings, part 1: Connecting custom fields
- Introducing Block Bindings, part 2: Working with custom binding sources
You’ll learn how to get and set post data with JavaScript as this tutorial progresses. But the first thing you need to do is register a binding for handling this data on the front end. Add this code to your plugin.php
file:
add_action( 'init', 'devblog_register_binding_sources' );
function devblog_register_binding_sources() {
register_block_bindings_source( 'devblog/post-data', [
'label' => __( 'Post Data', 'devblog' ),
'get_value_callback' => 'devblog_post_data_callback',
'uses_context' => [ 'postId' ],
]);
}
In the code above, the get_value_callback
value is set to devblog_post_data_callback
. WordPress triggers this function whenever it finds a block attribute that is connected to the binding source on the front end.
Let’s keep this relatively simple and focus on three pieces of data for our custom bindings source:
- Post title
- Post excerpt
- Post permalink
Each of these are strings, so they are good fits for the current Block Bindings API. Add this callback function to your plugin.php
file:
function devblog_post_data_callback( $args, $block, $name ) {
if ( ! isset( $args['key'] ) ) {
return null;
}
$post_id = $block->context['postId'] ?? get_the_ID();
if ( 'title' === $args['key'] ) {
return get_post_field( 'post_title', $post_id );
} elseif ( 'excerpt' === $args['key'] ) {
return get_post_field( 'post_excerpt', $post_id );
} elseif ( 'permalink' === $args['key'] ) {
return get_permalink( $post_id );
}
return null;
}
This function retrieves the value of the post title, excerpt, or permalink when connected to a block attribute. Otherwise, it returns null
.
Adding block markup
Now let’s make sure that the custom binding source actually works—it won’t do any good to jump into JavaScript if the initial implementation is broken. As always, test early and often.
Go to Posts > Add New Post in your WordPress admin. On the new post screen, click the Options button (⋮ icon in the top corner) and switch to the Code editor view in the dropdown. Then add this block markup before switching back to Visual editor:
<!-- wp:heading {
"placeholder":"Add post title",
"metadata":{
"bindings":{
"content":{
"source":"devblog/post-data",
"args":{"key":"title"}
}
}
}
} -->
<h2 class="wp-block-heading"></h2>
<!-- /wp:heading -->
<!-- wp:paragraph {
"placeholder":"Add post excerpt",
"metadata":{
"bindings":{
"content":{
"source":"devblog/post-data",
"args":{"key":"excerpt"}
}
}
}
} -->
<p></p>
<!-- /wp:paragraph -->
<!-- wp:buttons -->
<div class="wp-block-buttons">
<!-- wp:button {
"metadata":{
"bindings":{
"url":{
"source":"devblog/post-data",
"args":{"key":"permalink"}
}
}
}
} -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Read post
</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
I know what you’re thinking: this doesn’t look like anything special. You’ll see something like this:
Basically, what you’ve done is recreate common WordPress blocks using the Block Bindings API:
- The Heading block’s content is connected to the post title.
- The Paragraph block’s content is connected to the post excerpt.
- The Button block’s URL is connected to the post permalink.
You’ll also see the Attributes panel showing the connection in the sidebar. In WordPress 6.7, you can’t overwrite the label displayed in that panel, but this should change in a future release.
While that might not seem revolutionary on its own, it actually represents something truly powerful. You can take any data and connect it to basic elements like a heading, paragraph, and button.
This tutorial uses the existing post data because it’s readily available and the simplest thing to showcase. But there are no limits to what you can do. You can connect to user data, site options, or even third-party APIs. Use your imagination to think up all the possibilities.
If you’d rather avoid entering block markup manually, you can wrap it in a block variation. Learn how in the Building a book review site with Block Bindings, part 1: Custom fields and block variations tutorial.
Just a little tip before moving forward. You’ll notice the Post Data placeholder in the above screenshot. This appears when the content
attribute is not editable. But in the Heading and Paragraph block markup, the placeholder
attributes are defined to read Add post title and Add post excerpt text in the Editor. Those placeholders will be reflected in the Editor once the attributes are made editable.
Getting and setting values in the Editor
Now for the part that you’re probably reading this tutorial for: manipulating binding values in the Editor with JavaScript.
Remember that start
script you defined in your package.json
earlier? Go ahead and run it by entering this command:
npm run start
This will build your JavaScript anytime you make a change to editor.js
.
Enqueue your JavaScript
Before working inside of your editor.js
file, you must first load it. Add this code to your plugin.php
file:
add_action( 'enqueue_block_editor_assets', 'devblog_editor_assets' );
function devblog_editor_assets() {
$dir = untrailingslashit( plugin_dir_path( __FILE__ ) );
$url = untrailingslashit( plugin_dir_url( __FILE__ ) );
if ( file_exists( "{$dir}/public/js/editor.asset.php" )) {
$asset = include "{$dir}/public/js/editor.asset.php";
wp_enqueue_script(
'devblog-editor-bindings',
"{$url}/public/js/editor.js",
$asset['dependencies'],
$asset['version'],
true
);
wp_set_script_translations( 'devblog-editor-bindings', 'devblog' );
}
}
To learn more about loading JavaScript, check out the Enqueueing assets in the Editor documentation.
JavaScript foundation
Now open your /resources/js/editor.js
file. You’ll work with it throughout the rest of this tutorial. In particular, you’ll use two WordPress functions:
registerBlockBindingsSource
: Necessary for registering custom binding sources in the client.__()
: Used for internationalizing text strings.
Go ahead and import these by adding this code at the top of your editor.js
file:
import { registerBlockBindingsSource } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
As you should remember from earlier, the custom binding source outputs the post title, excerpt, and permalink on the front end. The idea is that you should also support those values in the Editor.
However, let’s break these down into two groups:
- Read-only: This group will be values that appear in the Editor content but are not editable by the user (permalink).
- Editable: This group will be both viewable and editable in the Editor content (title and excerpt).
With this structure in mind, create two arrays that contain the values that are read-only and editable:
const readOnlyAttributes = [
'permalink'
];
const editableAttributes = [
'title',
'excerpt'
];
It’s very possible to make the permalink editable too, but I wanted to show that you can treat values differently depending on your use case.
Registering the binding source on the client
WordPress 6.7 introduces a new function for registering block bindings in the Editor: registerBlockBindingsSource()
. Let’s take a look at the properties you can set:
registerBlockBindingsSource({
name: '',
label: '',
usesContext: [],
getValues( { select, clientId, context, bindings } ) {
return {};
},
setValues( { select, clientId, dispatch, context, bindings } ) {
},
canUserEditValue( { select, context, args } ) {
return false;
}
});
You can define several properties for the function’s object parameter:
name
: A namespaced identifier for your block binding source (e.g.,devblog/post-data
). If registered on the server, this must match in both your PHP and JavaScript.label
: An internationalized label for the binding source. It will default to the label defined on the server if not defined in here.usesContext
: An array of contexts to pass to the callback functions. By default, this uses the context defined on the server. If also defined here, the contexts are merged.getValues
: A callback function to retrieve values when a binding is set for a block’s attributes. It must return an object containing the attribute name as the key and the bound data as the value.setValues
: A callback function that lets you manipulate data as the user edits. This is only necessary when making data editable.canUserEditValue
: A callback to determine whether the user can edit the bound block attribute. It must return a boolean value if defined and defaults tofalse
.
In most cases, you should not need to define the label
and usesContext
arguments on the client. The only time you’d do so is to change what’s already registered on the server.
As you study registerBlockBindingsSource
, keep in mind how the structure looks at the block markup level, particularly the bindings
object:
<!-- wp:heading {
"placeholder":"Add post title",
"metadata":{
"bindings":{
"content":{
"source":"devblog/post-data",
"args":{"key":"title"}
}
}
}
} -->
<h2 class="wp-block-heading"></h2>
<!-- /wp:heading -->
The properties defined in the markup will match the properties on the JavaScript side.
Now add the most basic values you need to register your block binding source to the client, which is the name and context:
registerBlockBindingsSource({
name: 'devblog/post-data',
usesContext: [ 'postType' ],
// Add additional code here in the next steps.
});
There are a couple of things to make note of:
- The
name
value matches thedevblog/post-data
value that you set in PHP and thesource
in the block markup. This is an absolute must. - The
usesContext
method addspostType
, which does not match what was set in the PHP function. That’s OK. The values of both the JavaScript and PHP arrays are merged, so you have access tocontext.postId
andcontext.postType
in your JavaScript if needed. You’ll use this to limit to a specific post type later.
Showing values in the Editor
The most basic thing you’d want to do for nearly any custom binding source is to show that data in the Editor. If you recall from the screenshot earlier, there was a very unhelpful bit of text (Add post data) that was simply a placeholder for adding data.
Let’s change that so that users see real data in the editor, which will first look something like this when no data is defined (notice that the custom placeholders appear):
For this particular example, you need two of the available arguments passed into the getValues()
callback:
select
: You’ll use this to access thecore/editor
store’sgetEditedPostAttribute()
selector. This will let you retrieve specific post data.bindings
: An object that contains all the attribute names and arguments set for a block using your custom source.
Inside your registerBlockBindingsSource()
function, add the getValues()
callback:
getValues( { select, bindings } ) {
const values = {};
for ( const [ attributeName, source ] of Object.entries( bindings ) ) {
if (
editableAttributes.includes( source.args.key )
|| readOnlyAttributes.includes( source.args.key )
) {
values[ attributeName ] = select( 'core/editor' ).getEditedPostAttribute( source.args.key );
}
}
return values;
},
getValues()
is triggered in the Editor whenever WordPress finds a block attribute that is connected to the custom binding source. This gives you a chance to manipulate the value.
The code above loops through each of the connected attributes for a block and retrieves the post data that it’s tied to. The most important thing is that the returned values
object contain the attribute names as keys and the data as the value, such as:
{
attributeName: "some-value",
anotherAttribute: "another-value"
}
This way, if you add a title, excerpt, or permalink in the post-editing interface, those values will automatically appear in the connected blocks:
At this point, all you’re doing is ensuring the values appear in the content area. The user cannot directly edit the blocks yet.
It is not necessary to make values editable for every block binding source. There are plenty of legitimate scenarios to make them read-only, such as static data or when pulling from a third-party API.
Making connected blocks editable
There are times when you’ll want to let the user edit the data from the block instance. And you’ll use the setValues()
callback to do so.
For your post data source, you’ll need to access the bindings
argument passed into the setValues()
callback as well as the dispatch()
function, which you’ll use to dispatch the editPost()
action from the core/editor
store. Essentially, what you’ll do is catch any values set by the user and update the post with the new data.
Add this code to your registerBlockBindingsSource()
function call:
setValues( { dispatch, bindings } ) {
const values = {};
for ( const [ attributeName, source ] of Object.entries( bindings ) ) {
values[ source.args.key ] = source.newValue;
}
if ( Object.keys( values ).length > 0 ) {
dispatch( 'core/editor' ).editPost( values );
}
},
Note that source.newValue
is provided by the API, which is passed along whenever a user adds a new value in the Editor.
There’s one last thing to make this work. You need to tell WordPress whether a specific value can be edited. In this specific case, the user is editing post data, so you already know they can edit it under both of these conditions:
- The post type is
post
. - The key is stored in the
editableAttributes
array you defined earlier.
Add this code inside your registerBlockBindingsSource()
function:
canUserEditValue( { context, args } ) {
return 'post' === context.postType && editableAttributes.includes( args.key );
}
Once you’ve saved your code, and refreshed your Editor screen, you should now be able to fully edit both the Heading block and the Paragraph blocks, which are connected to the post title and excerpt:
Barely scratching the surface
I’m fully aware that this tutorial doesn’t showcase the most advanced use case possible. It uses data that is easily retrievable from the post editor. This was intentional. The goal was to show how the Editor API works without any additional complexity.
So, if I may suggest, here’s a little homework: open a new project and start exploring.
I’d love to see what you build around user data, term metadata, site options, or even integrating with a third-party API. The foundation is there. You just have to build new and interesting solutions on top of it.
Props to @cbravobernal, @gziolo, @artemiosans, @santosguillamot, @ndiego, and @bph for feedback and review on this post.
Leave a Reply