Gutenberg Block Generator

Scaffold a complete custom block plugin: the plugin file, block.json, the editor script, the render or save, and both stylesheets.

Enable JavaScript to change the fields; the files below are for the defaults.

What the inserter shows. The slug comes from this unless you set one below.
The half before the slash in my-plugin/pricing-table. Use your plugin's name so two plugins cannot collide.
Leave empty to use the title.
A Dashicon name without the dashicons- prefix.
Rendered by
Dynamic renders in PHP and saves nothing to the post. Static saves its markup into the post. This is the one choice that is expensive to change later.
Attributes
The first string attribute becomes an editable RichText field in the editor.

Create this folder: wp-content/plugins/pricing-table

A block is not one file. The generator that writes block.json gets you the manifest and leaves you to write the plugin that registers it, the editor script that gives it an edit function, the save or render.php that decides what lands in the post, and the two stylesheets. This writes all of them, as one plugin folder you can drop into wp-content/plugins/ and build.

How to use

  1. Name the block and the namespace. The namespace is the half before the slash in my-plugin/pricing-table; use your plugin’s name, because two blocks with the same name cannot both register and the second one silently loses.
  2. Choose dynamic or static. This is the one choice that is expensive to reverse, and the difference is explained under Pitfalls.
  3. Add the attributes the block needs. The first string attribute becomes an editable RichText field in the editor, so the block does something the moment you install it.
  4. Turn on the supports you want. Each one adds controls to the block sidebar and costs you nothing.
  5. Copy each file, or read down the tabs. The folder path above the tabs is where they go.
  6. npm install, then npm run build. register_block_type reads build/, not src/.

Example

A dynamic block called Pricing Table in the acme-blocks namespace, with a heading string and a columns number, produces seven files:

pricing-table.php      the plugin, registering build/ on init
src/block.json         apiVersion 3, the attributes, the supports, render: file:./render.php
src/index.js           registerBlockType with edit and save
src/render.php         the front end, escaping the heading it prints
src/style.css          front end and editor
src/editor.css         editor only
package.json           wp-scripts build and start

block.json carries the attributes with defaults of the right JSON type, which matters: a number attribute whose default is the string "3" is a type mismatch WordPress will not correct for you.

Pitfalls

  • Dynamic and static are not interchangeable. A static block saves its markup into post content. If you later change what save returns, every post that already contains the block shows “this block contains unexpected or invalid content” until you write a deprecation. A dynamic block saves nothing and renders in PHP on every request, so you can change the markup whenever you like, but the content disappears from the post if the plugin is ever deactivated.
  • The block name must be unique across the whole site, not just your plugin. my-plugin/card is one global name.
  • register_block_type points at build/. Pointing it at src/ is the most common reason a newly scaffolded block does not appear: the JSX in src/index.js has not been compiled yet.
  • Attribute names are JavaScript identifiers. Spaces and punctuation are stripped here rather than written into a manifest that will not register.
  • This does not write sourced attributes. An attribute with a source and a selector parses its value back out of saved markup, and a selector that does not match the markup you write next is the invalid-content error above. Add those by hand, against markup you have already settled.

Compatibility

apiVersion: 3 needs WordPress 6.3 or newer; the generated plugin declares 6.5 because that is where render: file:./render.php in block.json is dependable. The build needs Node 18 or newer and @wordpress/scripts 27, which the generated package.json pins. Nothing here is uploaded: the files are written in your browser.

Frequently asked questions

Should I pick dynamic or static?
Dynamic unless you have a reason. It cannot fail editor validation, you can change the markup later without deprecations, and it can show things that change after publish. Pick static when the block must survive the plugin being removed, or when the markup is genuinely fixed.
Why is there no save for my dynamic block?
Because there is nothing to save: PHP renders the front end. The one exception is a dynamic block that allows inner blocks, where save writes the inner blocks out so render.php receives them as $content. The generator handles that for you.
Can I add my own fields to the block sidebar?
Yes. The generated edit function is a normal component. Import InspectorControls from @wordpress/block-editor and put PanelBody inside it; the attributes are already declared.
Does it support InnerBlocks?
Yes, with the “Allow blocks inside it” switch. The editor uses useInnerBlocksProps so the block is a drop target, and a dynamic block still saves its children.
Why does the editor show a dashed outline on my block?
That is src/editor.css, which editorStyle loads in the editor only. Delete the rule if you do not want it; nothing in that file reaches the front end.

From the people who built this tool

WP Adminify

The WordPress admin, rebuilt: a dashboard worth looking at, menu and column control, a real file manager and the login page your client sees.

See WP Adminify Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.