You need to construct a customized WordPress block. However you don’t need to be taught React, handle a construct pipeline, or cope with NPM packages.
Seven and half years after blocks arrived in Core, WordPress introduces a option to construct blocks with none of this stuff. All you want is PHP.
However was the lengthy wait value it?
A radically simplified block constructing expertise
A conventional WordPress block must be registered twice. As soon as in PHP, and as soon as in JavaScript.
However WordPress 7.0 introduces a brand new and streamlined strategy, permitting you to register a block utilizing solely PHP.
Registering a block utilizing solely PHP
Let’s use this characteristic to construct a Hey World Block:
operate css_tricks_hello_world_block() {
register_block_type(
'css-tricks/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', 'css_tricks_hello_world_block');
The block is absolutely practical within the block editor, and matches proper in with all the opposite blocks:

The important thing addition is the 'autoRegister' => true flag within the helps part. When set, WordPress mechanically generates the required JavaScript to your block primarily based on the PHP registration. This contains the client-side registration, and the editor preview.
Including attributes to PHP-only registered blocks
Attributes let customers customise the block’s look and conduct. In conventional block improvement you not solely must outline the attributes, but in addition construct out the corresponding controls within the editor interface.
With PHP-only registration, all that’s wanted is the attributes definition throughout block registration:
operate css_tricks_hello_world_block()
{
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes(),
esc_html($attributes['greeting'])
);
},
'helps' => [
'autoRegister' => true,
],
'attributes' => [
'greeting' => [
'type' => 'string',
'default' => 'Hello World!',
],
],
]
);
}
add_action('init', 'css_tricks_hello_world_block');
This code registers a greeting attribute as a string, with a default worth. WordPress generates the corresponding enter management within the block’s Settings sidebar.

At first sight, there’s quite a bit to love about PHP-only registered blocks. For any WordPress developer, it appears like the great previous occasions when programming was easier.
Limitations of PHP-only registered blocks
You could be tempted to delay studying JavaScript block improvement indefinitely. However the PHP-only strategy has essential limitations value understanding.
No interactions with the content material of the block
The editor shows the HTML as returned by the block’s render_callback PHP operate. When the block is first displayed or when the person interacts with considered one of its controls, the editor part requests a brand new PHP render from a REST API endpoint.
Whereas blocks rendered this manner combine seamlessly into the editor, they don’t seem to be a part of the only web page JavaScript software that powers your complete editor expertise.
This creates two key limitations:
First, you can’t add any controls throughout the block preview. This implies you’re restricted to the auto-generated controls within the Settings sidebar.
The default interplay mode with blocks is the block preview itself. Think about that you simply want a testimonial block. With a JavaScript rendered block, you’ll construct out the testimonial design, and permit modifying in place.
With a PHP-rendered block you possibly can solely use the sidebar. And even right here you’re restricted, as presently there’s no assist for picture uploads or multiline textual content.
With JavaScript, you possibly can permit modifying within the block, in addition to within the sidebar. Moreover you have got entry to all of the controls that WordPress Core makes use of, and may even implement your personal.
However with out JavaScript, you’ll at all times be restricted to the choices WordPress gives primarily based on the registered attributes of your PHP-only block.
Secondly you can’t connect any JavaScript to markup within the block preview. Think about you need to develop a block that pulls 5 associated posts, and which shows them in a slider. For that you’d output the markup, after which cross a DOM node to the JavaScript library which then transforms the uncooked markup into the specified slider interface.
This reliance on discovering and manipulating DOM components is typical for conventional JavaScript improvement. However with PHP-only registered blocks within the block editor, the markup is fetched asynchronously and changed on each re-render. This makes interacting with the DOM of the block preview unreliable or unimaginable.
Whereas the entrance finish render works wonderful with JavaScript libraries, the editor authoring expertise won’t work appropriately. Even if you happen to handle to connect any occasion listeners on first load, these can be disconnected the second the preview re-renders.
These limitations are attributable to the structure of this characteristic, and they won’t change sooner or later.
No entry to contemporary knowledge
On the preliminary load of the block editor, WordPress hundreds the put up knowledge from the database right into a client-side retailer managed by JavaScript. Any adjustments that you simply make within the editor replace this knowledge retailer on the shopper aspect. However the database isn’t up to date till you save the put up.
PHP-only registered blocks bypass this client-side retailer. When a block renders, it queries the database straight. However the database would possibly include stale knowledge in comparison with what’s presently within the editor. Even worse, the PHP-rendered block isn’t notified of adjustments within the shopper aspect knowledge, so it will possibly’t refresh when knowledge adjustments.
Let’s take a sensible instance: Think about you’re constructing a block that shows a header ingredient with the put up title. When the person adjustments the title within the editor, your block will nonetheless present the worth from the database. You would want to avoid wasting the put up and reload the editor for the modified title to point out up within the PHP-only block.
This makes PHP-only blocks unsuitable for any block that shows knowledge that the person can change within the editor like title, content material, excerpt, options pictures, or connected phrases.
No entry to the present put up context
PHP-only registered blocks render by means of a REST API endpoint. So the identical code renders the editor preview and the entrance finish. However there’s a vital distinction: the worldwide state.
On the entrance finish, blocks render inside The Loop, which units key international variables like $put up. Template tags like the_title() or the_content() depend on these globals to know which put up is displayed.
However REST APIs are stateless, and don’t depend on international state. The endpoint that renders the block editor preview accepts a put up ID parameter, however the editor part doesn’t cross it by means of. Because of this your render callback has no option to know which put up is edited.
This limits the capabilities that you need to use within the block editor preview. Template tags or capabilities like get_post_meta() must know in regards to the put up context.
It is a important architectural limitation as of WordPress 7.0. This may very well be addressed by passing the put up ID to the endpoint, however there aren’t any concrete plans to alter this on the time of this writing.
Restricted attribute sorts and modifying interfaces
WordPress 7.0 helps solely three attribute sorts: strings, numbers, and booleans. These map to 4 fundamental editor controls: textual content inputs, quantity inputs, checkboxes, and a dropdown.
This screenshot reveals a block that makes use of all out there person interface components:

The dropdown ingredient is the one superior management, but it surely has a major limitation: it doesn’t assist keyed arrays. This makes it unimaginable to have a label that differs from the saved worth.
Let’s take the instance of a associated posts block the place customers can choose a class. You need to show the class names within the dropdown, however retailer the class IDs. This isn’t attainable.
As an alternative, you will need to select between displaying names or slugs, which each are user-editable, and retailer that worth:
'attributes' => [
'selected_category' => [
'label' => 'Select a category',
'type' => 'string',
'default' => 'uncategorized',
'enum' => wp_list_pluck( get_categories( [ 'hide_empty' => false ] ), 'slug' ),
],
],
This protects the slug to the block markup:
<!-- wp:css-tricks/related-posts {"selected_category":"information"} -->
Utilizing slugs not solely doesn’t look good within the interface, however this implementation will even break when renaming a class. All current blocks referencing the previous slug will be unable to drag the associated posts. IDs are stabler, and would solely be invalid when the class is deleted.
Past dropdowns, important controls — like picture uploads, wealthy textual content editors, or date pickers — are absent. This would possibly change in future releases, however once more, there aren’t any plans for it as of but.
The killer use case: Migrating legacy PHP code
It’s simple to get discouraged these limitations. It’s true that PHP-only registered blocks are a poor selection for constructing new blocks from scratch.
However I take into account them to nonetheless be very precious as a result of there’s one use case the place these limitations don’t matter: migrating legacy PHP code into block themes. This WordPress 7.0 characteristic is an actual sport changer on the subject of builders adopting block themes, which remains to be a barrier of types for a lot of theme authors.
The block theme adoption downside
In my expertise, block themes are extra performant, simpler to take care of, and quicker to construct than legacy themes. But many builders are nonetheless counting on basic themes. And that’s not by selection, however due to current PHP-based options.
Till now, migrating these options got here up towards almost insurmountable obstacles. First, the necessity to be taught JavaScript block improvement, and arrange a completely new improvement workflow with dependency administration and construct pipelines. Second, the time wanted to rewrite all this code in JavaScript.
PHP-only registered blocks take away each these obstacles.
An actual-world migration instance
In 2022, I needed emigrate a basic theme to a block theme.

The content material space and the footer had been easy to rebuild with blocks. However the header was extra complicated, particularly with the extra restricted block constructing options of the time.
So, moderately than spending time rebuilding the header, I took the present PHP-header, and wrapped it in a server-side rendered block.

That mentioned, we must be life like. This header block was removed from good. The block preview was not responsive, dropdowns didn’t work within the editor, and you would not edit something.
Did it matter? Under no circumstances. The block rendered completely on the entrance finish, and the editor preview was ok. And due to this strategy, I might migrate the theme in hours as an alternative of days.
Earlier than PHP-only registration, constructing such blocks nonetheless required a strong JavaScript proficiency and construct tooling. However now any PHP developer can use this migration path utilizing the abilities they have already got.
What you possibly can migrate
PHP-only registered blocks are perfect for changing:
- Legacy widgets: The Settings sidebar of the block editor is ideal to breed a legacy widget’s settings.
- Shortcodes: Whereas you need to use shortcodes in block templates, working with them is awkward at greatest. Migrating shortcodes to blocks is now easy with WordPress 7.0.
- Template elements and customized template tags: Headers, footers, writer biographies, associated posts, and many others.
- Customized performance: Something that works on the entrance finish while not having any interactivity within the editor.
The blocks you create don’t must be good within the editor. What counts is that they render appropriately on the entrance finish. By utilizing current PHP code, diversifications to dam themes can be minimal.
Sensible suggestions for constructing PHP-only registered blocks
Right here are some things I’ve realized alongside the way in which as I’ve been enjoying with blocks registered with PHP.
Distinguishing between entrance finish and again finish rendering
There could be instances through which you need to have a special block rendering relying on whether or not the block is displayed within the admin, or on the entrance finish.
Utilizing the is_admin() operate for this use case doesn’t work, because it doesn’t consider to true when the REST API endpoint generates the markup for the block editor preview.
However there’s one other operate that we will use: wp_is_rest_endpoint(). If it returns true, it signifies that WordPress is producing a REST API endpoint request. However this may very well be any endpoint rendering posts, so we have to be sure that we’re coping with the Block Renderer endpoint.
operate css_tricks_php_only_detecting_editor_render()
{
register_block_type(
'css-tricks/php-only-detecting-editor-render',
[
'title' => 'PHP-Only Detecting Editor Render',
'render_callback' => function () {
if ( wp_is_rest_endpoint()
&& str_contains($GLOBALS['wp']->query_vars['rest_route'] ?? '', 'v2/block-renderer/' )
) {
$frontend = false;
} else {
$frontend = true;
}
$bgcolor = $frontend ? 'inexperienced' : 'blue';
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes(['style' => "color: #fff; background-color: $bgcolor;"] ),
$frontend ? 'Rendered on the frontend' : 'Rendered within the editor'
);
},
'helps' => [
'autoRegister' => true,
]
]
);
}
add_action('init', 'css_tricks_php_only_detecting_editor_render');
This reveals completely different textual content and styling relying on whether or not the block is rendered within the editor or on the entrance finish:


Accessing the present put up ID
We’ve seen that WordPress out of the field doesn’t provide you with entry to the ID of the edited put up in PHP-only registered blocks. There’s a workaround although.
WordPress registers blocks within the init hook. This hook additionally runs on each admin web page. Once you edit a put up, the ID of the edited put up is handed as a GET argument within the URL, for instance: https://css-tricks.com/wp-admin/put up.php?put up=5&motion=edit
Because of this for the time being of the block registration, we will retrieve this ID. To cross it to the block, we use an attribute. However we don’t want WordPress so as to add an interface ingredient, so we set the supply of the attribute to native.
operate css_tricks_php_only_post_title_block()
{
register_block_type(
'css-tricks/php-only-post-title',
[
'title' => 'PHP-Only Post Title',
'render_callback' => function ($attributes) {
$post_id = is_int(get_the_ID()) ? get_the_ID() : $attributes['postId'];
if ($post_id === 0) {
return sprintf(
'<div %s>Please save the put up and reload the web page.</div>',
get_block_wrapper_attributes()
);
}
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes(),
get_the_title($post_id)
);
},
'helps' => [
'autoRegister' => true,
],
'attributes' => [
'postId' => [
'type' => 'integer',
'default'=> isset($_GET['post']) ? absint($_GET['post']) : 0,
'function' => 'native'
],
]
]
);
}
add_action('init', 'css_tricks_php_only_post_title_block')
This solely works when modifying an current put up. When a brand new put up is created, there isn’t any put up ID handed by means of the URL. WordPress will create one when the put up is first saved, and replace the URL.
However that is carried out by means of JavaScript with out triggering a brand new web page load from the server. That means that the PHP gained’t have a possibility to entry the put up ID till a full web page reload is completed.
So, yeah, not the best strategy. But it surely’s ok to unblock you till WordPress Core provides a correct implementation to cross put up knowledge to PHP-only registered blocks.
Utilizing placeholders
There are conditions through which it’s troublesome to realize an honest preview within the editor. In sure conditions, it’s even unimaginable.
Assume, for instance, of a publication type offered as a snippet of HTML and JavaScript. Because of the limitations we’ve seen, the editor preview will at all times look damaged.
In a scenario like this, you possibly can implement a placeholder within the editor. It is a technique that WordPress Core makes use of as nicely, as we will see for the Publish Content material block:

Customers don’t count on an actual preview in each case. Select one of the best compromise between the time wanted to realize a correct block editor preview and the anticipated UX acquire.
Including CSS stylesheets
You need to use WordPress optimized stylesheet enqueuing, which solely enqueues stylesheets on the entrance finish for the blocks current on that particular web page.
The register_block_type operate provides two arguments:
type: Enqueue each within the editor, and on the entrance finish.editor_style: Enqueue solely within the blocker editor (after the type stylesheets). This lets you implement overrides for front-end kinds within the editor.
So as to add a CSS stylesheet, register it utilizing wp_register_style() Then use the deal with throughout block registration:
operate css_tricks_hello_world_block()
{
wp_register_style(
'css-tricks-hello-world',
plugins_url( 'type.css', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'type.css' )
);
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
'type' => 'css-tricks-hello-world',
]
);
}
add_action('init', 'css_tricks_hello_world_block');
Styling blocks
WordPress auto-generates a .wp-block-{namespace}-{block-name} class and provides it to the wrapper container of your block as a part of get_block_wrapper_attributes().
If it is advisable to add further lessons or kinds, you possibly can cross these to get_block_wrapper_attributes() within the render callback operate.
$wrapper_attributes = get_block_wrapper_attributes(
[
'class' => 'custom-class',
'style' => 'color: #333',
]
);
It’s one of the best apply to make use of this class because the frequent root class for writing focused kinds. I desire to make use of the Block, Ingredient, Modifier (BEM) strategy for writing block kinds. It prevents my kinds from clashing with kinds offered by WordPress Core or different code.
A standard situation is that you should have current CSS, and restructuring this code and the markup utilizing BEM could be an excessive amount of work. In that case I like to recommend utilizing a novel prefix for these legacy lessons.
If you’re coping with a web site that makes use of a front-end framework like Bootstrap, keep away from enqueuing any framework stylesheets. You want to solely migrate the CSS directions that the block wants, making use of distinctive prefixes as described above.
Use the iframed editor, if attainable
There are two methods for WordPress to combine the put up editor into the admin:
- Embedded into the present admin web page
- Built-in by means of an iframe
WordPress began with the primary strategy however rapidly realized that it made styling the block editor very troublesome. With out an iframe, any admin kinds can intervene with that kinds of the block editor, together with your customized blocks.
In apply, which means your blocks can look completely different within the editor than they do on the entrance finish. For simplicity you need to use the identical kinds throughout each the entrance finish and the editor preview with minimal adjustment. And the iframed put up editor means that you can try this.
As of WordPress 7.0, the put up editor is iframed if all blocks within the put up are Model 3 or increased. WordPress 7.1 will implement the iframe strategy independently of the blocks.
So, to simplify constructing blocks and put together for the subsequent launch, I believe it’s greatest to make sure that all blocks in your websites use the Block API Model 3.
Including JavaScript
JavaScript assist for PHP-only registered blocks is restricted to the entrance finish. So as to add a script, you possibly can register it, after which cross the deal with to the view_script throughout registration:
operate css_tricks_hello_world_block()
{
wp_register_script(
'css-tricks-hello-world',
plugins_url( 'script.js', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'script.js' )
);
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function () {
return sprintf(
'<div %s>Hello World!</div>',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
'view_script' => 'css-tricks-hello-world',
]
);
}
add_action('init', 'css_tricks_hello_world_block');
WordPress will solely enqueue this script when the block is current on the present web page.
Including customization choices
PHP-only registered blocks can use the Block Helps API, which permits opt-in to core options. Relying on the characteristic, the block editor will expose further interface components to the person. It can additionally add attributes to the block to retailer the person’s decisions.
There are options that can work independently of the theme. Others must be enabled by the theme by means of its theme.json file.
Right here is an instance enabling colour customization for the textual content and background colour:
operate css_tricks_hello_world_block()
{
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'<div %s>%s</div>',
get_block_wrapper_attributes(),
esc_html($attributes['greeting'])
);
},
'helps' => [
'autoRegister' => true,
'color' => [
'background' => true,
'text' => true,
],
],
'attributes' => [
'greeting' => [
'type' => 'string',
'default' => 'Hello World!',
],
],
]
);
}
add_action('init', 'css_tricks_hello_world_block');
WordPress will care for the outputting the corresponding CSS lessons and inline kinds utilizing get_block_wrapper_attributes().
Helpful block helps choices
Permitting customers to customise the looks is an efficient demonstration, however not one thing that you’re prone to usually use. So, let’s take a look at three helpful choices for PHP-only registered blocks.
Hiding a block from the inserter
All register blocks seem within the inserter by default. However this doesn’t make sense for each block. Think about, for instance, that you simply use a block emigrate a legacy PHP characteristic solely utilized in a single template.
On this situation, you would set inserter to false to cover the block from the inserter. Hidden blocks keep absolutely practical.
'helps' => [
'autoRegister' => true,
'inserter' => false, // Hide from inserter
],
Solely permitting a single block occasion per put up
Setting a number of to false permits the block to solely be inserted as soon as into every put up. An instance is the core Extra block.
'helps' => [
'autoRegister' => true,
'multiple' => false, // ← How to limit to single instance
],
As soon as a non-multiple block is inserted, the block’s icon is disabled within the inserter to stop inserting a second occasion.
Enabling alignment choices
Setting align to true permits all out there alignment choices:
'helps' => [
'autoRegister' => true,
'align' => true, // All alignments
],
The textual content alignments like left, heart, and proper are at all times out there. Broad and full-width alignment are solely enabled if the theme helps it.

WordPress handles outputting the mandatory lessons for the block’s design to mirror the specified alignment.
If you wish to selectively allow alignments, you possibly can specify them. The out there choices are left, heart, proper, vast, and full.
'helps' => [
'autoRegister' => true,
'align' => ['left', 'center', 'right'], // Selective alignments
],
With the following pointers you must have the ability to take advantage of out of PHP-only registered blocks, even with the restrictions in WordPress 7.0.
Wrapping up
Bear in mind the opening query: Was it value ready seven-and-a-half years for this?
For constructing new, feature-rich blocks, the reply is not any. You want JavaScript to ship the sorts of interactive and native-feeling modifying experiences that WordPress customers count on. PHP-only block registration gained’t change JavaScript-powered blocks, and nor ought to it.
As a result of it’s not what this characteristic is for.
PHP-only registered blocks are the answer for hundreds of WordPress websites caught with basic themes due to the excessive studying curve and excessive value of rebuilding with JavaScript.
Now you can take shortcodes, widgets, and template elements and port them to the block editor with the PHP expertise you have already got. No JavaScript. No construct pipeline. No code duplication.
And these blocks that you simply construct don’t must be good. So long as you possibly can insert them into block content material, and so they render appropriately on the entrance finish, that’s all that’s wanted.
That’s the killer use case for this characteristic. And for that, the wait was value it.
However past this characteristic, PHP-only registered blocks sign an essential shift: WordPress Core is lastly prioritizing developer expertise. At the same time as somebody who builds JavaScript-powered blocks commonly, I’ll admit that the method entails an excessive amount of boilerplate code, and an excessive amount of coordination between block.json, the PHP code, and the JavaScript. Which isn’t to say that point spent organising and sustaining the construct pipeline.
Something that we will do to make this course of simpler, or keep away from it completely, is greater than welcome.
So you probably have tasks with legacy PHP code stopping a migration to a block theme, then WordPress 7.0 has eliminated your greatest impediment.
Migrate these legacy options to blocks and unlock every thing trendy WordPress has to supply.

