Native block bindings (experiment)
CoverKit can bind coverkit/layer attributes to post fields via WordPress native block bindings (metadata.bindings on the block). This path runs alongside the existing per–use-case mapping UI stored in _coverkit_use_cases. The experiment proves end-to-end binding without replacing the mapping sidebar.
Requirements: WordPress 7.0+ (register_block_bindings_source(), editor bindings UI). Gated by the coverkit_block_bindings_enabled filter (default true).
Two ways to map fields
Section titled “Two ways to map fields”| Use-case mappings | Native block bindings | |
|---|---|---|
| Where configured | Template sidebar → use case → field → layer | Layer block inspector → Layer {id} SmartPanel, or use-cases sidebar → Field bindings |
| Storage | _coverkit_use_cases.mappings (per use case, per layer) | metadata.bindings on each coverkit/layer block |
| Scope | One mapping list per enabled use case; multiple layers can map the same field | Per block, per attribute (text, image, imageId) |
| Editor UI | src/editor/use-cases/MappingFields.js | src/editor/use-cases/FieldBindingSelect.js, src/shared/inspector/bindings.js (core Attributes panel is intentionally hidden) |
| Apply at render | Mapping_Applicator | Block_Bindings_Applicator |
Legacy per-layer mapping.field in the layer inspector is unchanged; bindings are an additional opt-in path.
Why CoverKit reimplements binding resolution
Section titled “Why CoverKit reimplements binding resolution”CoverKit does not render templates through WP_Block::render(). Generation parses block JSON into canvas elements in Template::parse(). WordPress resolves bindings inside private WP_Block::process_block_bindings().
Block_Bindings_Applicator mirrors that step: read element['metadata']['bindings'], call get_block_bindings_source( 'coverkit/field' )->get_value(), then write onto parsed elements via Mapping_Applicator::set_element_attribute() (including image hide-on-empty).
flowchart LR subgraph editor [Template editor] LayerBlock["coverkit/layer"] BindUI["Layer bindings SmartPanel"] OldMap["Use-case mappings sidebar"] LayerBlock --> BindUI LayerBlock --> OldMap end
subgraph storage [Saved on template CPT] MetaBindings["attrs.metadata.bindings"] MetaUseCases["_coverkit_use_cases.mappings"] end
subgraph render [Image generation] Template["Template::apply_data()"] BindApp["Block_Bindings_Applicator"] MapApp["Mapping_Applicator"] Generator["Generator"] end
BindUI --> MetaBindings OldMap --> MetaUseCases MetaBindings --> BindApp MetaUseCases --> MapApp Template --> BindApp --> MapApp --> GeneratorRender order and precedence
Section titled “Render order and precedence”In Template::apply_data():
- Placeholder tokens in layer text (
{post_title}, …) apply_block_bindings()— native bindingsapply_use_case_mappings()— sidebar mappingsprocess_elements()— typography, fit text, etc.
Precedence: When both a binding and a use-case mapping target the same layer and same attribute, the use-case mapping wins. Bindings apply first; mappings overwrite on conflict. Existing templates without bindings behave as before.
PHP components
Section titled “PHP components”| Class | File | Role |
|---|---|---|
Block_Bindings | includes/class-coverkit-block-bindings.php | Registers source coverkit/field; declares bindable attributes text, image, imageId; get_field_value() reuses Field_Resolver + active use case formatting |
Block_Bindings_Context | includes/class-coverkit-block-bindings-context.php | Static render scope (post_id, use case class, generation key) for one generation request |
Block_Bindings_Applicator | includes/class-coverkit-block-bindings-applicator.php | Applies bindings to parsed elements |
Boot: Block_Bindings::get_instance() from coverkit.php init().
Binding source registration:
register_block_bindings_source( 'coverkit/field', [ 'label' => __( 'CoverKit Field', 'coverkit' ), 'uses_context' => [ 'postId', 'postType' ], 'get_value_callback' => [ Block_Bindings::class, 'get_field_value' ],] );Field catalog for the editor: GET /wp-json/coverkit/v1/binding-fields?use_case=sandbox (see rest-api.md).
Editor components
Section titled “Editor components”src/editor/block-bindings/index.js—registerBlockBindingsSource( 'coverkit/field', … )src/editor/block-bindings/bindingFieldsStore.js— loads field list from RESTsrc/editor/block-bindings/resolveFieldValue.js— preview values (same preview post as Sandbox /useUseCasePreview)src/editor/use-cases/FieldBindingSelect.js—FieldBindingsPanel/FieldBindingsContent(SmartPanel in inspector, plain content in use-cases sidebar)src/shared/inspector/bindings.js— layer inspector panel wired intoInspectorControls
WordPress core’s native Attributes panel is hidden for coverkit/layer by returning an empty supported-attributes list from Block_Bindings::filter_supported_attributes(); server-side binding resolution is unchanged (Block_Bindings_Applicator falls back to SUPPORTED_ATTRIBUTES).
Imported from src/editor/index.js.
Disable the experiment
Section titled “Disable the experiment”add_filter( 'coverkit_block_bindings_enabled', '__return_false' );When disabled, the binding source is not registered, supported attributes are not declared, and Template::apply_block_bindings() returns early.
Automated tests
Section titled “Automated tests”tests/php/BlockBindingsApplicatorTest.php— boundtexton a parsed element resolvespost_titlefor Sandboxtests/php/UseCaseRestTest.php—GET /binding-fieldscatalog
Sandbox manual QA
Section titled “Sandbox manual QA”Use the fixture markup below on a WordPress 7.0+ dev site with the Sandbox use case enabled.
Fixture
Section titled “Fixture”Copy block markup from tests/fixtures/block-bindings-sandbox-qa.html into a new coverkit template (Code editor), or paste via Tools → Import if you maintain a reusable pattern.
Checklist
Section titled “Checklist”- Open the template in the block editor.
- Select the Bound title layer → confirm there is no core Attributes panel in the block inspector.
- Open the Layer {id} SmartPanel in the block inspector (or Field bindings in the use-cases sidebar) → bind Text to Post Title (fixture markup may already include a binding).
- Enable Sandbox, pick a preview post with a known title and featured image.
- Confirm the canvas preview and generated Sandbox image show the post title on the headline layer and the featured image on the image layer.
- Add a Sandbox use-case mapping for the same layer’s
textto a different field → confirm the mapping overrides the binding. - Optional: bind
image/imageIdon an image layer via the CoverKit bindings panel and repeat with another post.
Binding shape (reference)
Section titled “Binding shape (reference)”Stored on the layer block as metadata.bindings:
{ "text": { "source": "coverkit/field", "args": { "field": "post_title" } }}Supported attributes: text (strings), image (URL), imageId (attachment ID).
Out of scope (this phase)
Section titled “Out of scope (this phase)”- Replacing the use-case mapping sidebar with bindings-only workflow
- Pattern overrides / synced patterns
core/post-metabinding source for arbitrary meta keys
Pull request draft
Section titled “Pull request draft”Copy when opening the feature PR:
feat: experiment with native block bindings on coverkit/layer
Adds a CoverKit field binding source and render applicator so layer attributes can be bound via WordPress 7.0 native metadata.bindings, alongside the existing per-use-case mapping system.
Registers server and client binding sources, applies bindings during Template::apply_data() before use-case mappings (mappings override on conflict), and documents the experiment for Sandbox QA.