FrankenPress sites are designed locally in WP Site Editor against an FSE block theme, captured into a versioned snapshot, and promoted through git the same way code changes are. No click-ops on production, no admin-side imports, no premium-theme bake-and-pray. The mental model: design is data, code is code, both live in the image as immutable artefacts. The site image carriesDocumentation Index
Fetch the complete documentation index at: https://docs.frankenpress.com/llms.txt
Use this file to discover all available pages before exploring further.
web/imports/<slug>/ snapshot directories alongside the WordPress
core and theme files; the chart’s install Job applies them on every
helm upgrade.
What this is for
| Theme code | composer-installed, in the image |
Designer-built wp_template, wp_template_part, wp_global_styles, wp_navigation | captured by snapshot, in the image |
Site-identity options (blogname, show_on_front, etc.) | captured by snapshot, in the image |
Uploaded media (attachment post type) | captured by snapshot; the binaries themselves land in S3 |
| User-generated content (orders, comments, customer accounts) | never captured, lives only in the runtime DB |
What it’s not
- A backup system. UGC is structurally outside snapshot scope — restoring a snapshot doesn’t restore orders or comments.
- A premium-theme tool. FrankenPress dropped premium-theme adapters in mu-plugin v0.10.0. Classic-mode themes that need post-install regen hooks, theme-bundled plugins, or Options Frameworks aren’t supported by the designer flow.
- A “design once, sync forever” loop. WP-Importer matches by GUID and skips existing posts; the snapshot model is additive-on-first-apply, not bidirectional sync.
Designers operate as admins
Designers use the local docker-compose stack as full WordPress admins. The safety boundary is the immutable image + in-cluster lockdown, not WP roles. In-cluster,DISALLOW_FILE_EDIT and DISALLOW_FILE_MODS
block theme edits and plugin installs for everyone, admins included.
Locally, designers have full latitude to install block plugins,
evaluate themes, or break things — local state is throwaway.
The controlled promotion path is the PR + image-build + gitops bump,
not a WordPress capability check.
The workflow
Install WordPress (fresh DBs only)
twentytwentyfive) is activated automatically
on fresh installs.Design in Site Editor
Log in at
http://localhost:8080/wp/wp-admin/. Open
Appearance → Editor. Edit templates, template parts, global
styles, and navigation. Upload media. Create pages/posts.Settings you’ll typically also touch under Settings → General:
Site Title, Tagline, and the front-page configuration
(Reading → Your homepage displays).Capture the snapshot
web/imports/<slug>/ containing:manifest.yaml+manifest.json—fp.snapshot/v3manifest, the same data in two forms.content.xml.gz— WXR of every captured post.options.json— scopedwp_options+theme_mods_<stylesheet>.uploads-manifest.txt— sha256 + size per file in the uploads dir.
--slug overwrites cleanly.The bundled Fse adapter captures these CPTs in full:
wp_template, wp_template_part, wp_global_styles,
wp_navigation, attachment, page, post. Options captured:
blogname, blogdescription, show_on_front, page_on_front,
page_for_posts, permalink_structure, site_icon, site_logo,
custom_logo. Theme mods captured for the active stylesheet.What apply does
The chart’s install Job iterates every subdirectory of/app/web/imports/ whose manifest.json exists and runs
wp fp apply per snapshot. For each snapshot:
- Schema check. Reject if not
fp.snapshot/v3. - Idempotency check. Compare
manifest.idandwxr_sha256against thefp_snapshot_applied_ref/_sha256options. Match → skip (Success: apply skipped). - Transient WP-Importer install. If WP-Importer isn’t on disk
(the FrankenPress site image doesn’t ship it by default), the
install Job’s RW overlay filesystem allows
wp plugin install wordpress-importer --activateto land it transiently. The plugin is deactivated at teardown so the next web Pod (read-only filesystem) doesn’t try to load a missing plugin file. - WXR import. Posts INSERT-only via
wp importrunning as a fresh subprocess (avoids aWP_CLI::runcommanddeadlock specific to theimportcommand). - Options apply.
update_option(key, value)for each scoped key. Theme mods written directly totheme_mods_<stylesheet>. - URL retarget.
wp search-replace <source_url> <target_url> --all-tables --report-changed-only. - Adapter
post_apply(). TheFseadapter sweepswp_global_stylesrows whose stylesheet metadata doesn’t match the currentget_stylesheet()(theme-switch cleanup). - Markers stamped.
fp_snapshot_applied_ref+fp_snapshot_applied_sha256— subsequent re-applies short-circuit.
Safety properties
- Additive WXR. WP-Importer only INSERTs. Existing posts matched by GUID are skipped (not overwritten). No DROP, no DELETE, no TRUNCATE anywhere in the apply path.
- Scoped options. Only the adapter’s declared
option_keysare touched. Anything not in that list is invisible to the apply. - UGC is structurally inaccessible. WooCommerce
wc_orders,wp_users,wp_comments, andwp_*metafor posts outside scope are not in any adapter’s scope, so they can never enter or be modified by a snapshot. wp_global_stylesorphan deletion is scoped. The Fse adapter deletes onlywp_global_stylesrows it owns (the CPT is in its declared scope). UGC outside that CPT is untouchable.
Iteration
Designer iteration on existing FSE templates is one-shot, not bidirectional. WP-Importer matches by GUID and skips existing posts — re-applying a snapshot that updates an existing template doesn’t propagate the change. For now, the iteration pattern is:- Capture the new design with a new
--slug=<v2>. - On the target, manually delete the prior version’s
wp_templaterow (or the wholeweb/imports/<v1>/directory + re-apply). - Apply the new snapshot.
Common questions
My snapshot's `blogname` is wrong. Re-capture or edit the option later?
My snapshot's `blogname` is wrong. Re-capture or edit the option later?
Both work. Re-capturing keeps the snapshot self-contained
(recommended). Edit-after-deploy via
wp option update blogname "X" works as a one-off but the snapshot’s blogname value will
overwrite it on the next apply unless fp_snapshot_applied_*
markers match.Can I capture a snapshot from production?
Can I capture a snapshot from production?
Technically yes (
wp fp snapshot runs anywhere WP-CLI runs), but
production captures will pick up customer-edited templates +
customer-uploaded media you may not want in the image. Local
capture from a designer-controlled stack is the canonical flow.What about classic (non-FSE) themes?
What about classic (non-FSE) themes?
The shipping
Fse adapter detects via wp_is_block_theme();
classic themes won’t trigger it. wp fp snapshot errors out with
“no snapshot adapter detected.” A site that genuinely needs a
classic-theme adapter can register one against the existing
AdapterInterface — but the slim mu-plugin baseline ships only
Fse, and adding a second registered adapter requires explicit
user approval per the mu-plugin slim-by-default contract.The snapshot directory has my designer's site URL baked in
The snapshot directory has my designer's site URL baked in
Yes —
manifest.source.site_url records the URL the snapshot was
captured against (typically http://localhost:8080). On apply,
the Restorer runs wp search-replace from that source URL to the
target’s home_url() across all tables (only the snapshot-
imported rows are touched in practice, since the existing site’s
rows already reference the target URL).How do I roll back a snapshot apply?
How do I roll back a snapshot apply?
The forward path is image-versioned. Roll back by deploying an
earlier image tag whose
web/imports/ didn’t contain the
snapshot. The snapshot-imported posts will still exist in the DB
(additive — apply doesn’t delete on rollback), but no apply runs
against the missing snapshot directory so no further changes
land. For a full content rollback, restore from your MariaDB
backup.Companion repos
| Repo | Role in the designer flow |
|---|---|
mu-plugin | Ships the Fse adapter + wp fp snapshot + wp fp apply WP-CLI subcommands. v0.10.0+. |
site-template | What you forked from. web/imports/.gitkeep is the placeholder for committed snapshots. |
charts | Install Job iterates web/imports/<slug>/ directories on every helm upgrade. v0.10.0+. |