This is the opt-in variant of the designer flow. Same goal — designer customizations ship through git, content stays live — but design state lives as theme files (Documentation Index
Fetch the complete documentation index at: https://docs.frankenpress.com/llms.txt
Use this file to discover all available pages before exploring further.
parts/*.html,
templates/*.html, theme.json) in the site repo rather than as DB
rows in a snapshot sidecar.
When to use this variant
Stick with DB-row mode
Default. Snapshots are the canonical channel. Sidecar carries
wp_template / wp_template_part / wp_global_styles / wp_navigation.
Fine for most sites. No tenant changes required.Switch to theme-files mode
Designer customizations are diff-able HTML in code review.
Snapshot shrinks to just navigation + options + attachments.
No DB-row taxonomy juggling at apply time.
What changes vs the default
| Default (DB-row mode) | Theme-files mode | |
|---|---|---|
wp_template, wp_template_part, wp_global_styles | Ship in templates.json sidecar | Live in web/app/themes/<child>/ files |
| Code review diff | Opaque JSON blob | Clean HTML + theme.json diff |
| Apply-time DB writes for design state | Upsert per row, set wp_theme taxonomy | None — files load from disk on render |
Synced patterns (wp_block) | DB-resident, captured | Still DB-resident (no file equivalent yet) |
wp_navigation, options, attachments | DB sidecar | DB sidecar (unchanged) |
| Snapshot footprint | Full design state in JSON | Slim — only the items still in the DB |
| Designer tool | WP Site Editor → save customization → DB row | WP Site Editor → save customization → DB row → “Save to theme” → file write + DB clear |
Tenant bootstrap (one-time per site)
Bump mu-plugin
Tenant
composer.json requires frankenpress/mu-plugin: ^0.13.10 or later.
The drift linter at this version accepts site-tracked themes; the reaper
correctly handles “captured set is empty → reap all rows of this type”.Add Create Block Theme as a designer dep
site-template v0.7.0+ already adds
wpackagist-plugin/create-block-theme to require-dev. On a fresh
fork: nothing to do. On an older fork:--dev is load-bearing: the plugin is a designer tool and must
never reach the production image. The Dockerfile’s
composer install --no-dev already excludes it.Create + activate a child theme
On the designer’s local docker-compose stack (lockdown off, plugin active):Creates
web/app/themes/mysite-design/ with style.css declaring
Template: twentytwentyfive (or whatever the active parent is) and
auto-activates the child.The child theme must be active before any save-user-changes
call. With the parent active, the plugin writes INTO the
composer-managed parent directory — silently overwritten on the
next
composer install locally; fails outright on the
read-only production filesystem.Commit the child theme tree
Unignore the child theme dir in the site repo’s Then
.gitignore so
composer’s web/app/themes/* ignore rule doesn’t swallow it:git add web/app/themes/mysite-design/ && git commit. The
Dockerfile’s existing COPY for web/app/ will bake the child
theme into every image build.Bump the chart's active-theme value
In Before:
gitops-fp (or wherever the chart values live for the tenant):twentytwentyfive (composer-installed parent). After:
the child theme. The install Job’s
wp theme activate {{ .activeTheme }} runs on every release, so
new pods on a fresh DB activate the correct theme.Designer workflow per release
Edit in Site Editor
Designer’s local docker-compose stack, lockdown off, child theme
active. Edit
parts/header.html via Site Editor like normal.
WordPress creates wp_template_part DB rows attached to the
active child theme as customizations land.Save to theme files
Once the designer is happy, invoke Writes
POST /create-block-theme/v1/save:web/app/themes/mysite-design/parts/header.html,
web/app/themes/mysite-design/theme.json, etc. Then clears the
corresponding DB rows.Snapshot + commit
templates.json is now slim — no
wp_template / wp_template_part / wp_global_styles entries
because the DB rows were cleared. The reaper picks up the
“captured set is empty” signal for those types and trashes any
orphan rows on apply (e.g. left over from earlier DB-row-mode
releases).Commit both:- The theme files at
web/app/themes/mysite-design/ - The slim snapshot at
web/imports/launch/
What wp fp apply does in theme-files mode
The apply path is identical to the default — the slim snapshot just
gives it less to do:
- Stage 1 — WXR import: no-op (no additive post types).
- Stage 2 — Attachments: ships the design-asset binaries that templates reference, same as before.
- Stage 3 — Owned posts: upserts
wp_navigation(and anywp_blocksynced patterns that haven’t been migrated). Nowp_template_part / wp_template / wp_global_styleswork because the snapshot doesn’t carry any. - Stage 3b — Reaper: trashes orphan rows for owned types whose captured set is empty. This is what cleans up the previous-release DB-row design state on the first theme-files-mode apply.
- Stage 4 — Options: same as before (site-identity options, theme_mods, page_on_front slug remap).
- Stage 5 — URL retarget: same as before.
Tradeoffs to know about
Exported HTML loses attachment-ID linkage
Exported HTML loses attachment-ID linkage
When the plugin writes The URL still gets search-replaced on apply (so cross-environment
URL rewriting still works), but the bidirectional
WP-attachment-post linkage is gone. Blocks that read the
attachment ID at render time (lightboxes, view-original-size
buttons) won’t find one to read.Acceptable for most sites. Consider sticking with DB-row mode if
you have heavy image-interactive UX.
parts/*.html, image blocks that had
"id":33 and class="wp-image-33" in their captured form get
stripped down to URL-only:Synced patterns (wp_block) still ride in the DB
Synced patterns (wp_block) still ride in the DB
Create Block Theme’s
savePatterns flag can write synced
patterns into the theme, but FrankenPress doesn’t recommend it
yet (not empirically validated end-to-end). For now, leave synced
patterns as DB-resident — they’re captured by the snapshot’s
templates.json and applied via the standard upsert path.Local docker-compose state can drift if you swap themes
Local docker-compose state can drift if you swap themes
Common gotcha: if the designer accidentally activates the
composer-managed parent (not the child) and runs save-user-changes,
the plugin writes into the parent dir. On the next
composer install locally, those files get clobbered.Recovery: re-activate the child, re-run save-user-changes. The
plugin re-reads from DB rows; if those are gone, restore from a
recent snapshot.Drift linter expectations
Drift linter expectations
wp fp snapshot runs a drift check at capture time:- Plugins active but not composer-installed AND not
site-tracked (in
web/app/plugins/<slug>/) → drift error. - Theme active but not composer-installed AND not
site-tracked (in
web/app/themes/<slug>/) → drift error.
web/app/themes/mysite-design/ but isn’t
composer-installed. mu-plugin v0.13.10+ recognises that as valid.
Older mu-plugin versions will trip the linter; bump first.Rolling back to DB-row mode
Rolling back to DB-row mode
Per-tenant migration is reversible:
- Delete
web/app/themes/mysite-design/parts/*.htmlfrom the site repo (keepstyle.css+theme.jsonif you want). - Revert
siteInstall.activeThemeto the composer-managed parent (twentytwentyfiveor whichever). - Re-capture customizations as DB rows: open Site Editor on
local, recreate the customization in admin,
fp snapshot.
wp_template_part rows under the child theme (since it’s no
longer active).Per-repo versions
| Repo | Minimum version |
|---|---|
mu-plugin | v0.13.10 (drift linter site-tracked + reaper empty-set semantics) |
site-template | v0.7.0 (Create Block Theme require-dev) |
charts | v0.11.0+ (any post-v0.11.0; chart logic unchanged for theme-files mode) |
| Create Block Theme plugin | v2.9.0+ |
Companion docs
- Designer flow — default DB-row mode.
- mu-plugin — capture/apply mechanics.
- site-template — Bedrock layout + composer setup.
- charts — install Job +
siteInstall.activeTheme.