Visual Editor - v1.1.0
Block Bindings
Status: v1.1.0 — Issue #504.
Block bindings let any block attribute pull its value from the surrounding post, page, or CPT record at render time. Authors keep editing the block once; the binding resolver substitutes the live value on every render and the editor shows the resolved preview inline.
The system covers three first-party sources out of the box and exposes a small contract so host applications and packages can plug in their own.
Built-in sources
| Source | Identifier | What it binds to |
|---|---|---|
| Custom fields | custom_field |
A cms-framework CustomField on the record. |
| Post core | post_core |
A column on the post/page/CPT (title, excerpt, …). |
| Relation | relation |
A dotted path through a related record. |
post_core and custom_field resolve against whichever record the
current render is scoped to — typically a Post, Page, or CPT
instance bound through cms-framework. relation walks
relation.field paths so a block can bind to, for example, the
author's display name (author.name).
Binding shape
A binding is stored on the block under the standard metadata.bindings
map. Each entry maps an attribute name to a source + args pair:
{
"metadata": {
"bindings": {
"content": {
"source": "post_core",
"args": { "field": "title" }
},
"url": {
"source": "relation",
"args": { "path": "author.website" }
}
}
}
}
The resolver replaces the bound attribute value on the server, and the editor inspector surfaces a picker so authors can pick a source + field per attribute without hand-editing JSON.
Empty-value policy
When a binding resolves to null or an empty string, the renderer
honors the binding's onEmpty policy:
keep— leave the authored attribute value in place (default).hide— drop the block from the rendered output.placeholder— render the authored placeholder text.
This is the same policy the editor uses to preview a binding when the record is missing the underlying field.
Registering a custom source
A binding source is any class that implements
\ArtisanPackUI\VisualEditor\Services\Bindings\BlockBindingSource.
Register it from a service provider's boot() against
BlockBindingSourceRegistry:
use ArtisanPackUI\VisualEditor\Registries\BlockBindingSourceRegistry;
use App\Bindings\SiteSettingSource;
public function boot(): void
{
$this->app->resolving(
BlockBindingSourceRegistry::class,
function ( BlockBindingSourceRegistry $registry ): void {
$registry->register( new SiteSettingSource() );
}
);
}
The source's name() is the identifier that appears in the block's
bindings map and in the editor's source picker. Names must match
/^[a-z][a-z0-9_]*$/ so they survive JSON round-trips and JS object
keys. Re-registering a name overwrites the previous driver, which is
the intentional escape hatch for replacing a built-in source.
Editor surface
The inspector adds a Bindings panel to every supported block.
Authors pick an attribute, a source, and (depending on the source) a
field or path. The picker calls
GET /visual-editor/binding-sources for the available sources, and
POST /visual-editor/binding-resolve to preview the resolved value
against the currently-edited record.
Both endpoints are first-party controllers (BindingSourcesController
and BindingResolveController) and respect the package's standard
authorization gates.