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.

Developer writing PHP code for WordPress block registration on a code editor Software Concept Art

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' );

Server-side rendering diagram showing PHP block output in WordPress editor System Abstract Visual

Limitations You Must Know

PHP-only blocks are not a full replacement for JavaScript blocks. Here's what you lose:

CapabilityPHP-Only BlockJS-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 typesStrings, numbers, booleansAny
Editor controlsText, number, checkbox, dropdownFull 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.

Laptop screen displaying WordPress block editor with PHP-only block preview Coding Session Visual

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

This content was drafted using AI tools based on reliable sources, and has been reviewed by our editorial team before publication. It is not intended to replace professional advice.