WordPress Block Bindings API: Complete Developer Guide

The WordPress Block Bindings API turns core blocks into a controlled presentation layer for dynamic data, but only when the data contract, permissions, editor behaviour, and rendering path are designed correctly.

Introduced in WordPress 6.5, the API connects supported block attributes to registered data sources through metadata.bindings. It can display post meta inside a Paragraph block, use post or term data inside templates, allow per-instance overrides in synced patterns, or expose plugin-defined data without creating a new block for every field.

This guide covers the current API, including the built-in sources, supported attributes, a working post-meta implementation, Query Loop usage, custom PHP and JavaScript sources, security controls, performance risks, and the production decisions that matter on enterprise WordPress platforms.

Version baseline: Block Bindings requires WordPress 6.5 or newer. This article includes additions documented through WordPress 6.9, including core/post-data, core/term-data, editor field lists, and filters for extending supported attributes.

What Is the WordPress Block Bindings API?

The WordPress Block Bindings API provides a declarative connection between a block attribute and a data source. The block continues to control markup and presentation, while the binding source provides the value rendered into a supported attribute.

The connection is stored on the block instance inside metadata.bindings. This is different from declaring an attribute in block.json. A binding changes where an existing supported attribute gets its value. It does not register a new block attribute.

<!-- wp:paragraph {
	"metadata": {
		"bindings": {
			"content": {
				"source": "core/post-meta",
				"args": {
					"key": "job_title"
				}
			}
		}
	}
} -->
<p>Job title unavailable</p>
<!-- /wp:paragraph -->

In this example, the Paragraph block’s content attribute is bound to the job_title post meta key. WordPress resolves the value for the current post and uses it when rendering the block.

Block Bindings is useful because it lets teams reuse core blocks instead of creating narrowly scoped dynamic blocks. It works especially well with block themes, templates, patterns, and Query Loops. For client-side behaviour such as reactive interactions, state, or event handling, use the WordPress Interactivity API instead. Bindings solve data-to-attribute mapping, not browser-side application state.

Supported Blocks and Attributes

Block Bindings does not support every attribute on every block by default. The current WordPress handbook lists the following core combinations:

BlockSupported attributes
core/imageid, url, title, alt, caption
core/headingcontent
core/paragraphcontent
core/buttonurl, text, linkTarget, rel
core/navigation-linkurl
core/navigation-submenuurl
core/post-datedatetime

Do not assume that an output-related attribute is bindable because it exists in a block schema. Unsupported attributes may be ignored or behave inconsistently. WordPress 6.9 added filters for extending the supported list, which is covered later in this guide.

Core Block Binding Sources

WordPress currently provides four built-in sources. They cover the most common content modelling and pattern use cases without custom registration.

core/post-meta

core/post-meta binds a supported block attribute to registered post metadata. The meta key must be registered with show_in_rest enabled, and protected keys beginning with an underscore cannot be used.

For a custom post type, make sure it supports custom-fields. The registered meta type should also match the bound attribute type. A Paragraph block expects a string, while an Image block ID expects an integer.

core/post-data

Available since WordPress 6.9, core/post-data exposes selected post fields without custom meta. The documented fields are date, modified, and link. This is useful in templates where the value already belongs to the post object and should not be duplicated in metadata.

core/term-data

Also added in WordPress 6.9, core/term-data exposes term fields such as ID, name, link, slug, description, parent, and count. It requires term context, normally provided by the Terms Query and Term Template blocks or by a custom block that provides termId and taxonomy.

core/pattern-overrides

core/pattern-overrides lets selected attributes inside a synced pattern be overridden for each pattern instance while the pattern structure remains synchronized. It is the correct choice when editors need controlled variation without detaching the pattern.

This model aligns well with enterprise design systems. A central team can govern layout, spacing, and component structure while local editors change approved content fields. Combine it with a documented block styles strategy to keep presentation separate from data.

Bind a Paragraph Block to Registered Post Meta

Consider an event platform with a speaker custom post type. Each speaker has a job_title field that must appear in single templates and speaker listing cards.

The first step is registering the meta key. Storing a value with update_post_meta() is not enough. Registration defines the schema WordPress needs for the REST API and editor.

<?php
/**
 * Register speaker metadata used by block bindings.
 */
function mg_register_speaker_meta(): void {
	register_post_meta(
		'speaker',
		'job_title',
		array(
			'type'              => 'string',
			'single'            => true,
			'default'           => '',
			'show_in_rest'      => true,
			'sanitize_callback' => 'sanitize_text_field',
		)
	);
}
add_action( 'init', 'mg_register_speaker_meta' );

The speaker post type must have show_in_rest enabled and include custom-fields support. If the meta value is editable, WordPress still applies REST and post-editing capability checks. For sensitive workflows, define an explicit auth_callback and test it for every role that can edit the post type.

Next, add the binding to a Paragraph block in a template, pattern, or post:

<!-- wp:paragraph {
	"metadata": {
		"bindings": {
			"content": {
				"source": "core/post-meta",
				"args": {
					"key": "job_title"
				}
			}
		}
	},
	"className": "speaker-job-title"
} -->
<p class="speaker-job-title">Job title unavailable</p>
<!-- /wp:paragraph -->

The same block can be placed inside a Query Loop’s Post Template. WordPress passes the current post context to each iteration, so every card resolves the job_title belonging to that speaker.

This is a cleaner architecture than duplicating the same field-rendering logic across PHP templates, shortcodes, and custom blocks. It also makes the layout editable through the Site Editor, which is one of the practical differences discussed in Full Site Editing vs classic themes.

Register a Custom Block Binding Source in PHP

Use a custom source when the value comes from a custom table, an option, a service layer, or requires transformation that does not belong in the block. Server registration is responsible for frontend rendering.

For a simple field, prefer core/post-meta. The following example intentionally wraps the speaker job title in a custom source to demonstrate the complete server and editor registration contract. This pattern becomes useful when the source needs centralized normalization, fallbacks, authorization, or storage abstraction.

<?php
/**
 * Register a read-only speaker job title binding source.
 */
function mg_register_job_title_binding(): void {
	if ( ! function_exists( 'register_block_bindings_source' ) ) {
		return;
	}

	register_block_bindings_source(
		'mg/job-title',
		array(
			'label'              => __( 'Speaker Job Title', 'mg-block-bindings' ),
			'uses_context'       => array( 'postId' ),
			'get_value_callback' => 'mg_get_job_title_binding_value',
		)
	);
}
add_action( 'init', 'mg_register_job_title_binding' );

/**
 * Resolve the job title for the current post context.
 *
 * @param array    $source_args   Arguments stored in metadata.bindings.
 * @param WP_Block $block_instance Current block instance.
 * @param string   $attribute_name Bound attribute name.
 *
 * @return string|null
 */
function mg_get_job_title_binding_value(
	array $source_args,
	WP_Block $block_instance,
	string $attribute_name
): ?string {
	unset( $source_args, $attribute_name );

	$post_id = isset( $block_instance->context['postId'] )
		? absint( $block_instance->context['postId'] )
		: 0;

	if ( 0 === $post_id ) {
		return null;
	}

	$value = get_post_meta( $post_id, 'job_title', true );

	return is_string( $value ) && '' !== $value
		? $value
		: __( 'Position not provided', 'mg-block-bindings' );
}

Use the source in a supported block attribute:

<!-- wp:paragraph {
	"metadata": {
		"bindings": {
			"content": {
				"source": "mg/job-title"
			}
		}
	}
} -->
<p>Position not provided</p>
<!-- /wp:paragraph -->

Register sources on init, use a namespaced lowercase source name, validate every argument, and return a value compatible with the target attribute. The official register_block_bindings_source() reference documents the callback contract and accepted source properties.

Add Editor Support with registerBlockBindingsSource()

PHP registration renders the source on the frontend. JavaScript registration controls how the source behaves in the editor. Since WordPress 6.7, use registerBlockBindingsSource() from @wordpress/blocks. Do not use the obsolete registerBindingSource name or the old experimental binding properties.

import { registerBlockBindingsSource } from '@wordpress/blocks';
import { store as coreDataStore } from '@wordpress/core-data';

registerBlockBindingsSource( {
	name: 'mg/job-title',
	usesContext: [ 'postType' ],

	getValues( { select, context } ) {
		const record = select( coreDataStore ).getEditedEntityRecord(
			'postType',
			context.postType,
			context.postId
		);

		return {
			content: record?.meta?.job_title ?? '',
		};
	},

	canUserEditValue() {
		return false;
	},

	getFieldsList() {
		return [
			{
				label: 'Speaker job title',
				type: 'string',
				args: {},
			},
		];
	},
} );

getValues() returns an object keyed by block attribute. setValues() can persist editor changes, while canUserEditValue() decides whether direct editing is allowed. WordPress 6.9 added getFieldsList(), which lets custom fields appear in the Block Bindings selector.

For enterprise implementations, read-only editor bindings are often safer. Let editors update structured data through a validated sidebar, form, or workflow rather than editing the rendered block value directly. This keeps the source of truth clear and reduces accidental changes.

Extend Supported Attributes Carefully

WordPress 6.9 introduced block_bindings_supported_attributes and block-specific variants. These filters can add binding support to another attribute, including attributes on custom blocks.

<?php
/**
 * Allow the value attribute of the acme/kpi block to use bindings.
 *
 * @param string[] $supported_attributes Existing supported attributes.
 * @return string[]
 */
function mg_support_kpi_value_binding( array $supported_attributes ): array {
	$supported_attributes[] = 'value';

	return array_values( array_unique( $supported_attributes ) );
}
add_filter(
	'block_bindings_supported_attributes_acme/kpi',
	'mg_support_kpi_value_binding'
);

Adding an attribute to the supported list does not automatically make the block safe or compatible. The attribute must have a stable schema and render correctly when its value is replaced. Test serialization, editor previews, frontend output, invalid values, and backward compatibility before enabling it in a shared component library.

Enterprise Architecture, Security, and Performance

Treat bindings as a public data contract

Anything rendered into public block markup should be treated as public. Never bind API keys, internal identifiers, private customer data, unpublished operational values, or protected metadata. Enabling show_in_rest is a deliberate schema decision, not a checkbox to make an example work.

Keep source resolution deterministic

A binding callback can run many times across templates, Query Loops, navigation, and pattern instances. Avoid remote HTTP requests during every block render. Resolve local data cheaply, cache expensive results, and invalidate the cache when the source changes. A page containing 30 bound cards should not trigger 30 uncached API calls.

Separate storage, presentation, and interaction

Use registered meta, options, or a domain service as the data layer. Use Block Bindings to map values into block attributes. Use block styles and theme.json for presentation. Use the Interactivity API only when the browser needs stateful behaviour. This separation makes the system easier to test and replace.

Plan version compatibility

If a plugin supports versions older than WordPress 6.5, guard source registration with function_exists(). If it depends on WordPress 6.9 features, declare and enforce that minimum version instead of silently degrading. Enterprise teams should include the WordPress version requirement in architecture records and deployment checks.

Test the full editorial path

  • Editor preview and direct editing permissions
  • Frontend rendering for empty, invalid, and valid values
  • Query Loop and nested block context
  • Synced pattern overrides and template changes
  • REST API reads and writes for every editorial role
  • Object cache, page cache, and cache invalidation behaviour
  • Accessibility when binding image alt text, labels, and links

Run the implementation through the same code-review and CI controls used for other production code. My guide to enforcing WordPress Coding Standards in team projects covers the baseline workflow for shared engineering repositories.

When Block Bindings Is the Wrong Tool

Block Bindings is not a replacement for every dynamic block or template. Use another approach when:

  • The output requires complex conditional markup rather than a value inside one attribute.
  • The interface needs client-side state, events, optimistic updates, or live polling.
  • The data source is highly volatile and cannot be cached safely.
  • The target attribute is unsupported and extending it would create fragile serialization.
  • Editors need a purpose-built workflow with validation across multiple related fields.

A server-rendered dynamic block remains the better choice for complex markup. A dedicated editor extension is better for structured authoring. Build a custom plugin when the capability belongs to the application rather than the theme. See the custom WordPress plugin development guide for a maintainable starting structure.

Production Implementation Checklist

  1. Confirm the minimum supported WordPress version.
  2. Choose a built-in source before creating a custom one.
  3. Register meta with an explicit type, sanitization, REST exposure, and authorization policy.
  4. Bind only documented or explicitly enabled attributes.
  5. Register custom frontend sources in PHP on init.
  6. Add editor registration only for the preview, editing, or field-selector behaviour required.
  7. Allowlist source arguments and reject unknown fields.
  8. Cache expensive data outside the per-block render path.
  9. Test permissions, context, empty values, caching, and accessibility.
  10. Document the binding source and ownership for future teams.

The official Block Bindings handbook, the register_meta() reference, and the Block Context documentation should remain the source of truth as the API evolves.

Need Block Bindings Designed for a Production Platform?

I help product teams and agencies design maintainable WordPress data models, custom plugins, block systems, and enterprise editorial workflows. The goal is not merely to make dynamic content render. It is to make the implementation secure, testable, upgradeable, and understandable by the next team.

Frequently Asked Questions

Which WordPress version introduced the Block Bindings API?

WordPress 6.5 introduced the Block Bindings API. Later releases expanded editor APIs, built-in sources, field selection, and supported-attribute controls. Check each feature’s minimum version before using it in a distributed plugin or enterprise platform.

Can Block Bindings use ACF fields?

Yes, when the underlying post meta is registered in a compatible way. The key must be available through the REST API, must not be protected with a leading underscore, and must use a type compatible with the block attribute. Do not assume every ACF field is automatically eligible.

Do I need PHP to use Block Bindings?

You can use built-in sources such as core/post-meta directly in block markup, but the underlying metadata still needs proper registration. Custom sources that render on the frontend require PHP registration with register_block_bindings_source().

Can I bind any block attribute?

No. WordPress supports a defined set of blocks and attributes by default. WordPress 6.9 added filters for extending support, but the target block must still serialize, edit, and render the replacement value safely.

Can Block Bindings fetch data from an external API?

A custom server source can return external data, but making a remote request during every block render is usually a poor production design. Fetch and cache the data through a service layer, define failure behaviour, and let the binding source read the cached value.

Are Block Bindings suitable for enterprise WordPress?

Yes, particularly for governed design systems, reusable templates, controlled pattern overrides, and structured content. Enterprise suitability depends on strict source contracts, authorization, caching, version support, accessibility, and testing across editorial roles and rendering contexts.

Mehul Gohil
Mehul Gohil

Mehul Gohil is a Full Stack WordPress developer and an active member of the local WordPress community. For the last 13+ years, he has been developing custom WordPress plugins, custom WordPress themes, third-party API integrations, performance optimization, and custom WordPress websites tailored to the client's business needs and goals.

Articles: 163

Leave a Reply

Your email address will not be published. Required fields are marked *