Skip to content

Block Bindings: Support innerBlocks bindings on the Group and Column blocks - #15164

Draft
cbravobernal wants to merge 10 commits into
WordPress:trunkfrom
cbravobernal:add/inner-blocks-bindings
Draft

cbravobernal wants to merge 10 commits into
WordPress:trunkfrom
cbravobernal:add/inner-blocks-bindings

Conversation

@cbravobernal

Copy link
Copy Markdown
Contributor

Trac ticket: https://core.trac.wordpress.org/ticket/66415

What?

Lets a block bindings source supply the inner blocks of a Group or Column block through metadata.bindings.innerBlocks. Backports the server part of WordPress/gutenberg#85568.

How?

  • WP_Block::process_block_bindings() handles innerBlocks itself and calls a new private replace_inner_blocks(), instead of treating it as an attribute.
  • The value is serialized block markup. It's sanitized with wp_kses_post(), as bound rich text already is, and the supplied blocks lose their custom CSS and their own innerBlocks bindings, so sources can't expand each other forever.
  • The blocks go where the block's own inner blocks were. A block without any gets them before the closer of its wrapper, found with WP_HTML_Processor.
  • __default pattern overrides bindings never expand to innerBlocks.
  • get_block_editor_settings() leaves innerBlocks out, as the editor doesn't render these bindings yet.

Testing

npm run test:php -- --group block-bindings: the new tests/phpunit/tests/block-bindings/innerBlocks.php covers the value contract, sanitizing, recursion, placement and the editor settings.

Replaces #12432, which backported an earlier design.

🤖 Generated with Claude Code

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

…n blocks.

A block bindings source can now supply the inner blocks of a Group or Column block through `metadata.bindings.innerBlocks`. The value is serialized block markup, which replaces the block's own inner blocks before they render. An empty string leaves the block without inner blocks, and any other value keeps its own.

The markup is sanitized with `wp_kses_post()`, as bound rich text already is, and its custom CSS is removed. Supplied blocks can't bind their own inner blocks, so that sources can't expand each other forever. The editor isn't offered `innerBlocks` yet, as it doesn't render these bindings.

Backports WordPress/gutenberg#85568.

Fixes #66415.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cbravobernal
cbravobernal force-pushed the add/inner-blocks-bindings branch from e561be2 to 417cff4 Compare October 29, 2026 14:13
cbravobernal and others added 5 commits October 29, 2026 16:09
… has no blocks.

The property is declared as a `WP_Block_List`, so assigning an empty array
fails PHPStan's `assign.propertyType` check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bound inner blocks render inside `do_blocks()`, after `WP_Embed` has run its
`[embed]` shortcode on the content, like the synced pattern block does for
its own content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves the explanations into docblocks, and tests that the embeds of the
value survive sanitizing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cbravobernal and others added 2 commits November 10, 2026 11:51
…g request, and let trusted sources skip kses.

Ports the latest changes of the Gutenberg PR:

- A value is block markup when the first delimiter WP_Block_Processor
  finds, after whitespace-only HTML, opens a block, which matches what
  parse_blocks() reads as a block.
- Instead of removing the innerBlocks bindings of every supplied block,
  a binding is skipped, without asking its source, only while the inner
  blocks a source supplied for the same request render: the same source,
  arguments, and the context the source uses. Sources whose values bind
  each other's, directly or through content that loads as it renders,
  still can't render forever, and nested bindings that ask something
  else resolve.
- The `block_bindings_sanitize_inner_blocks` filter lets a source whose
  markup comes from trusted code keep it as supplied. Custom CSS is
  removed either way.
- The tests no longer assert with regular expressions.

See WordPress/gutenberg#85568.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cbravobernal and others added 2 commits November 10, 2026 18:39
…value, and key requests by attributes too.

A block whose inner content held only block slots, with no wrapper
markup, crashed when its source supplied no inner blocks: the empty inner
content never reached the block instance, which rendered its old slots
against an empty list of inner blocks. The instance now gets its inner
content and HTML directly.

The request a binding is skipped for also includes the attributes of the
block, since a source may read them, like the pattern overrides source
reads the name of the block.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCeAiscBeSgrKjrPgYBr1b
…ders a Group unchanged.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCeAiscBeSgrKjrPgYBr1b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant