You wish to construct a customized WordPress block. However you don’t wish to study React, handle a construct pipeline, or cope with NPM packages.
Seven and half years after blocks arrived in Core, WordPress introduces a approach to construct blocks with none of this stuff. All you want is PHP.
However was the lengthy wait price it?
A radically simplified block constructing expertise
A standard 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 method, 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:
perform css_tricks_hello_world_block() {
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function () {
return sprintf(
'Hello World!
',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
]
);
}
add_action('init', 'css_tricks_hello_world_block');
The block is totally useful 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 routinely generates the required JavaScript in 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 growth you not solely must outline the attributes, but additionally construct out the corresponding controls within the editor interface.
With PHP-only registration, all that’s wanted is the attributes definition throughout block registration:
perform css_tricks_hello_world_block()
{
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'%s
',
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 may be lots to love about PHP-only registered blocks. For any WordPress developer, it looks like the great previous instances when programming was easier.
Limitations of PHP-only registered blocks
You could be tempted to delay studying JavaScript block growth indefinitely. However the PHP-only method has vital limitations price understanding.
No interactions with the content material of the block
The editor shows the HTML as returned by the block’s render_callback PHP perform. When the block is first displayed or when the consumer 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 fashion combine seamlessly into the editor, they aren’t a part of the only web page JavaScript utility that powers your entire editor expertise.
This creates two key limitations:
First, you can not add any controls inside the block preview. This implies you might be 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 just want a testimonial block. With a JavaScript rendered block, you’d construct out the testimonial design, and permit enhancing in place.
With a PHP-rendered block you’ll be able to solely use the sidebar. And even right here you might be restricted, as at the moment there’s no assist for picture uploads or multiline textual content.
With JavaScript, you’ll be able to enable enhancing within the block, in addition to within the sidebar. Moreover you’ve got entry to all of the controls that WordPress Core makes use of, and might even implement your individual.
However with out JavaScript, you’ll all the time be restricted to the choices WordPress offers primarily based on the registered attributes of your PHP-only block.
Secondly you can not connect any JavaScript to markup within the block preview. Think about you wish 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 move 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 parts is typical for conventional JavaScript growth. 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 is not going to work accurately. Even for those who handle to connect any occasion listeners on first load, these will likely be disconnected the second the preview re-renders.
These limitations are brought on by 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 publish knowledge from the database right into a client-side retailer managed by JavaScript. Any adjustments that you just make within the editor replace this knowledge retailer on the shopper facet. However the database isn’t up to date till you save the publish.
PHP-only registered blocks bypass this client-side retailer. When a block renders, it queries the database instantly. However the database may include stale knowledge in comparison with what’s at the moment within the editor. Even worse, the PHP-rendered block isn’t notified of adjustments within the shopper facet knowledge, so it might probably’t refresh when knowledge adjustments.
Let’s take a sensible instance: Think about you might be constructing a block that shows a header component with the publish title. When the consumer adjustments the title within the editor, your block will nonetheless present the worth from the database. You would wish to save lots of the publish 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 consumer can change within the editor like title, content material, excerpt, options pictures, or connected phrases.
No entry to the present publish context
PHP-only registered blocks render by a REST API endpoint. So the identical code renders the editor preview and the entrance finish. However there’s a crucial distinction: the worldwide state.
On the entrance finish, blocks render inside The Loop, which units key international variables like $publish. Template tags like the_title() or the_content() depend on these globals to know which publish is displayed.
However REST APIs are stateless, and don’t depend on international state. The endpoint that renders the block editor preview accepts a publish ID parameter, however the editor part doesn’t move it by. Which means that your render callback has no approach to know which publish 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 concerning the publish context.
It is a vital architectural limitation as of WordPress 7.0. This might be addressed by passing the publish ID to the endpoint, however there are not any concrete plans to vary this on the time of this writing.
Restricted attribute varieties and enhancing interfaces
WordPress 7.0 helps solely three attribute varieties: 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 consumer interface parts:

The dropdown component 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 wish to show the class names within the dropdown, however retailer the class IDs. This isn’t potential.
As a substitute, you 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:
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 tug 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 may change in future releases, however once more, there are not any plans for it as of but.
The killer use case: Migrating legacy PHP code
It’s simple to get discouraged taking a look at these limitations. It’s true that PHP-only registered blocks are a poor alternative for constructing new blocks from scratch.
However I take into account them to nonetheless be very worthwhile as a result of there may be 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 with regards to builders adopting block themes, which continues to be a barrier of types for a lot of theme authors.
The block theme adoption drawback
In my expertise, block themes are extra performant, simpler to take care of, and sooner to construct than legacy themes. But many builders are nonetheless counting on traditional themes. And that’s not by alternative, however due to current PHP-based options.
Till now, migrating these options got here up in opposition to practically insurmountable obstacles. First, the necessity to study JavaScript block growth, and arrange a wholly new growth 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 wished emigrate a traditional 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, reasonably than spending time rebuilding the header, I took the present PHP-header, and wrapped it in a server-side rendered block.

That mentioned, we should be reasonable. This header block was removed from good. The block preview was not responsive, dropdowns didn’t work within the editor, and you can 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 method, I might migrate the theme in hours as an alternative of days.
Earlier than PHP-only registration, constructing such blocks nonetheless required a stable JavaScript proficiency and construct tooling. However now any PHP developer can use this migration path utilizing the abilities they have already got.
What you’ll be able to 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 components and customized template tags: Headers, footers, creator biographies, associated posts, and so on.
- Customized performance: Something that works on the entrance finish without having any interactivity within the editor.
The blocks you create don’t should be good within the editor. What counts is that they render accurately on the entrance finish. Through the use of current PHP code, diversifications to dam themes will likely be minimal.
Sensible ideas for constructing PHP-only registered blocks
Right here are some things I’ve discovered alongside the best way as I’ve been enjoying with blocks registered with PHP.
Distinguishing between entrance finish and again finish rendering
There could be circumstances during which you wish to have a unique block rendering relying on whether or not the block is displayed within the admin, or on the entrance finish.
Utilizing the is_admin() perform 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 may be one other perform that we are able to use: wp_is_rest_endpoint(). If it returns true, it implies that WordPress is producing a REST API endpoint request. However this might be any endpoint rendering posts, so we have to be certain that we’re coping with the Block Renderer endpoint.
perform 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(
'%s
',
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 publish ID
We’ve seen that WordPress out of the field doesn’t provide you with entry to the ID of the edited publish 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. If you edit a publish, the ID of the edited publish is handed as a GET argument within the URL, for instance: https://css-tricks.com/wp-admin/publish.php?publish=5&motion=edit
Which means that for the time being of the block registration, we are able to retrieve this ID. To move it to the block, we use an attribute. However we don’t need WordPress so as to add an interface component, so we set the supply of the attribute to native.
perform 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(
'Please save the publish and reload the web page.
',
get_block_wrapper_attributes()
);
}
return sprintf(
'%s
',
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 enhancing an current publish. When a brand new publish is created, there isn’t a publish ID handed by the URL. WordPress will create one when the publish is first saved, and replace the URL.
However that is accomplished by JavaScript with out triggering a brand new web page load from the server. That means that the PHP received’t have a chance to entry the publish ID till a full web page reload is completed.
So, yeah, not the best method. However it’s ok to unblock you till WordPress Core provides a correct implementation to move publish knowledge to PHP-only registered blocks.
Utilizing placeholders
There are conditions during which it’s tough to realize a good preview within the editor. In sure conditions, it’s even unimaginable.
Assume, for instance, of a publication kind supplied as a snippet of HTML and JavaScript. Because of the limitations we’ve seen, the editor preview will all the time look damaged.
In a scenario like this, you’ll be able to implement a placeholder within the editor. It is a technique that WordPress Core makes use of as nicely, as we are able to see for the Publish Content material block:

Customers don’t anticipate an actual preview in each case. Select the very best compromise between the time wanted to realize a correct block editor preview and the anticipated UX acquire.
Including CSS stylesheets
You should 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 perform 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:
perform 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(
'%s
',
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 you want to add further courses or kinds, you’ll be able to move these to get_block_wrapper_attributes() within the render callback perform.
$wrapper_attributes = get_block_wrapper_attributes(
[
'class' => 'custom-class',
'style' => 'color: #333',
]
);
It’s the very best apply to make use of this class because the widespread root class for writing focused kinds. I want to make use of the Block, Ingredient, Modifier (BEM) method for writing block kinds. It prevents my kinds from clashing with kinds supplied by WordPress Core or different code.
A standard state of affairs 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 singular prefix for these legacy courses.
If you’re coping with an internet site that makes use of a front-end framework like Bootstrap, keep away from enqueuing any framework stylesheets. You should solely migrate the CSS directions that the block wants, making use of distinctive prefixes as described above.
Use the iframed editor, if potential
There are two methods for WordPress to combine the publish editor into the admin:
- Embedded into the present admin web page
- Built-in by an iframe
WordPress began with the primary method however shortly realized that it made styling the block editor very tough. With out an iframe, any admin kinds can intrude with that kinds of the block editor, together with your customized blocks.
In apply, because of this your blocks can look completely different within the editor than they do on the entrance finish. For simplicity you wish to use the identical kinds throughout each the entrance finish and the editor preview with minimal adjustment. And the iframed publish editor permits you to do this.
As of WordPress 7.0, the publish editor is iframed if all blocks within the publish are Model 3 or larger. WordPress 7.1 will implement the iframe method independently of the blocks.
So, to simplify constructing blocks and put together for the following 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’ll be able to register it, after which move the deal with to the view_script throughout registration:
perform 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(
'Hello World!
',
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 parts to the consumer. It is going to additionally add attributes to the block to retailer the consumer’s selections.
There are options that may work independently of the theme. Others should be enabled by the theme by its theme.json file.
Right here is an instance enabling shade customization for the textual content and background shade:
perform css_tricks_hello_world_block()
{
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'%s
',
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 deal with the outputting the corresponding CSS courses and inline kinds utilizing get_block_wrapper_attributes().
Helpful block helps choices
Permitting customers to customise the looks is an effective demonstration, however not one thing that you’re more likely 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 just use a block emigrate a legacy PHP characteristic solely utilized in a single template.
On this state of affairs, you can set inserter to false to cover the block from the inserter. Hidden blocks keep totally useful.
'helps' => [
'autoRegister' => true,
'inserter' => false, // Hide from inserter
],
Solely permitting a single block occasion per publish
Setting a number of to false permits the block to solely be inserted as soon as into every publish. 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 forestall 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 all the time out there. Huge and full-width alignment are solely enabled if the theme helps it.

WordPress handles outputting the required courses for the block’s design to mirror the specified alignment.
If you wish to selectively allow alignments, you’ll be able to specify them. The out there choices are left, heart, proper, large, and full.
'helps' => [
'autoRegister' => true,
'align' => ['left', 'center', 'right'], // Selective alignments
],
With the following pointers you must be capable 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 price 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 enhancing experiences that WordPress customers anticipate. PHP-only block registration received’t exchange 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 1000’s of WordPress websites caught with traditional themes due to the excessive studying curve and excessive price of rebuilding with JavaScript.
Now you can take shortcodes, widgets, and template components 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 just construct don’t should be good. So long as you’ll be able to insert them into block content material, they usually render accurately 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 price it.
However past this characteristic, PHP-only registered blocks sign an vital shift: WordPress Core is lastly prioritizing developer expertise. Whilst somebody who builds JavaScript-powered blocks usually, I’ll admit that the method includes 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 are able to do to make this course of simpler, or keep away from it fully, is greater than welcome.
So if in case you have initiatives with legacy PHP code stopping a migration to a block theme, then WordPress 7.0 has eliminated your largest impediment.
Migrate these legacy options to blocks and unlock the whole lot fashionable WordPress has to supply.









