Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/chillibyteuk/cbp-block-builder/archive/refs/heads/main.zipA GUI block designer for cb-js-skeleton2026-shaped WordPress themes
(native blocks — block.json / src/edit.js / render.php, no ACF). It's a
visual replacement for that theme family's add_block.sh CLI script: design
a block's title and fields in wp-admin — including ACF-style conditional
logic between fields — click Create Block, and the files land in the
active theme's blocks/ directory ready for npm run blocks:build. A block
it created can be reopened later and have its fields edited the same way.
Kept as a separate plugin on purpose — the skeleton theme stays one freely
copyable/forkable asset, and this tool stays a separate one. It never edits
theme files beyond writing a new blocks/{slug}/ directory, and it never
touches git.
Local-only, by design
This plugin does nothing at all unless it detects a local development
environment — either wp_get_environment_type() === 'local' or a site
hostname ending in .local. Outside that, it registers no admin menu, no
admin-post handler, nothing. It is safe to have installed (even active) on
a production site.
npm detection (command -v npm) is separate and informational only — it
just changes the wording of a status message on the Blocks tab, it
doesn't gate anything, since shell_exec is commonly restricted even on a
genuine local machine.
File permissions
Blocks are generated by the web server's own user (www-data or similar),
but afterward you still need npm run blocks:build — run as your own CLI
user — to be able to write into that block's new build/ directory.
Those are two different OS users sharing one theme checkout, so the
generator explicitly creates every directory/file it writes as
group-writable (0775/0664), rather than trusting whatever umask the
host happens to be running under. This only works if your CLI user is in
the same group as the web server user (the standard local LAMP/MAMP-style
setup) — a one-time usermod -aG www-data yourusername (or equivalent) if
that isn't already the case. Because this is done in PHP at generation
time, it's correct on any machine the plugin runs on, with no host-level
setup (umask tweaks, ACLs) required per project or per checkout.
Requirements
- A currently active theme shaped like
cb-js-skeleton2026orcb-hts-js-2026:- a
blocks/directory thatinc/blocks.php(or equivalent) auto-registers viaglob( 'blocks/*/block.json' ) - a
src/css/editor.csswith an existing.{prefix}-editor-block { ... }rule — the plugin sniffs this to find the theme's own editor-chrome CSS class prefix, since that prefix isn't reliably derivable from the theme's Text Domain alone (seeinc/environment.php) blocks/_shared/EditorBlockShell.js, which generated blocks import for the shared show/hide editor chrome on the block title rowblocks/_shared/RepeaterField.js, if you intend to use the Repeater field type — this plugin only ever emits a reference to it (import RepeaterField from '../../_shared/RepeaterField'), it does not write that component itself. A repeater's Radio sub-field type additionally needs that component's ownradiobranch (added upstream inlc-js-skeleton2026/cb-js-skeleton2026) — without it, a radio sub-field silently falls back to a plain text inputblocks/_shared/PostTypePicker.js, if you intend to use the Post Type field type — same "emits a reference, doesn't write it" deal (import PostTypePicker from '../../_shared/PostTypePicker')
- a
- Node/npm installed on the machine you'll run
npm run blocks:buildon (not required by the plugin itself, only to actually use the generated block)
If the active theme doesn't look like this shape, the Blocks tab shows a warning rather than hiding the page outright.
Usage
- Activate the plugin on a local (
.local) WordPress install. - Go to Block Builder in the wp-admin sidebar.
- On the Design New tab:
- Enter a block title — the slug it will be created at
(
blocks/{slug}/) previews live underneath, turning red if it collides with an existing block. - Optionally enable Colour support (adds Gutenberg background/text
colour support to
block.json). - Add fields: label, type, optional help text, and a column width (100/50/33/25 — consecutive non-100% fields share a row, same as the CLI's own layout behaviour).
- Optionally toggle Conditional logic on a field to only show it in the block editor when other fields meet a rule — see "Conditional logic" below.
- Enter a block title — the slug it will be created at
(
- Click Create Block.
- In a terminal, in the theme directory, run
npm run blocks:build(ornpm run blocks:startwhile iterating) — the block won't appear correctly in the editor until this has run. - Check the Blocks tab to see every block already in the theme (title, slug, field count) before you start, so you don't collide with one. A block previously created (or edited) here shows an Edit fields link; a hand-written block, or one that predates this plugin's edit support, shows a Custom badge instead — see "Editing an existing block's fields" below.
Field types
| Type | Notes |
|---|---|
| Text / URL / Number | Single-line input |
| Textarea | Choose paragraph (wpautop), list (<ul><li>), or line-break rendering |
| Rich Text | RichText control, wp_kses_post on render |
| Image | Single image, MediaUpload |
| Gallery | Multiple images — the useSelect/core-data + MediaUpload gallery picker pattern, array of attachment IDs |
| Link | Text + URL, optional "open in new tab" toggle |
| Select | Comma-separated options. Wrap one in [brackets] to make it the default; otherwise the first option is used. |
| Radio | Same comma-separated/[bracket]-default options as Select, rendered as RadioControl. Also available as a repeater sub-field type — needs blocks/_shared/RepeaterField.js's matching radio branch. |
| Checkbox | Boolean toggle |
| File | Single non-image file, MediaUpload. Optional "Allowed file types" setting — a comma-separated extension list (e.g. pdf, doc, docx), same convention as ACF's file field; resolved through WordPress's own MIME-type registry (wp_get_mime_types()) to filter the media picker. Leave blank to allow any file type. Stores {name}Id/{name}Name/{name}Url. |
| Repeater | Repeating rows of sub-fields (text / number / textarea / image / file / link / radio — link fields get their own "open in new tab" toggle, radio fields get the same options/default syntax as the top-level Select/Radio types). A repeater's own file sub-field type takes a comma-separated MIME-type allow-list instead (e.g. application/pdf) — a different, lower-level convention than the top-level File field's extension list above, inherited as-is from the shared RepeaterField.js component. Rows can lay out as a row (default) or a column. |
| Post Type | Single post picker (search-as-you-type ComboboxControl, backed by @wordpress/core-data) — the block-editor equivalent of ACF's single post_object field. Pick which registered post type it searches (e.g. product) from a dropdown of post types actually registered on this site. Stores one post ID ({name}Id, type: number); render.php gets a generic get_post() + linked-title skeleton to hand-finish. |
Every generated field, including repeater/gallery, mirrors the structure
of how the reference theme's own hand-written blocks do it — this tool
produces a working skeleton, not necessarily finished design; expect to
open render.php/src/edit.js afterward and adjust markup/classes to the
project's actual design.
Conditional logic
Any field can be shown only when other top-level fields on the same block
meet a rule, matching ACF's own "Conditional Logic" model: OR-groups of
AND-rules (e.g. "show if Layout == Advanced, or if Featured has any
value"). Toggle Conditional logic on a field row to configure it. Only
scalar-valued field types (Text, Textarea, Number, URL, Select, Radio,
Checkbox) can be the target of a rule — Image/Gallery/Link/Repeater/File/
Post Type fields can't be compared against, though any field type
(including those) can itself be conditionally shown. Repeater sub-fields
can't participate on either side. The rules only affect the block editor's
canvas (src/edit.js) — a hidden field's value still saves and still
renders on the front end, same as ACF's own behaviour.
Editing an existing block's fields
Blocks created by this plugin get a .block-builder.json sidecar file
alongside block.json — the designer's own field schema (types,
sub-fields, conditional logic, etc.) that block.json's attributes
can't fully represent on its own. Any block with that sidecar shows an
Edit fields link on the Blocks tab, which re-opens it in the
Design New panel with every field — conditional logic included —
pre-filled.
Saving an edit regenerates block.json, src/index.js, src/edit.js,
and the sidecar from the edited field list — so adding, removing,
resizing, or reordering fields actually takes effect in the editor. The
trade-off: any hand edits previously made to src/edit.js are
overwritten. render.php is never touched by an edit — the on-screen
notice reminds you to check it still matches whatever field changes you
just made. Blocks with no sidecar (hand-written, or created before this
existed) show a Custom badge instead of an Edit fields link on the
Blocks tab — they stay exactly as read-only as before, but the badge
makes clear that's deliberate rather than a missing button.
What this plugin does not do
- It does not run
npm run blocks:buildfor you — it only writes files. - It does not preserve hand edits to
src/edit.jsacross a field edit, and it never touchesrender.phpat all after initial creation — see "Editing an existing block's fields" above. - It does not support the
relationshipfield type (multiple posts, e.g. blocks that pull a whole grid from a custom post type viaWP_Query), or a user-picker field type — both still need to be built by hand, same as with the CLI generator. Single-post lookups are covered by the Post Type field type above. - Conditional logic can't target or be placed on a repeater's sub-fields —
only top-level fields on the block can reference each other. A block
whose hand-written
src/edit.jsfilters which sub-fields a repeater shows based on another field's value (a different, more involved mechanism than field-level show/hide) has no equivalent here, and stays a Custom-badged, hand-maintained block.
File layout
cb-block-builder.php Plugin header, environment gate, bootstraps the rest
inc/
environment.php Local-environment + npm detection, active-theme shape/prefix sniffing
generator.php Builds block.json / src/index.js / src/edit.js / render.php (+ .block-builder.json sidecar) and writes/updates them
admin-page.php Registers the admin menu, renders the tabbed page
handlers.php admin-post.php handler: validates, calls the generator, redirects with a notice
js/
tabs.js Generic tab switching (own copy, not shared with the theme)
field-designer.js Field/sub-field row add-remove-reorder UI, live slug preview
css/
admin.css Admin page styling