WordPress
WordPress Block Bindings API for product pages in 2026
The WordPress Block Bindings API is how I now put SKU, dimensions and merchandising lines on a WooCommerce product page without a custom block for every field. I am Alan Vo, a Gold Coast web developer with 18 years on business sites and shops. In 2026 this API is no longer a 6.5 preview. Core ships sources, the editor can pick fields, and a product template can stay in paragraph, heading, button and image blocks while the values still come from the catalogue.
This note is for people who already compose a WooCommerce product page with blocks and then reach for a shortcode the first time merchandising wants lead time next to the price. It is also for anyone shipping a WordPress block theme who wants those fields to stay normal blocks.
Why the WordPress Block Bindings API matters on product pages
The WordPress Block Bindings API matters on product pages because merchandising copy is catalogue data, and catalogue data used to force a custom block or a PHP template part. A furniture PDP needs dimensions, finish and lead time in the summary column. A footwear PDP needs a last width and a size-chart link. Those values already live on the product. Pasting them into a paragraph block is how they go stale the next time stock or a supplier changes.
WordPress 6.5 introduced bindings so a supported block attribute can point at a source instead of storing the value. WordPress 6.7 added the editor JavaScript registration and the block_bindings_source_value filter. WordPress 6.9, documented in the 6.9 Field Guide, added core/post-data, core/term-data, getFieldsList() for the dropdown, and the block_bindings_supported_attributes filter. That is the stack I use in 2026. I do not wait for a Woo-specific binding UI. I register the source the store actually needs.
The other reason is editor training. Merchandisers already know how to style a paragraph. They do not want a new "Lead time block" with its own sidebar. Bind the content attribute. Keep the typography, spacing and colour tools they already have. If the same SKU line appears in a pattern used on a landing page, the pattern override source can still let one instance change the headline while the bound SKU stays honest.
I still use WooCommerce product blocks for gallery, price, add to cart and reviews. Bindings are for the extra facts those blocks do not print, or for a campaign pattern that must stay in core blocks. They are not a reason to rebuild the add-to-cart form.
How the WordPress Block Bindings API actually works
The WordPress Block Bindings API actually works as a metadata.bindings object on a block, plus a registered source that returns a value when that block renders. The block HTML still has fallback text inside the tag. The source replaces the attribute on the front end. The official Block Bindings reference is the list I trust for compatible blocks and core sources. I do not guess attribute names.
Compatible core blocks in that handbook are narrower than people expect: image (id, url, title, alt, caption), heading and paragraph (content), button (url, text, linkTarget, rel), navigation link and submenu (url), and post date (datetime). WordPress 6.9 lets you extend the list with block_bindings_supported_attributes or the per-block block_bindings_supported_attributes_{$block_type} filter. I only add an attribute I have tested on a staging PDP. Pattern overrides share that list, so a sloppy filter also changes what editors can override in a synced pattern.
Core sources I actually use: core/post-meta for registered public keys (show_in_rest true, no leading underscore), core/post-data (6.9: date, modified, link) for an updated line, core/term-data (6.9) inside a term template for real category archives, and core/pattern-overrides for campaign headlines. Underscore keys are why a naive _sku or _price binding fails on WooCommerce.
Custom sources are the production path for WooCommerce catalogue fields. Register them on init with register_block_bindings_source(). The name must be namespaced (my-theme/product-sku). The callback receives $source_args, the $block_instance, and the attribute name. I set uses_context to postId (and postType when the editor also needs it) so the callback can load the current product.
On the server I load the product with wc_get_product(), then read values through the WooCommerce CRUD getters (get_sku(), get_stock_status(), get_weight(), attribute getters) instead of raw post meta. I format money with wc_price() so tax display and currency match the rest of the shop. I never print a raw _price string from get_post_meta.
On the editor side, WordPress 6.7 added registerBlockBindingsSource from @wordpress/blocks. WordPress 6.9 added getFieldsList(), which is what puts "SKU", "Lead time" and "Board width" in the Attributes dropdown. Without that list, merchandisers cannot connect the binding in the UI and you will be pasting block markup by hand. getValues and setValues are optional. I implement getValues so the canvas shows the live SKU. I only implement setValues when the field is safe for an editor to change from a paragraph. Stock quantity is not that field.
The block_bindings_source_value filter (6.7) is for formatting, not for fetching. Prefix a SKU, localise a date, or return a fallback when the callback is empty. Keep the fetch in the callback so you can see one place that talks to WooCommerce.
Production checklist for bound product fields
A bound product field is not "it previewed in my local editor". Work through this list before it hits a catalogue.
- Confirm WordPress is 6.9 or newer if you need
core/post-data,core/term-dataor the field dropdown. Bindings exist from 6.5, but the editor story I ship assumes 6.9. - Split facts: core blocks for SKU, lead time and a size-chart URL; Woo product blocks for price, add to cart and gallery. Do not bind a second price unless merchandising asked for a campaign line you already control.
- For custom copy,
register_meta()on theproductobject type withshow_in_rest,singleand a public key. No leading underscore. - For Woo fields under protected meta, register a custom source. Do not fight
core/post-metafor_sku. - Call
register_block_bindings_source()oninit. Putuses_contextonpostId. Guard the callback whenwc_get_product()returns false. - Return escaped, formatted strings.
esc_html()for text,esc_url()for button URLs. Empty string beats a PHP warning in the HTML. - Register the editor source with
getFieldsList()so the type matches the attribute. Put fallback text in the saved HTML. "SKU pending" beats an empty<p>. - Keep bound blocks in the single product template, not only on one hand-built PDP.
- Test simple, variable and out-of-stock products. Decide whether the bound line shows the parent SKU or the variation, and document it. Bindings should not leak a draft price to a public user.
- Pair interactive controls with the WordPress Interactivity API. Bindings fill text. They do not replace click behaviour.
I keep a staging product with an empty SKU, a zero price, and a missing lead-time meta key. Binding bugs hide in the empty states.
What breaks when you bind WooCommerce fields the naive way
The first break is protected meta. WooCommerce still stores a lot of catalogue facts in underscore keys. core/post-meta will not read them. The paragraph keeps its fallback forever, and someone "fixes" it by typing the SKU into the block, which is how the binding dies and the next import looks wrong.
The second break is a custom source that calls get_post_meta( $id, '_price', true ) and prints the string. That skips tax display, sale price and currency. The real price block says one thing. Your bound line says another. Customers notice. I load wc_get_product() and use getters, then wc_price() when the value is money.
Other breaks I have had to unwind: a block that is not in the compatible list, missing postId inside a product collection, core/term-data outside a term template, double escaping that prints & in the SKU, a cache that stored a logged-in price, setValues writing stock from a paragraph, and a page-builder widget still printing the same fact. Pattern overrides and a product source should not share one content attribute. I pick one owner.
When a bound block "does nothing", I view source and look for metadata.bindings in the comments, then dump what the callback received. If postId is empty, it is context. If the product loads and the key is wrong, it is args. If the value returns and the HTML does not change, the attribute is not supported.
How to measure bound catalogue fields
Measure whether the rendered HTML matches the product object, not whether the editor canvas looked right. I open a staging PDP, view source, and compare the bound paragraph to wc_get_product()->get_sku() (or the getter you chose). Then I change the product in admin and reload the front with cache primed as a customer would see it.
I also check variable products (parent versus selected variation), draft or private products (core core/post-data already refuses non-public posts; your custom source should too), and a brand-new product after you ship the template. If the bound blocks are missing on that new SKU, you edited one page, not single-product. Bindings should not add a script. If a helper plugin enqueued a sidebar app on every PDP, isolate that change.
Do not claim a conversion lift from binding a SKU line. I will not invent one here. The honest win is one source of truth and fewer custom blocks for merchandisers to break.
Related work on this site
Bound product facts pay rent on catalogues where photography and spec copy are the product. The Skanvi furniture storefront needs dimensions next to the gallery. The Janet Janet footwear work needs last width in the summary. The BridgeWorx ticketing site treats a venue line as catalogue data, not a typed paragraph. All three were contract development on an agency team. The rule is the same: read the product object, print it in a core block, keep add to cart as a Woo block.
FAQ
What is the WordPress Block Bindings API for if I already have product blocks?
The WordPress Block Bindings API is for facts WooCommerce product blocks do not print, or for campaign patterns that must stay in paragraph, heading, button and image blocks. It does not replace the gallery, the price widget or add to cart. Use product blocks for commerce. Use bindings for the extra lines merchandising keeps asking you to "just add".
Can the WordPress Block Bindings API read WooCommerce _price and _sku?
The WordPress Block Bindings API cannot read those keys through core/post-meta, because underscore meta is protected and show_in_rest does not change that rule. Register a custom source, load the product with wc_get_product(), and use CRUD getters. That is the supported path, not a workaround.
Does the WordPress Block Bindings API work in a classic PHP theme?
The WordPress Block Bindings API works in a classic theme if the bound block actually goes through block rendering. Dropping metadata.bindings into a random PHP string does nothing. A block theme with a single product template is the cleaner production setup.
Should I bind price with the WordPress Block Bindings API or keep the Woo price block?
Keep the Woo price block for the commercial price. Bind a second price only when merchandising needs a campaign line you do not want inside that widget. Two prices that disagree is worse than one widget you style. If you do bind money, format it with wc_price() so tax and currency match.
Keep reading
Contact if you want this kind of work on a live store.