A block in a pattern does not have to show the words it starts with. It can use a value from whoever places the pattern, or from the post the pattern lands in — a custom field, a post date, anything a plugin publishes. WordPress calls this a block binding, and Pattern Builder’s Pattern Bindings panel is where a pattern’s bindings are set.
The panel sits in the sidebar when you edit a pattern. It lists every block that can be bound, one card each, with a row for every attribute that block allows — the content of a paragraph, the URL and alt text of an image, the text and link of a button. Each row says where that value comes from, and clicking it offers the alternatives.
Fields from
Above the cards is a single control, Fields from, and it is worth understanding before anything else. A pattern is written before it meets a post, so there is no record to read a list of fields from. Naming a post type supplies that missing piece: it tells the panel whose fields to offer.
It is not saved to the pattern. The pattern is not tied to that post type, and the binding still resolves against whichever post the pattern is actually placed in. Fields from only decides what the menus below it contain. It starts at User input only, where no post type is chosen and a block gets the two choices a pattern can make entirely by itself.
Only post types with something to bind to are listed, so a site’s templates, navigation menus and font families stay out of a menu where they would promise fields that do not exist.
Four places a value can come from
1. Whoever places the pattern
Set a row to Overridable and the block becomes a content slot. In a synced pattern, each place the pattern is used can fill that slot with its own words while the design stays in the theme file. This is WordPress core’s Pattern Overrides, and it needs no data source at all — the value is typed by the person placing the pattern.
A block has to be named before it can be overridable, since the name is how each use of the pattern says which slot it is filling. The panel asks for that name on the card. Synced theme patterns covers slots, placeholder copy and how instances render in full.
Reach for this when the words change per placement and belong to the page, not to a record: a hero headline, a call to action, a testimonial quote.
2. A custom field on a post type
This is the cleanest route, and the one to prefer when your data fits it. Register the field as post meta and WordPress does the rest — the field shows up in Pattern Builder’s panel, in WordPress’s own Attributes panel, and resolves on the front end, with no JavaScript anywhere:
add_action( 'init', function () {
register_post_meta( 'post', 'subtitle', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'label' => __( 'Subtitle', 'my-plugin' ),
) );
} );
Two of those arguments matter more than they look. show_in_rest is what makes the key visible to the editor at all — without it the field exists but no binding UI will ever offer it. label is the name shown in the menu; leave it out and you get the raw key.
Choose that post type under Fields from and the field appears under Post Meta. It works because WordPress ships a binding source, core/post-meta, that asks the server at runtime which meta a post type has registered — so adding a field is genuinely all you do.
See register_post_meta() and Registering custom post types in the WordPress developer handbook.
3. An Advanced Custom Fields field
ACF fields work too, and need no code. Pattern Builder reads the field groups whose location rules match the post type you chose and offers their fields under Custom Fields.
One setting stands between a new field and the panel. Open the field, go to its Presentation tab, and switch on Allow Access to Value in Editor UI. ACF turns this off by default for every field created since its 6.3.6, and a field without it returns an empty string wherever it is bound — so Pattern Builder does not offer such a field rather than hand you a choice that renders nothing. ACF’s own bindings security page explains the reasoning.
ACF also decides which field types can be bound at all: its layout types — tabs, groups, accordions, messages — are excluded, and Pattern Builder honours that list rather than second-guessing it.
Expect one oddity in the editor. A block bound to an ACF field displays the words Custom Fields on the canvas instead of the value. That is WordPress falling back to the source’s name because ACF publishes no editor-side preview; the front end renders the real value. See ACF’s Block Bindings documentation.
4. A data source of your own
When the value is computed, fetched, or stored somewhere post meta cannot reach, register a binding source. This takes two registrations, and both are needed — they do different jobs, and one without the other half-works in a way that is easy to misread.
PHP makes the data work. register_block_bindings_source() gives the source a get_value_callback, and that is what produces the value when the page is rendered. Without it a binding resolves to nothing on the front end.
add_action( 'init', function () {
register_block_bindings_source( 'acme/inventory', array(
'label' => __( 'Inventory', 'acme' ),
'uses_context' => array( 'postId', 'postType' ),
'get_value_callback' => 'acme_get_inventory_value',
) );
} );
JavaScript makes the editor work. The editor keeps its own registry, and WordPress does not copy PHP registrations into it, so a source registered only in PHP is invisible to every binding interface — not merely short of fields, but absent. Registering it in the editor too, with a getFieldsList, is what puts your fields in the menu:
wp.blocks.registerBlockBindingsSource( {
name: 'acme/inventory',
label: 'Inventory',
usesContext: [ 'postId', 'postType' ],
getFieldsList( { context } ) {
return context.postType === 'product'
? [ { label: 'SKU', args: { key: 'sku' }, type: 'string' } ]
: [];
},
} );
Declare usesContext on both sides. WordPress builds the context object from that list alone, so a source that omits it is handed nothing and returns nothing. The same registration can carry getValues to show the real value on the editor canvas, and setValues to let it be edited in place. The Block Bindings API reference covers all of them.
You do not need a build step for that JavaScript. Printing it from PHP with wp_add_inline_script on the enqueue_block_editor_assets hook works, and reaches WordPress’s own Attributes panel as well as Pattern Builder’s.
Declaring fields from PHP alone
Some binding sources were written before the editor could list fields, and publish none. They resolve perfectly well, but nothing can offer them in a menu, because a value callback cannot be asked what it accepts.
Pattern Builder adds a filter for that case. Answer pattern_builder_binding_fields and the fields appear in its panel, whoever registered the source:
add_filter( 'pattern_builder_binding_fields', function ( $fields, $source, $post_type ) {
if ( 'acme/inventory' === $source && 'product' === $post_type ) {
$fields[] = array(
'label' => __( 'SKU', 'acme' ),
'args' => array( 'key' => 'sku' ),
'type' => 'string',
);
}
return $fields;
}, 10, 3 );
args is written into the block’s binding exactly as given, and type is matched against the attribute’s own type, so a string field is offered for a paragraph’s content and not for an image’s ID. This is how Pattern Builder offers ACF’s fields, and it is there for any source in the same position. For a source you own, the JavaScript registration above is the better choice: it reaches every binding interface, not only this one.
Which to reach for
- Different words each time the pattern is placed — make the block overridable. No data, no code.
- A value belonging to the post — register post meta. Five lines, no JavaScript, and it works in every editor that supports bindings.
- Fields you already manage in ACF — switch on Allow Access to Value in Editor UI and pick them from the panel.
- Anything else — register a source, in PHP for the value and in JavaScript for the editor.
Whichever you choose, the binding itself is core’s. Pattern Builder writes nothing but WordPress’s own metadata.bindings, so a pattern bound here keeps working wherever it is used, with or without this plugin installed.
