You can now register a working custom WordPress block using nothing but PHP and a bit of HTML markup — no React, no npm, no wp-scripts build step, no node_modules folder eating your disk. WordPress added native support for PHP-only block registration, and it closes the gap that kept a lot of us out of block development for years.
If you've ever wanted a reusable "callout box" or "pricing table" block for a client but recoiled at the toolchain, this is the shortcut you were waiting for. Below is exactly how to build one, plus an honest line on where PHP-only runs out of road and JavaScript earns its keep.
What actually changed with PHP-only block registration
The short version: WordPress now reads a block's metadata from a block.json file and lets you supply the front-end and editor output through PHP render callbacks, so you never touch a JavaScript build pipeline. The block still shows up in the inserter, still saves cleanly, still behaves like a first-class block — it's just defined server-side.
Before this, the "proper" way to make a block meant scaffolding a project with @wordpress/create-block, writing your edit and save functions in JSX, and running Webpack to compile it all. That's a real barrier if you're a freelancer who writes PHP all day and hasn't touched a bundler. The new path leans on dynamic blocks — blocks whose HTML is generated by PHP at render time — and pairs them with a small metadata file. That combination is now enough to ship.
The catch worth stating up front: PHP-only blocks render on the server, so the block editor shows a preview generated by that same PHP, not a live in-place editing experience. For a huge number of real client blocks, that trade is completely fine.
Build a working block in three files
You need three things: a block.json, a PHP render template, and a few lines to register it. Here's a complete "notice" block you can drop into a plugin or your theme's functions.php today.
First, the metadata. Create blocks/notice/block.json:
block.json
"apiVersion": 3"name": "tpc/notice""title": "Notice""category": "text""icon": "info""render": "file:./render.php""attributes": { "message": { "type": "string", "default": "" }, "tone": { "type": "string", "default": "info" } }
The line that does the heavy lifting is "render": "file:./render.php". That tells WordPress to run a PHP file every time the block is displayed — in the editor and on the front end. No save function, no compiled bundle.
Next, the template. Create blocks/notice/render.php right beside it:
render.php
- "notice notice--$tone" ) ); ?>>
Inside render.php you get three variables for free: $attributes (your block settings), $content (inner blocks, if any) and $block (the block instance). Use get_block_wrapper_attributes() so your block picks up alignment, custom classes and the block supports WordPress adds automatically.
Finally, register it. One line in your plugin or theme:
add_action( 'init', function() { register_block_type( __DIR__ . '/blocks/notice' ); } );
Point register_block_type() at the folder containing block.json and WordPress does the rest. Refresh the editor, search for "Notice" in the inserter, and it's there. Three files, zero build tools.
Making it editable without a build step
The honest answer: for text and settings, you edit PHP-only blocks through the sidebar controls, and WordPress can generate those controls from your block.json attributes — but true click-into-the-block editing still needs JavaScript. Here's how to get a genuinely usable editing experience anyway.
For most content blocks, lean on inner blocks. Instead of storing the message as a custom attribute, let editors type directly using core blocks nested inside yours. In block.json you'd drop the message attribute and in render.php you echo $content. The editor then shows a normal paragraph the user can style, link and format with the tools they already know. You wrote no JS and gave them a rich editing surface.
For the settings that aren't content — a "tone" dropdown, a toggle, a number — you have two clean options. Expose them through the block's HTML comment attributes and let a power user set them, or add a tiny sidebar panel. That panel is the one place a few lines of JavaScript genuinely pay off, and you can inline it without a bundler because it's plain ES5-compatible wp.blocks and wp.element calls enqueued as a normal script. No compilation required — just don't write JSX.
When PHP-only is enough — and when it isn't
Use PHP-only blocks whenever the block's output depends on server data or the editing needs are simple; reach for a full JavaScript build when editors need live, in-canvas interaction or heavy custom controls. That line is clearer than most tutorials admit.
| PHP-only is the right call | You'll want JavaScript |
|---|---|
| Latest posts, product lists, dynamic queries | Rich drag-and-drop layout tools |
| Callouts, notices, CTA boxes using inner blocks | Live preview that updates as you type in-canvas |
| Blocks whose content changes after publish | Complex custom sidebar controls (color pickers, media grids) |
| Anything you'd otherwise build as a shortcode | Blocks distributed to thousands of unknown users |
A useful rule of thumb: if you were about to write a shortcode, write a PHP-only block instead. You get the same server-side simplicity plus a proper inserter entry, block supports, and a preview — without the shortcode's clunky [bracket] UX. Dynamic content is where this shines, because that content should render fresh on every page load anyway.
Where PHP-only starts to strain is bespoke editing. If a client needs to visually arrange cards, drag a slider and watch spacing change in real time, you're rebuilding an interactive UI — and that's what React-based blocks exist for. Don't fight it. Knowing which side of the table you're on saves hours.
A quick pre-launch checklist
Before you ship a PHP-only block to a client site, run through these to avoid the common gotchas.
- Escape everything on output —
esc_attr(),esc_html(),wp_kses_post(). Render callbacks run on the front end, so treat every attribute as untrusted. - Use
get_block_wrapper_attributes()so alignment and custom CSS classes actually apply. - Set
apiVersionto 3 inblock.json— older values miss recent features. - Enqueue block CSS via
block.jsonusing thestyleandeditorStylekeys, not a global stylesheet, so styles load only when the block is present. - Test in a staging copy first. If your host gives you an easy staging environment, clone the site, register the block there, and confirm the editor preview matches the front end before touching production.
That last point matters more than it sounds. Because PHP-only blocks render server-side, a fatal error in your render.php can white-screen the editor. Catching that on staging keeps a client from ever seeing it. On TPC Hosting you can spin up a staging site, break things freely, and push the working version live once it checks out — with real engineers on hand 24/7 if something goes sideways.
Why this is a genuinely good moment for freelancers
PHP-only registration means the barrier to custom blocks is now roughly the same as writing a shortcode — which most of us have done a hundred times. That's the whole story, and it's a big deal for anyone running client sites without a front-end build habit.
For small agencies especially, this cuts maintenance. A block with no compiled bundle has nothing to re-build when Node updates, nothing to break when a dependency deprecates, and nothing a future developer has to reverse-engineer. It's just PHP in a folder. When you hand a site over — or inherit one — plain PHP blocks are far kinder than a mystery build/ directory.
If you host client sites with us at TPC Hosting, you already have the pieces: PHP version control, staging, and free migration if you're bringing sites over from elsewhere. Build the block, test it on staging, ship it. No toolchain tax.
FAQ
Do I need Node.js or npm to build a PHP-only WordPress block?
No. PHP-only block registration needs only a block.json file, a PHP render template, and a call to register_block_type() — there's no npm, Webpack or build step involved. You write the block entirely in PHP and HTML, the same skills you already use for shortcodes.
Can PHP-only blocks be edited visually in the WordPress editor?
Partly — settings are edited through the sidebar and content through nested inner blocks, but there's no live in-canvas typing preview without JavaScript. For most content blocks, using core inner blocks gives editors a rich, familiar editing experience with no custom JS required.
When should I still use React for a WordPress block?
Use React when editors need live, interactive in-canvas editing or complex custom controls like drag-and-drop layouts and media grids. If the block's output is dynamic server data or the editing needs are simple, PHP-only is the better, lower-maintenance choice.
Is a PHP-only block the same as a dynamic block?
Effectively yes — a PHP-only block uses a server-side render callback, which is what makes a block 'dynamic.' Its HTML is generated by PHP on every page load rather than saved into post content, which is ideal for content that changes over time.
What WordPress version do I need for PHP-only block registration?
You need a recent WordPress version that supports the file:./render.php key in block.json and apiVersion 3. Keep your install current; if you're unsure, check on a staging copy before rolling the block out to a live client site.

