Manifests
Manifests are the center of gravity in Wonderpress. Every component and every page template is defined by a JSON file, and nearly everything else in the system is either generated from those files or reads them at runtime. If you understand the manifests, you understand the project.
This page explains how the system works. When you are hand-editing a manifest and need every key, constraint, and allowed value, the per-type references live with their subjects: the partial manifest reference on the Partials page, and the page-template manifest keys on the Page templates page.
Why they exist
Without a manifest, the definition of a component’s fields lives in three places at once: the PHP class, the block.json attributes, and an ACF field group. Three copies drift, and the drift stays invisible until an editor breaks. Wonderpress collapses those into one file per component and generates the rest.
That single file buys several things at once:
- One definition, every consumer. The same properties drive the PHP class, the block editor’s inspector controls, and the ACF field group.
- Reviewable change. Adding a field is a one-file JSON diff in a pull request, instead of a PHP edit, a
block.jsonedit, and a screenshot of an ACF admin screen. - Clean removal. The manifest records exactly which files were written for a component, so deletion removes exactly what creation made.
- Legibility for agents. An AI agent working in the project reads the manifests (indexed in the generated
AGENTS.md) and knows every component’s shape without spelunking through PHP.
The manifest tree
All manifests live under .wonderpress/manifest/ inside the theme, typed by path:
wp-content/themes/<theme>/.wonderpress/manifest/├── partials/<slug>.json ← one per component├── page-templates/<template>.json ← one per page template└── schema/*.json ← generated JSON Schemas
Each manifest starts with a $schema key pointing at the matching schema file (partial.schema.json or page-template.schema.json). Those schemas are generated from the same constants the CLI validates with, so your editor’s completion matches the validator exactly: it completes keys, property types, lock values, and tab placement while you edit, and underlines a row that mixes shapes. partial create, partial sync, template create, and agents write all keep the schema files and $schema keys current.
One rule with teeth: manifests must be strict JSON. No comments, no trailing commas. A parse error skips the whole file, and for an ACF-compatible partial that failure has a confusing symptom: core falls back to per-slug field groups from wonderpress_template_fields, which can look like fields “went global.”
Partial manifests
.wonderpress/manifest/partials/<slug>.json records what a component is and exactly what was written for it:
{ "$schema": "../schema/partial.schema.json", "name": "Hero", "slug": "hero", "block": "acme/hero", "acf_compatible": true, "properties": [ { "name": "title", "type": "string", "required": true, "description": "" }, { "name": "photo", "type": "image" } ], "artifacts": { "class": "src/partials/class-hero.php", "view": "partials/hero.php", "block": "blocks/hero/block.json", "render": "blocks/hero/render.php", "style": "static/src/scss/components/_hero.scss" }}
Three kinds of information:
- Identity. The class name, slug, block name if a wrapper exists, and whether the partial is ACF-compatible.
block,artifacts.block, andartifacts.renderappear only when the partial has a block wrapper. - Properties. The fields, with types, labels, required flags, and
whenconditionals. This is the only place fields are defined. The full type catalog and wire formats are in Property types. - Artifacts. The paths of every file written for this partial. The manifest never advertises a file that does not exist:
styleandscriptare recorded only when Static Kit actually wrote them, andpartial removedeletes exactly these paths. That is also how removal keeps working across Static Kit layout generations; the recorded path is the source of truth, whatever the installed kit’s current convention.
Partial manifests declare fields only, never ACF location rules. Placement is the job of page-template manifests and the wonderpress_template_fields filter. See Working with ACF.
Page-template manifests
.wonderpress/manifest/page-templates/<template>.json is the other half: if partial manifests are the component dictionary, template manifests are the sentence. Each declares an ordered composition (which partials and field groups make up the page) and the editor contract (editor.lock, editor.acf, editor.native) for pages saved to that template. Everything that normally lives as scattered editor configuration is one reviewable file per template.
The full anatomy, row types, and validation rules are on the Page templates page.
Who reads manifests
| Consumer | When | What it does |
|---|---|---|
| The CLI | generation time | partial sync regenerates $_properties and block.json; partial list, partial remove, block create, block list, and block remove index manifests instead of scanning source files |
| wonderpress-core | runtime | Loads the theme’s manifests; on acf/init, registers field groups for located ACF-compatible partials; feeds property definitions to the block editor as window.wonderpressBlockSchemas so inspector controls match the manifest semantics |
| Your editor | while you type | Completion and validation via the $schema pointer |
| Agents | always | The generated AGENTS.md carries a live index of the manifests |
The CLI owns the files; core only ever reads them.
Syncing
When you change a component’s fields, edit the manifest and regenerate:
# 1. edit .wonderpress/manifest/partials/hero.json# 2. regenerate the class $_properties and block.jsonwonderpress partial sync Hero# 3. reload the block editor; re-save a page if ACF groups changed shape
Variants:
wonderpress partial sync --all # every partial in the themewonderpress partial sync Hero --dry-run # print what would be writtenwonderpress partial sync --slug hero # address by slugwonderpress partial sync Hero --properties-only # skip render.php
The division of labor this enforces:
| Safe to edit by hand | Generated: edit the manifest, then sync |
|---|---|
partials/*.php view templates | src/partials/class-*.php ($_properties) |
SCSS and JS stubs under static/ | blocks/<slug>/block.json attributes |
| Helpers, services, custom PHP outside generated classes |
Drift
Drift is the state where a generated file no longer matches its manifest, which happens exactly one way: someone edited $_properties or block.json by hand. Wonderpress treats drift as a failure, because a drifted file means there are two definitions of the component again, which is the disease manifests exist to cure.
wonderpress partial check-drift Hero # one partialwonderpress partial check-drift --all # the whole theme, for CI
Exits non-zero on drift. Repair with partial sync (the manifest wins), or revert the hand-edits (your edit was wrong to make there). wonderpress lint runs the same check, and --format json is supported for scripts. Note that lint --fix does not repair drift; that is always partial sync‘s job.
For a pre-merge guard in the theme repo, a husky hook that only runs when relevant paths changed:
#!/bin/sh# .husky/pre-commit (adjust paths to your theme root)changed=$(git diff --cached --name-only)echo "$changed" | grep -qE '\.wonderpress/manifest/partials/|src/partials/class-|blocks/.+/block\.json' || exit 0wonderpress partial check-drift --all || { echo "Partial drift: run wonderpress partial sync <Name> or revert generated files." exit 1}
Template manifests have their own validator, wonderpress template validate, which checks what the schema cannot: that every partial slug exists in the theme, that instance ids are unique, and that every composition row is actually rendered by the template PHP. Also run by lint. See Page templates.
Core primitive manifests
wonderpress-core ships manifests for its primitive partials (currently link). Core loads them automatically for ACF registration when the primitive is located; to reference one explicitly in your project:
wonderpress partial install-manifest linkThat copies the canonical manifest into .wonderpress/manifest/partials/, where it behaves like any other.