WP Manifestindependent plugin directory
manifest / developer / cbp-block-builder

CBP Block Builder

A GUI block scaffolder for cb-js-skeleton2026-shaped native-block themes (block.json/edit.js/render.php). Local-development only — inert on any environment that isn't detected as local.

★ 0stars

Install

Download source zip No release zip yet

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.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

A 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-skeleton2026 or cb-hts-js-2026:
    • a blocks/ directory that inc/blocks.php (or equivalent) auto-registers via glob( 'blocks/*/block.json' )
    • a src/css/editor.css with 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 (see inc/environment.php)
    • blocks/_shared/EditorBlockShell.js, which generated blocks import for the shared show/hide editor chrome on the block title row
    • blocks/_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 own radio branch (added upstream in lc-js-skeleton2026/cb-js-skeleton2026) — without it, a radio sub-field silently falls back to a plain text input
    • blocks/_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')
  • Node/npm installed on the machine you'll run npm run blocks:build on (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

  1. Activate the plugin on a local (.local) WordPress install.
  2. Go to Block Builder in the wp-admin sidebar.
  3. 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.
  4. Click Create Block.
  5. In a terminal, in the theme directory, run npm run blocks:build (or npm run blocks:start while iterating) — the block won't appear correctly in the editor until this has run.
  6. 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:build for you — it only writes files.
  • It does not preserve hand edits to src/edit.js across a field edit, and it never touches render.php at all after initial creation — see "Editing an existing block's fields" above.
  • It does not support the relationship field type (multiple posts, e.g. blocks that pull a whole grid from a custom post type via WP_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.js filters 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