WordPress 7.0 introduces PHP-only block registration—a new way to register blocks using PHP as the source of truth for block metadata, without requiring a block.json file. If you’re building dynamic blocks (or PHP-first plugins), this is a nice workflow upgrade: you can keep registration, metadata, assets, and rendering together. block.json is still a solid default, but it can feel like extra overhead when the block is server-rendered and the bulk of the work already happens in PHP. PHP-only registration in 7.0 fills in that gap by letting you define the block’s metadata directly in the register_block_type() call.
WordPress 7.0 is scheduled for release on April 9 during WordCamp Asia. Until it’s released, this feature is only enabled by installing the Gutenberg plugin or by using the Beta Tester plugin to install the WordPress Beta or RC.
How it works
The flow is the same shape you’re used to:
- Hook into
init. - Register scripts/styles (optional).
- Call
register_block_type()with a full metadata array. - Provide a
render_callbackfor dynamic output.
Enabling PHP-only registration with autoRegister
To opt into PHP-only block registration, set autoRegister under supports:
'supports' => array(
'autoRegister' => true,
),PHPThat flag tells WordPress to register the block using the metadata you provide in PHP.
Basic registration
Start by registering on init:
add_action(
'init',
__NAMESPACE__ . '\register_blocks'
);PHPThen register the block. For PHP-only registration, make sure supports.autoRegister is set:
function register_blocks() {
register_block_type(
'twitch/php-only-block',
array(
'title' => 'My PHP Only block',
'icon' => 'smiley',
'category' => 'widgets',
'description' => 'A block registered entirely in PHP',
'keywords' => array( 'php' ),
'supports' => array(
'autoRegister' => true,
),
'render_callback' => __NAMESPACE__ . '\render_sample_block',
)
);
}PHPHere’s a simple render callback. The important piece is get_block_wrapper_attributes()—it ensures wrapper-based features (supports, custom class names, etc.) are applied consistently:
function render_sample_block( $attributes ) {
$title = $attributes['title'];
ob_start();
?>
<div <?php echo wp_kses_data( get_block_wrapper_attributes( array( 'class' => 'php-only-block' ) ) ); ?>>
<h2 class="php-only-block-title"><?php echo esc_html( $title ); ?></h2>
</div>
<?php
return ob_get_clean();
}PHPAttributes and supports
Once you’re defining the block in PHP, it’s natural to keep the rest of the definition there too—attributes and supports included.
Attributes
From your reference snippet:
'attributes' => array(
'title' => array(
'type' => 'string',
'default' => 'Hello World',
),
'count' => array(
'type' => 'integer',
'default' => 5,
),
'enabled' => array(
'type' => 'boolean',
'default' => true,
),
'size' => array(
'type' => 'string',
'enum' => array( 'small', 'medium', 'large' ),
'default' => 'medium',
),
),PHPWhat’s really cool about this is that once we register the attributes, they automagincally get Inspector Controls rendered to manage them.

And in your render callback you can read them:
$title = $attributes['title'];
$count = $attributes['count'];
$enabled = $attributes['enabled'];
$size = $attributes['size'];PHPSupports
Supports work the same way they do elsewhere, including opting into built-in design tools:
'supports' => array(
'autoRegister' => true,
'color' => array(
'background' => true,
),
'spacing' => array(
'margin' => true,
'padding' => true,
'blockGap' => true,
),
),PHPIf you enable wrapper-based supports like spacing and color, make sure your render callback outputs wrapper attributes. Otherwise you’ll have controls in the editor that don’t show up on the front end.
5. Adding scripts and styles
Register assets normally, then attach them to the block using handles.
Register assets
$css_file = plugin_dir_path( __FILE__ ) . '/assets/test.css';
wp_register_style(
'php-only-blocks-style',
plugin_dir_url( __FILE__ ) . 'assets/test.css',
array(),
filemtime( $css_file )
);
wp_register_script(
'php-only-blocks-script',
plugin_dir_url( __FILE__ ) . 'assets/index.js',
array(),
filemtime( plugin_dir_path( __FILE__ ) . '/assets/index.js' ),
true
);
$css_editor_file = plugin_dir_path( __FILE__ ) . '/assets/editor.css';
wp_register_style(
'php-only-blocks-editor-style',
plugin_dir_url( __FILE__ ) . 'assets/editor.css',
array(),
filemtime( $css_editor_file )
);PHPAttach handles to the block
'script_handles' => array( 'php-only-blocks-script' ),
'style_handles' => array( 'php-only-blocks-style' ),
'editor_style_handles' => array( 'php-only-blocks-editor-style' ),PHPThis keeps assets block-scoped, which is usually what you want.
Full Code
Click to see the full code example
// Register the block on the init action.
add_action(
'init',
__NAMESPACE__ . '\register_blocks'
);
/**
* Renders the sample block.
*/
function register_blocks() {
$css_file = plugin_dir_path( __FILE__ ) . 'assets/test.css';
wp_register_style(
'php-only-blocks-style',
plugin_dir_url( __FILE__ ) . 'assets/test.css',
array(),
filemtime( $css_file )
);
wp_register_script(
'php-only-blocks-script',
plugin_dir_url( __FILE__ ) . 'assets/index.js',
array(),
filemtime( plugin_dir_path( __FILE__ ) . '/assets/index.js' ),
true
);
$css_editor_file = plugin_dir_path( __FILE__ ) . 'assets/editor.css';
wp_register_style(
'php-only-blocks-editor-style',
plugin_dir_url( __FILE__ ) . 'assets/editor.css',
array(),
filemtime( $css_editor_file )
);
register_block_type(
'twitch/php-only-block',
array(
'title' => 'My PHP Only block',
'icon' => 'smiley',
'category' => 'widgets',
'description' => 'A block registered entirely in PHP',
'keywords' => array( 'php' ),
'attributes' => array(
'title' => array(
'type' => 'string',
'default' => 'Hello World',
),
'count' => array(
'type' => 'integer',
'default' => 5,
),
'width' => array(
'type' => 'number',
'default' => 200,
),
'enabled' => array(
'type' => 'boolean',
'default' => true,
),
'size' => array(
'type' => 'string',
'enum' => array( 'small', 'medium', 'large' ),
'default' => 'medium',
),
),
'example' => array(),
'render_callback' => __NAMESPACE__ . '\render_sample_block',
'supports' => array(
'autoRegister' => true,
'color' => array(
'background' => true,
),
'spacing' => array(
'margin' => true,
'padding' => true,
'blockGap' => true,
),
),
'script_handles' => array( 'php-only-blocks-script' ),
'style_handles' => array( 'php-only-blocks-style' ),
'editor_style_handles' => array( 'php-only-blocks-editor-style' ),
)
);
}
/**
* Renders the sample block.
*/
function render_sample_block( $attributes ) {
$title = $attributes['title'];
$count = $attributes['count'];
$enabled = $attributes['enabled'];
$width = $attributes['width'];
$size = $attributes['size'];
ob_start();
?>
<div <?php echo wp_kses_data( get_block_wrapper_attributes( array( 'class' => 'php-only-block' ) ) ); ?>>
<h2 class="php-only-block-title"><?php echo esc_html( $title ); ?></h2>
<p>Count: <?php echo esc_html( $count ); ?></p>
<p>Enabled: <?php echo $enabled ? 'Yes' : 'No'; ?></p>
<p>Width: <?php echo esc_html( $width ); ?></p>
</div>
<?php
return ob_get_clean();
}PHPGotchas
- Don’t forget
supports.autoRegister
If you’re aiming for PHP-only registration, this is the required opt-in. - Inspector controls are generated automatically, but only for certain attribute types
Wherever possible, WordPress will generate controls in the Inspector sidebar for block attributes. The currently supported attribute types are:string,number,integer,boolean, andenum.
Controls won’t be generated for attributes with thelocalrole, or for attribute types outside that list. - Use
get_block_wrapper_attributes()
Especially with spacing/color supports. It’s the difference between “controls exist” and “controls work.” - Register asset handles before you reference them
If you attach handles inregister_block_type(), make sure they’ve already been registered.

Leave a Reply