Skip to content

PHP-only blocks: Add pattern support - #79598

Closed
priethor wants to merge 3 commits into
trunkfrom
priethor/php-only-blocks-notes
Closed

PHP-only blocks: Add pattern support#79598
priethor wants to merge 3 commits into
trunkfrom
priethor/php-only-blocks-notes

Conversation

@priethor

@priethor priethor commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What

Part of #79330.

Adds experimental support for PHP-only auto-registered blocks that provide a pattern. When the experiment is enabled, the editor uses that pattern as real, editable block content instead of treating the block only as a ServerSideRender preview.

Why

In 7.0, a PHP-only block is a server-rendered preview in the editor, and its content can only be edited through the Inspector, never in the canvas.

This PR lets that content be edited directly in the canvas, while PHP can still process the edited content at render time to wrap or transform the output. The block keeps server-side rendering but stops being a black box in the editor.

A content-only pattern already gives editable canvas content, so that part is not new. What a pattern cannot give is a registered block: a render_callback that runs over the edited content, and a name and namespace that CSS, block styles, theme.json, and filters can target. If a block only needs editable markup with no server processing, a content-only pattern is enough and this is not needed.

How

A PHP-only block provides a pattern. The editor renders it as editable inner blocks and saves the user's edits as those inner blocks; on the frontend, the optional render_callback receives them as $content.

Under the hood:

  • Sends the pattern markup, lock mode, and render-callback presence from PHP to the block library.
  • Registers eligible pattern blocks with editable inner blocks in the editor.
  • Skips the canvas-editable path when a block asks for an SSR editor preview.
  • Adds three test blocks that share the pattern and attributes but behave differently in the editor.

Examples

The PR adds a shared pattern to the server-side rendered block test plugin:

$pattern_markup = '<!-- wp:group {"layout":{"type":"constrained"}} --><div class="wp-block-group">'
	. '<!-- wp:heading --><h2 class="wp-block-heading">Title</h2><!-- /wp:heading -->'
	. '<!-- wp:paragraph --><p>Body text.</p><!-- /wp:paragraph -->'
	. '</div><!-- /wp:group -->';

The render_callback is optional for the canvas-editable blocks: without it, the saved inner blocks render natively on the frontend. The examples include it to show the combination. It receives the edited content as $content, wraps it, and adds a "Featured" ribbon and variant classes from the attributes, falling back to the pattern when there is no saved content:

$render_pattern_block = static function ( $attributes, $content ) use ( $pattern_markup ) {
	$body     = ( '' !== trim( (string) $content ) ) ? $content : do_blocks( $pattern_markup );
	$variant  = isset( $attributes['variant'] ) ? sanitize_html_class( $attributes['variant'] ) : 'default';
	$featured = ! empty( $attributes['featured'] );
	$classes  = 'pattern-block-demo is-variant-' . $variant . ( $featured ? ' is-featured' : '' );
	$wrapper  = get_block_wrapper_attributes( array( 'class' => $classes ) );
	$ribbon   = $featured ? '<p class="pattern-block-demo__ribbon"><strong>Featured</strong></p>' : '';
	return sprintf( '<div %1$s>%2$s%3$s</div>', $wrapper, $ribbon, $body );
};

The default option is a canvas-editable pattern using contentOnly locking. This keeps the structure tight, but the content-only UI hides the generated Inspector controls (see the second block in the video below):

register_block_type(
	'test/php-only-pattern-content-only',
	array(
		'attributes'      => $pattern_attributes,
		'supports'        => array(
			'autoRegister' => true,
		),
		'pattern'         => $pattern_markup,
		'render_callback' => $render_pattern_block,
	)
);

A block can keep the normal Inspector visible by setting patternLock to false. The trade-off is a softer structural lock (third block in the video below):

register_block_type(
	'test/php-only-pattern-no-content-only',
	array(
		'attributes'      => $pattern_attributes,
		'supports'        => array(
			'autoRegister' => true,
		),
		'pattern'         => $pattern_markup,
		'patternLock'     => false,
		'render_callback' => $render_pattern_block,
	)
);

A block can also opt out of canvas editing and keep a server-rendered editor preview (first block in the video). That path is useful when the controls need to update the preview live, but it means the pattern content is not editable directly in the canvas:

register_block_type(
	'test/php-only-pattern-ssr',
	array(
		'attributes'           => $pattern_attributes,
		'supports'             => array(
			'autoRegister' => true,
		),
		'pattern'              => $pattern_markup,
		'patternEditorPreview' => 'ssr',
		'render_callback'      => $render_pattern_block,
	)
);

In the two canvas-editable examples, the render callback receives the saved inner block markup as $content on the frontend. The SSR example falls back to rendering the shared pattern when there is no saved inner content.

Testing Instructions

  1. Enable the “Pattern-based PHP blocks” experiment.
  2. Open the post editor with the server-side rendered block test plugin available.
  3. Insert the PHP-only pattern block with content-only locking and confirm the pattern content appears in the canvas.
  4. Edit the heading or paragraph content and confirm the edited content is saved and shown on the frontend.
  5. Insert the PHP-only pattern block without content-only locking and confirm its generated Inspector controls remain visible.
  6. Insert the SSR preview variant and confirm control changes update the editor preview, while the pattern content is not editable directly in the canvas.

Screen recording

Grabacion.de.pantalla.2026-06-26.a.las.20.37.40.mov

I used Claude and GPT to implement the code changes while I guided and reviewed the approach.

@github-actions github-actions Bot added [Package] Block library /packages/block-library First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository labels Jun 26, 2026
@github-actions

Copy link
Copy Markdown

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @priethor! In case you missed it, we'd love to have you join us in our Slack community.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@priethor priethor added [Feature] Block API API that allows to express the block paradigm. and removed First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository labels Jun 26, 2026
@priethor
priethor force-pushed the priethor/php-only-blocks-notes branch from 8382328 to 8a37787 Compare June 26, 2026 18:09
@priethor
priethor changed the base branch from trunk to add/php-only-innercontent-prototype June 26, 2026 18:19
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

Size Change: +308 B (0%)

Total Size: 7.61 MB

📦 View Changed
Filename Size Change
build/scripts/block-library/index.min.js 325 kB +308 B (+0.09%)

compressed-size-action

@priethor
priethor changed the base branch from add/php-only-innercontent-prototype to trunk June 26, 2026 18:27
@priethor priethor added the [Type] Experimental Experimental feature or API. label Jun 26, 2026
@priethor priethor closed this Jun 26, 2026
@priethor
priethor deleted the priethor/php-only-blocks-notes branch June 26, 2026 18:32
@priethor
priethor restored the priethor/php-only-blocks-notes branch June 26, 2026 18:36
@priethor priethor reopened this Jun 26, 2026
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

Flaky tests detected in 3dd7f61.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28460170562
📝 Reported issues:

@priethor

Copy link
Copy Markdown
Contributor Author

Worth flagging a trade-off in this initial exploration, because it’ll probably come up in review: in the canvas-editable modes, the render_callback only runs on the frontend, not in the editor. So when the callback wraps or transforms the edited content, the editor and the frontend won’t match.

You can see it in the example: the editor shows a bare heading and paragraph, but the frontend wraps them in the pattern-block-demo container with the “Featured” ribbon and variant classes. None of that PHP decoration shows up while you’re editing.

The way it's currently implemented, there's a trilemma of things the block developer would want, and right now, they can only get two at once:

  • Edit the content in the canvas
  • Let PHP process it at render time (render_callback)
  • Have the editor match the frontend (WYSIWYG)

No render_callback gets you canvas editing and WYSIWYG, but nothing dynamic. Adding it gets you canvas editing and dynamic output, but the preview drifts from the real render. The SSR preview mode gets you dynamic output and WYSIWYG, but you lose canvas editing.

I think there's another way worth trying: render the PHP shell server-side in the editor as well, with the editable blocks as islands within it. That’s basically innerContent from #79115, but with a dynamic shell instead of a static one, especially now that #79115 is limited to the core/html block.

@priethor
priethor force-pushed the priethor/php-only-blocks-notes branch from 8a37787 to 1f21ed2 Compare June 29, 2026 18:53
@priethor

Copy link
Copy Markdown
Contributor Author

Instead of polluting this first iteration, I tried to leverage the new innerContent component to create "SSR islands" that allow us to match the editor and frontend in #79705.

@priethor priethor changed the title Add pattern support to auto-registered PHP blocks PHP-only blocks: Add pattern support Jun 30, 2026
@priethor
priethor marked this pull request as ready for review June 30, 2026 16:34
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: priethor <priethor@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@priethor

Copy link
Copy Markdown
Contributor Author

Instead of polluting this first iteration, I tried to leverage the new innerContent component to create "SSR islands" that allow us to match the editor and frontend in #79705.

The more I look at it, the more it looks like the best solution. Moreover, I don’t think content-only mode is the right fit here, so I’d drop it.

The way I see it, there’s really just one "mode" that matters: when the block has a render_callback, the editor renders its server output and lets you edit the content in place (SSR-islands), so you get the preview, the settings, and the layout locked while the text stays editable. That’s the whole point. 🤔

Without a render_callback there’s nothing to render, so the blocks are just the output and you edit them directly, which is basically what a plain block pattern already gives you.

@priethor

priethor commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Closed in favor of #79831

@priethor priethor closed this Jul 3, 2026
@priethor priethor mentioned this pull request Jul 3, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Block API API that allows to express the block paradigm. [Package] Block library /packages/block-library [Type] Experimental Experimental feature or API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant