Common workflows

Task-level recipes that cross the concept pages. Each chunk is complete on its own: the commands in order, the files you touch, and how to check your work. The concept pages (Partials, Page templates, Manifests, Working with ACF) explain why each step is what it is.

Build a composed page template

The flagship workflow: a bespoke page (a landing page, say) rendered from PHP, hydrated from ACF fields, locked in the editor. Four chunks, each useful on its own.

Chunk 1: create the partials the page is made of

code
wonderpress partial create --name Landing_Hero --acf \  --prop title:string:required --prop photo:imagewonderpress partial create --name Testimonials --acf \  --prop items:repeater --sub items:quote:string:required --sub items:name:string

--acf marks each manifest ACF-compatible, which is what lets the template register fields for them later. No --block here: these sections live on one designed page, so they have no business in the inserter.

If ACF is not installed yet, this is the moment:

code
ACF_PRO_LICENSE=... wonderpress acf install

Chunk 2: create the template with its composition

code
wonderpress template create --name Landing --lock all \  --section hero-main:landing-hero --section quotes:testimonials

That writes three things: template-landing.php with a real render() call per section, the manifest at .wonderpress/manifest/page-templates/template-landing.json, and per-page Static Kit assets (landing.scss, landing.js). The scaffolded PHP already contains:

code
( new \Wonderpress\Partials\Landing_Hero( wonder_partial_props( 'landing-hero', 'hero-main' ) ) )->render();( new \Wonderpress\Partials\Testimonials( wonder_partial_props( 'testimonials', 'quotes' ) ) )->render();

--lock all records the editor lock in the manifest: nothing on this page moves, and content is edited through the ACF fields.

Chunk 3: wire it up in WordPress

  1. Create or open the page in the admin.
  2. Page → Template → Landing, then Update.
  3. Reload the edit screen so PHP can apply the manifest (lock, ACF group, native panels).

The editor now shows one ACF field group for the template, with hero-main and quotes as group fields built from each partial’s manifest properties. Fill them in, update, view the page.

Chunk 4: style it

The template created static/src/scss/landing.scss and static/src/js/landing.js by delegation. Each partial created its component stub (_landing-hero.scss, _testimonials.scss). Opt the page into the components it renders, which is the tree-shaking decision:

code
// static/src/scss/landing.scss@use "lib/utilities";@use "components/landing-hero";@use "components/testimonials";
code
wonderpress static compile --watch

Check your work

code
wonderpress template validate Landingwonderpress lint

template validate confirms every composition row points at a real partial and is actually rendered by the PHP. lint runs that plus drift and the token bridge.

Add a partial to an existing template

Say the landing page needs a pricing section later.

  1. Create it, ACF-compatible: wonderpress partial create --name Pricing --acf --prop plans:repeater --sub plans:label:string --sub plans:price:string
  2. Add a row to the template manifest’s composition:
code
{ "id": "pricing", "partial": "pricing" }
  1. Add the render call where it belongs in template-landing.php:
code
( new \Wonderpress\Partials\Pricing( wonder_partial_props( 'pricing', 'pricing' ) ) )->render();
  1. wonderpress template validate Landing, then reload the editor and Update the page so the reshaped field group saves.
  2. @use "components/pricing"; in landing.scss and recompile.

Add page-only fields to a template

For values that belong to the page rather than to any partial (an SEO description, a settings toggle), add a fields row instead of creating a partial:

code
{ "id": "seo", "label": "SEO", "properties": [  { "name": "meta_description", "type": "string", "format": "textarea", "rows": 3 }] }

Fields rows have no render of their own. Read the values in the template PHP and write the HTML yourself:

code
 seo = wonder_template_composition_field( 'seo' );

template validate checks that the row is consumed, so the call is required, which is the point: a declared field nobody reads is a lie waiting to be found.

Add a field to an existing partial

Fields are defined in the manifest, so the change is a JSON edit plus a sync:

  1. Edit .wonderpress/manifest/partials/landing-hero.json, add to properties:
code
{ "name": "subtitle", "type": "string" }
  1. wonderpress partial sync Landing_Hero regenerates $_properties (and block.json if a wrapper exists).
  2. Use $subtitle in the view, partials/landing-hero.php.
  3. Reload the editor and re-save any page whose ACF group changed shape.

Never add the field in the PHP class or block.json directly; that is drift, and sync will overwrite it.

Expose an existing partial as a block

When a section graduates from one designed page to something editors place anywhere:

code
wonderpress block create Testimonials

That emits blocks/testimonials/ (a block.json plus a render.php that delegates to the partial). wonderpress-core registers it on init; if it is missing from the inserter, run composer install in the theme directory. Then mind the placement rule: a screen where editors insert the block should not also carry the partial’s ACF group, so keep the block off templates that locate those fields.

Add JS behavior to a partial

Most partials have no behavior, so it is scaffolded only on request:

code
wonderpress partial add-js Testimonials

That delegates a static/src/js/components/Testimonials.js class to Static Kit. Nothing auto-imports it; wire it into each page entry that renders the partial, mirroring the SCSS @use list:

code
// static/src/js/landing.jsimport Testimonials from './components/Testimonials.js';new Testimonials();

Then wonderpress static compile. The deliberate import is what keeps other pages from shipping JS they never run.

Remove a section from a template

  1. Delete the composition row from the template manifest and the render call from the PHP.
  2. wonderpress template validate Landing confirms nothing dangles.
  3. If no other template or page uses the partial, remove it entirely: wonderpress partial remove Pricing deletes every artifact its manifest recorded. A wrapping block refuses the removal until you run block remove or pass --with-block.