Why PHP-Only Blocks Matter
For years, building a custom WordPress block meant learning React, setting up a build pipeline with NPM, and maintaining a block.json file alongside PHP and JavaScript. That's a steep barrier for the thousands of developers comfortable with PHP but not with modern JavaScript tooling.
WordPress 7.0 changes this with a new autoRegister flag that lets you register a fully functional block using only PHP. WordPress automatically generates the required JavaScript for the editor preview based on your PHP registration.
This isn't a replacement for JavaScript-powered blocks—it's a bridge. The real value is migrating legacy PHP code (widgets, shortcodes, template parts) into block themes without rewriting everything in React. For a deeper look at how WordPress is evolving its developer experience, see our analysis of Azure's AI datacenter infrastructure for NVIDIA's Rubin platform, which similarly prioritizes developer-facing abstractions.
Let's dive into how it works, what you can and can't do, and when to use it.

Building a PHP-Only Block: Step by Step
1. Register a Basic Block
The key is the 'autoRegister' => true flag inside supports. WordPress handles the client-side registration and editor preview automatically.
function my_hello_world_block() {
register_block_type(
'my-plugin/hello-world',
[
'title' => 'Hello World',
'render_callback' => function () {
return sprintf(
'<div %s>Hello World!</div>',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
]
);
}
add_action( 'init', 'my_hello_world_block' );
2. Add Attributes (Auto-Generated Controls)
Define attributes in PHP, and WordPress generates the corresponding input controls in the block's Settings sidebar.
function my_greeting_block() {
register_block_type(
'my-plugin/greeting',
[
'title' => 'Greeting',
'render_callback' => function ( $attributes ) {
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes(),
esc_html( $attributes['greeting'] )
);
},
'supports' => [ 'autoRegister' => true ],
'attributes' => [
'greeting' => [
'type' => 'string',
'default' => 'Hello World!',
],
],
]
);
}
add_action( 'init', 'my_greeting_block' );
3. Detect Editor vs. Frontend Rendering
Use wp_is_rest_endpoint() combined with a route check to know when the block is rendered in the editor preview.
function my_context_aware_block() {
register_block_type(
'my-plugin/context-aware',
[
'title' => 'Context Aware',
'render_callback' => function () {
$is_editor = wp_is_rest_endpoint()
&& str_contains( $GLOBALS['wp']->query_vars['rest_route'] ?? '', 'v2/block-renderer/' );
$bgcolor = $is_editor ? '#0073aa' : '#46b450';
$label = $is_editor ? 'Rendered in editor' : 'Rendered on frontend';
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes( [ 'style' => "background:$bgcolor;color:#fff;padding:1em;" ] ),
esc_html( $label )
);
},
'supports' => [ 'autoRegister' => true ],
]
);
}
add_action( 'init', 'my_context_aware_block' );
4. Enqueue Styles and Scripts
Use style for both editor and frontend, editor_style for editor-only overrides, and view_script for frontend-only JavaScript.
function my_styled_block() {
wp_register_style(
'my-plugin-block-style',
plugins_url( 'style.css', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'style.css' )
);
register_block_type(
'my-plugin/styled',
[
'title' => 'Styled Block',
'render_callback' => function () {
return sprintf(
'<div %s>Styled content</div>',
get_block_wrapper_attributes()
);
},
'supports' => [ 'autoRegister' => true ],
'style' => 'my-plugin-block-style',
]
);
}
add_action( 'init', 'my_styled_block' );

Limitations You Must Know
PHP-only blocks are not a full replacement for JavaScript blocks. Here's what you lose:
| Capability | PHP-Only Block | JS-Powered Block |
|---|---|---|
| Inline editing in block preview | ❌ No | ✅ Yes |
| Attach JS to block preview DOM | ❌ No (breaks on re-render) | ✅ Yes |
| Access to fresh client-side data | ❌ No (queries DB directly) | ✅ Yes |
| Access to current post context | ❌ No (REST is stateless) | ✅ Yes |
| Attribute types | Strings, numbers, booleans | Any |
| Editor controls | Text, number, checkbox, dropdown | Full React components |
| Image upload / rich text / date picker | ❌ Not supported | ✅ Yes |
| Dropdown with keyed arrays (label ≠ value) | ❌ No | ✅ Yes |
These limitations are architectural, not temporary. They won't change in future releases.
The Killer Use Case: Migrating Legacy PHP
The real value of PHP-only blocks is migrating classic themes to block themes. If you have legacy widgets, shortcodes, or template parts, you can wrap them in a PHP-only block and get them rendering in the block editor in hours instead of days.
The editor preview won't be perfect—it might not be responsive, controls might not work—but if the frontend renders correctly, that's often enough. For a different angle on pragmatic engineering trade-offs, check out how we trained a text-to-image model in 24 hours.
Useful Block Supports for PHP-Only Blocks
'supports' => [
'autoRegister' => true,
'inserter' => false, // Hide from block inserter
'multiple' => false, // Only one instance per post
'align' => [ 'left', 'center', 'right' ], // Selective alignment
'color' => [ 'background' => true, 'text' => true ],
],
Pro Tip: Use the Iframed Editor
As of WordPress 7.0, the post editor is iframed if all blocks are Block API Version 3 or higher. WordPress 7.1 will enforce iframing regardless. To prepare, ensure your blocks use API Version 3—this prevents admin styles from interfering with your block styles.

Conclusion: Worth the 7.5-Year Wait?
For building feature-rich, interactive blocks from scratch—no. You still need JavaScript for native-feeling editor experiences.
But for migrating legacy PHP code into block themes? Absolutely. PHP-only block registration removes the two biggest barriers: learning JavaScript block development and rewriting everything in React. You can now port shortcodes, widgets, and template parts using the PHP skills you already have.
Next steps:
- Audit your classic theme for widgets and shortcodes that could become PHP-only blocks
- Start with a low-risk template part (header, footer, author bio) and wrap it in a block
- Enable Block API Version 3 on all blocks to prepare for WordPress 7.1's iframe enforcement
- Keep JavaScript block development in your learning roadmap—PHP-only blocks complement, not replace, JS blocks
Caveat: Don't expect perfect editor previews. Accept that some blocks will look broken in the editor but render fine on the frontend. That's the trade-off.
Source: CSS-Tricks