WebProCMS ships Themes — complete, niche-specific starter sites you choose from when you install. A theme isn't just a color scheme: it's a full set of pages (home, about, contact, and more), real starter copy, navigation, and a matched look. Pick the Legal theme and you start with a law-firm site that already reads like one; pick the Storefront theme and you start with a working online shop whose homepage is built around your products; pick the Realty theme and you start with an MLS-driven real-estate site — property search in the hero, featured and recently-sold listings, a home-valuation lead form, and community pages; pick the Bistro theme and you start with a working restaurant site — a seeded starter menu with dish photos, a live /menu page, and online ordering at /order; pick the Tradeworks theme and you start with a complete service-business site (electrician copy out of the box, ready for any trade) — services, testimonials, service-area, FAQs, and quote-request pages with real photography; pick the Masthead theme and you start with a working online magazine — a lead-story homepage that fills itself from your articles (featured hero, trending strip, a section per category), ten ready-to-replace starter articles with cover photography, and a newsletter signup wired in; pick the Cornerstone theme and you start with a complete church site — service times and a Plan Your Visit page, sermons published as "Messages" with eight starter messages across three series, online giving at /donate (the Donations feature comes on automatically), and events + sermon-video sections that light up if you add those features; pick the Motorline theme and you start with a complete car-dealership site (the Automotive feature comes on automatically) — an inventory-search hero, featured vehicles and latest arrivals pulled live from your lot, browse-by-body-type tiles, a Sell Your Car trade-in page, and dealer-location cards, in a dark Apex performance look or a bright Showroom one; pick the Default theme and you get a clean general-purpose site. Either way you're editing real pages from minute one instead of staring at a blank canvas.
Themes work on three levels, each safe to change without losing your work:
- Variants re-skin your site — colors, fonts, header, footer — in one click, never touching your content.
- Switching themes swaps in a whole different starter site (its own pages and content), non-destructively: your old site is parked in its own folder, and you can switch back anytime.
- The redesign workspace lets you build a new theme's site — editing its pages, previewing it privately — while your current site stays live, then flip it live when you're ready.
What a theme gives you
Every theme installs a working public site, not a template you have to assemble:
- Real pages — home, about, contact, privacy policy, terms, a blog, locations, and a styled 404 — already wired to routes and navigation.
- Niche-appropriate copy — headlines, intro paragraphs, practice-area cards, FAQs, and calls to action written for the theme's niche. You edit them like any other page; the starter text is just a head start.
- A matched look — brand colors, heading and body fonts, and a header/footer that suit the niche.
- At least one Variant, so there's a built-in way to change the feel without rebuilding anything.
- Any features it depends on, turned on for you. A theme built around a premium feature declares that dependency, and installing the theme activates the feature automatically. The Storefront theme requires E-Commerce: pick it and the shop module is enabled, its database tables are migrated, and
/shopis materialized before your homepage ever renders — no separate trip to Settings → Features.
Variants: change the look, keep the content
A Variant bundles a theme's look — brand colors, fonts, and the header/footer choice — separately from its content. Because every variant of a theme shares the same pages, switching variants is completely safe:
- Your page content is never touched. Headlines, paragraphs, images, button labels, and every edit you've made stay exactly as they were. Only the colors, fonts, and header/footer change.
- It's instant and reversible. Switch from Classic Serif to Modern Sans, decide you preferred the first one, switch back — your content is byte-for-byte unchanged either way.
- No rebuilding. You're not re-picking a header or re-entering brand colors; the variant carries all of that and applies it in one step.
Preview a variant before applying it
Every variant card also has a Preview button. Click it and WebProCMS opens your live site in a new tab with that variant's colors, fonts, header, and footer overlaid — only you see it. Visitors (and teammates who aren't previewing) keep seeing your current design, and nothing is written until you click Apply. A small "Previewing variant" badge floats at the bottom of the site with an Exit preview link, the card shows a Previewing badge back in the dashboard, and Stop preview ends it whenever you're done browsing. It's the same private-preview channel used for parked themes, scoped down to just the look.
Save your current design as a variant
You aren't limited to the variants a theme ships with. Tune your brand colors, fonts, header, and footer to taste, then click Save current design as variant and give it a name — your look is captured as a reusable variant of your theme. Custom variants sit alongside the built-in ones with a Custom badge, and you can rename, update from current design (re-snapshot after more tweaks), or delete them. It's the fastest way to bank a look you like before experimenting, or to keep a couple of seasonal palettes a click apart.
Switching themes after install
You are no longer locked into the theme you picked at signup. Any installed theme can become your live site — and because a theme brings its own pages and content, switching a theme is a real redesign, not a re-skin. WebProCMS makes that safe with a folder-per-theme model:
- Install a theme "parked." From Available Themes, click Install on any theme. It's set up in its own folder alongside your live site — its pages and starter content are ready — but your live site is completely untouched. You can have several themes parked at once.
- Switch to it when you're ready. Switch to this theme parks your current site into its own folder first — every page, all your content, your navigation, and your customized colors and fonts — then brings the new theme live. Nothing is deleted.
- Switch back anytime. Because each theme keeps its own folder, going back to a previous theme restores it exactly as you left it — including the design tweaks you'd made. Flip between them as often as you like.
Variants and full theme switches are different tools for different jobs. A variant changes the look and keeps your content. A theme switch brings a whole different site — different pages, different starter content — with your old one safely parked. If you want the new theme's look but your content, the answer is usually a new variant, not a switch.
Manage the parked themes you're not using from the same screen: each shows how much disk space it's using, and you can delete one you no longer want (its stored content is cleaned up automatically; your live site is never affected).
The redesign workspace: build the new site while the old one stays live
The most powerful part of switching is that you don't have to do it blind. Once a theme is parked, click Edit pages to open its workspace — a full page list for the parked theme where you edit its pages in the normal page editor, and add, clone, or delete pages, all while your current site keeps serving visitors.
- It's the real editor. Rows, content, design tools, media — everything works exactly as it does on your live pages. A page you're editing shows a clear Workspace badge so you always know you're working on the draft, not the live site.
- Your live site is never affected. Nothing you do in the workspace changes what visitors see. The site-wide bands you share across pages (like your page-title header or call-to-action footer) are shown locked in the workspace — those belong to your live site and switch over automatically when you go live.
- Flip it live when it's ready. When the redesign is done, Switch to this theme makes it your live site in one step — and your previous site parks away in case you change your mind.
This is the "start fresh" redesign flow: pick a new theme, build it out over days or weeks without any pressure or downtime, and launch it the moment it's ready.
Preview your draft privately, at real URLs
Building a redesign is one thing; seeing it is another. Click Preview on a parked theme and WebProCMS opens your site with the draft theme applied — its pages, its navigation, its colors and fonts, its header and footer — at your real web addresses. A small "Previewing draft theme" badge floats at the bottom so you always know it's the draft, with an Exit preview link to jump back to the live view.
The key is who sees it: only you do. While you're previewing, every ordinary visitor — and any teammate who hasn't turned preview on — keeps seeing your current live site, exactly as before. You get to click through the whole redesign as if it were live, on the real URLs, with zero risk of a visitor stumbling onto an unfinished page.
Save your current site as a theme
Any site you've built can become a reusable theme. Click Save current site as theme, give it a name, and WebProCMS captures your pages, content, navigation, media, and design into a theme bundle. From there it behaves like any other theme — install it parked, preview it, switch to it. Two things this is great for:
- A restore point. Snapshot your site as a theme before a big redesign, and you always have a one-click way back to exactly how it looked.
- A starting point for the next site. Built a great site for one client or location? Save it as a theme and start the next one from it instead of from scratch.
Start a new theme (duplicate)
Start new theme (next to Import theme) copies any theme you have — including the active one — into a brand-new theme under your own name, and parks it immediately. Open Edit pages on its card and build it out while your live site keeps running; switch when it's ready. The ⋯ menu on any Available Theme card has the same thing as Duplicate into a new theme…. This is how you start a redesign from your current look, or spin a client-specific variation off a base theme, without ever touching the live site.
Move themes between sites (export / import)
Themes travel as plain .zip files. Export theme (on the active theme card, or the ⋯ menu on any Available Theme) downloads the whole bundle — pages, starter content, media, and variants. On another WebProCMS site, Import theme (next to Save current site as theme) uploads that zip; the theme appears under Available Themes, and the live site isn't touched until you install it parked and switch. This is the agency workflow: build a starter site once, export it, import it on each new client install. Imported themes always live in your site's own theme folder, safe from CMS updates. Only import themes from sources you trust — a theme contains the page templates your site will run. CLI equivalents: php artisan themes:export {slug} / php artisan themes:import {file.zip}.
Where you manage it
Dashboard → Design → Theme is the home for all of this (admin only):
- Your installed theme and its details.
- Its Variants as cards — each with Preview (see it live, only you) and Apply buttons — plus Save current design as variant and the rename / update / delete controls for your custom variants.
- Available Themes — every other theme, each showing whether it's Installed (parked) or not. Parked themes get Switch to this theme, Edit pages (workspace), and Preview; not-yet-installed themes get Install. Save current site as theme lives here too.
- An in-progress banner if a theme switch is ever interrupted, with Resume and (before the new theme goes live) Roll back — so a switch is always recoverable.
Every switch, delete, and save is confirmed first, so none of it is an accidental click.
Kept up to date automatically
Themes are shipped and maintained as part of the CMS. When you update WebProCMS, theme fixes and new themes arrive with the update — there's nothing separate to install or manage. A theme improvement made by the maintainers lands for new installs and newly inserted content; it never overwrites pages you've already edited. Your customizations always win. Themes you create yourself (via Save current site as theme) and parked instances live outside the update path, so they're never touched by an update either.
For site authors & the maintainer (technical notes)
Authoring workflows, bundle anatomy, distribution rules, and invariants live in the dedicated theme-authoring.md — update that doc when any of this changes.
These pieces are mostly relevant to whoever owns and ships the CMS, not day-to-day editors.
- Themes live as repo files indexed to a database. Each theme is a directory under
resources/themes/{slug}/— atheme.jsonmanifest, page blades,routes.php+navigation.php, a starteroverrides.jsonof copy, optional bundledmedia/, one JSON file per variant undervariants/, and optionalfeature-pages/{feature}/page sources for feature modules (donations, ecommerce, real-estate, restaurant). Thethemes:indexcommand reads these into thethemes/theme_variantstables (the same repo-files-to-DB model the design library uses). See docs/theme-system-plan.md. - Installs scaffold from a theme. A fresh install runs
App\Support\Themes\ThemeInstaller, which copies the theme's pages into place, seeds its starter content, applies the chosen variant, and compiles the site. It's idempotent and re-run-safe: it never overwrites existing pages and won't re-apply a variant (and clobber your branding) once a site is established. It deliberately ignoresfeature-pages/— those sources are installed only by the owning feature's toggle-on generator, so feature-gated pages never appear on installs that haven't enabled the feature. - Custom variants. "Save current design as variant" runs
App\Support\Themes\ThemeVariantSaver, which snapshots the live branding, fonts, header/footer, and shared-row roles viaThemeCapturer::captureVariantData(). On a normal install the snapshot is a DB-onlytheme_variantsrow flaggedis_custom— the indexer's prune passes skip these, and a newly shipped variant that collides with a custom slug re-slugs the custom one out of the way rather than clobbering it. On an Author-Mode install the snapshot is instead written tovariants/{slug}.jsonand registered intheme.json— a shipped, committable variant. - Parked instances (folder-per-theme). Installing a theme post-install runs
ThemeInstaller::installParked(), which materialises the theme intoresources/views/theme-instances/{slug}/(pages tree +routes.php/navigation.phpsnapshots + aninstance.jsonof metadata and design state) without touching the live site. Its content seeds globally intocontent_overrides; because every theme's row slugs are frozen and theme-namespaced, multiple themes' content coexists in the DB with no collision and no schema change. The tree sits underresources/views/on purpose — the daily orphan-content GC scans there, so a parked theme's content is never swept while it waits. Registry:App\Support\Themes\ThemeInstances. - The workspace. Parked pages are editable in the page editor by path (
theme-instances/{slug}/pages/…); the editor runs them in an admin-only workspace mode where every live-only side effect is disarmed (no page-scoped override sweep, no version snapshot, no live sidecar compile, no shared-row fan-out), and shared rows are locked because they belong to the live site. Page create/clone/delete for the workspace go throughApp\Support\Themes\ThemeWorkspace, which writes into the instance's own tree androutes.phpsnapshot, never the live routes. - The switch.
App\Support\Themes\ThemeSwitcherperforms a checkpointed, resumable swap journaled in thetheme_switch_stateSetting: snapshot the outgoing site into its instance folder (pages, routes, nav, seeded bookkeeping, and its live design state) → park it → promote the incoming instance → regenerate content-type and feature pages the new theme doesn't itself ship (guarded so a theme-shipped page is never clobbered) → apply design state → rebuild sidecars, search, and CSS. A previously-live theme restores its snapshotted design on the way back, so A→B→A is faithful including customised branding. Driven from the dashboard (queued) or viaphp artisan theme:switch {slug} [--resume|--rollback]. - The admin preview channel (two settings).
theme.active_slugis what the public sees;theme.admin_preview_slugdesignates a parked instance for preview. TheThemeAdminPreviewmiddleware renders that instance's pages at their real URLs — with its navigation, branding, and header/footer overlaid per-request viaSetting::withRequestOverrides()— but only for an Admin+ user with the per-session toggle on. Guests are structurally safe: authenticated users never touch the response cache (GuestOnlyCacheProfile), and preview responses are marked no-store. Static URLs are covered in v1; record-driven detail URLs fall through to the live route. - Custom + captured themes. "Save current site as theme" runs
ThemeCapturertargetingresources/themes-custom/on a normal install (gitignored, so it survives CMS updates and is protected by the package updater) orresources/themes/under Author Mode (for shipping).ThemeManifest::scan()reads both roots; a custom theme whose slug collides with a shipped one loses. To make that collision impossible in practice, install-local captures get the reservedcustom-slug prefix (Theme::CUSTOM_SLUG_PREFIX— applied automatically, never double-applied) and the authoring CLIs (themes:new/themes:capture) rejectcustom-*slugs, so no shipped release can ever shadow a client’s own theme. Captured bundles install parked and switch like any shipped theme. - Export / import.
App\Support\Themes\ThemeTransferzips a theme'ssource_dir(export) and does the guarded inverse (import): zip-bomb ceilings, traversal/NUL/realpath guards mirroringFeatureInstaller::installFromZip, staged extraction into a hidden dot-dir, then the normalIndexThemesJobvalidation — a bundle that fails any check leaves no trace. Imports ALWAYS land incms.custom_themes_path, never the tracked tree (promote by moving the dir and re-indexing). Surfaced as Export theme / Import theme on the Theme page and asthemes:export/themes:import. A theme whose slug already exists (either root) is rejected — so a shipped theme's zip imported elsewhere no-ops safely. - Duplicate.
App\Support\Themes\ThemeDuplicatorcopies a theme under a new slug as a full RE-NAMESPACE: every frozen row slug in every blade and everyoverrides.jsonrow_slugis rewritten (:{src}-→:{new}-— the same clone+reslug move the Legal theme was authored with),bundlerefs are stripped from theme.json andbuild-map.jsondropped (the copy is hand-managed, detached from the Page Library bundle system, sothemes:build's drift guard never applies to it), then the copy is indexed/validated like any theme — a failing copy leaves no trace. The dashboard flow (Start new theme) applies the same root/prefix policy as capture (clients → custom tree +custom-prefix; AuthorMode → shipped tree) and auto-parks the copy viaThemeInstaller::installParkedso it's immediately editable. - Feature dependencies (
required_features). A theme'stheme.jsonmay declare"required_features": ["ecommerce"].ThemeInstaller::assertRequiredFeaturesAvailable()gates installation (a parked install checks availability only — enabling, with its migrations and page generators, is deferred to the moment the theme goes live viaenableRequiredFeatures()+FeatureActivator), and an unknown or unavailable feature aborts with aRuntimeException. - Content-type dependencies (
content_types+starter_items). The content-type twin ofrequired_features, introduced for the Masthead theme (whose data-driven homepage queries the blog)."content_types": ["blog"]makes a live install materialize each named type from the seed catalog — type + generated/{slug}pages + routes, via the same scaffold path as the install wizard's checkboxes — while a parked install creates the type definition only (page + route generation is a live-site write, run by ThemeSwitcher's regenerate step at switch time). An entry may also be an object{"slug": "blog", "name": "Messages", "singular": "Message"}when the theme rebrands the type — how Cornerstone publishes sermons on the blog plumbing; the override applies at CREATION only, so an install's existing type is never renamed."starter_items": "starter-items.json"seeds shipped starter records (a magazine's articles, a church's messages — with categories, tags, excerpts, and cover photos from the theme'smedia/) through the normalContentItemwrite path. Strictly one-shot, twice over: a type that already has ANY item is skipped (existing data means the owner owns it), and a durable per-theme Setting marker (theme.starter_items_seeded.{slug}) blocks every later re-run — so an update-time re-seed can never resurrect articles the owner deleted. Both keys are validated at index time (unknown type slugs, malformed or mistargeted starter items reject the theme). - Navigation seeding & authoring tools. A theme's
navigation.phpreturns['menus' => [...]]in the shape of thenavigation.menusSetting; fresh installs and theme switches seed it, ordinary re-seeds and variant switches leave a user's menu edits alone.themes:new {slug}scaffolds an empty theme;themes:capture {slug}snapshots a live site intoresources/themes/;themes:install {slug} --variant=installs one programmatically. - Frozen slugs. Theme pages use stable, theme-namespaced row identifiers (e.g.
hero-split:legal-home-1) so shipped starter copy can be keyed to them and two themes can never collide. The indexer rejects any theme whose identifiers aren't namespaced or collide with another theme — the same guarantee that lets parked themes' content coexist in one database. - Theme-template saves are self-freezing. The author never manages frozen slugs by hand: editing a theme source page in the dashboard (Author Mode) and saving runs
App\Support\Themes\ThemeSourceSync, which (1) re-freezes any freshly inserted row's random slug into the theme's{theme}-{page}-{n}namespace (existing conforming slugs are never touched, so shippedoverrides.jsonkeys stay bound), (2) prunesoverrides.jsonentries for rows that no longer exist on any theme page, and (3) bundles media the page references from the authoring install's storage into the theme'smedia/+manifest.jsonso fresh installs receive it. Deleting a hero and inserting a different one is therefore just: delete, insert, save. - Compose-at-build: theme pages compile from Page Library bundles. A theme page is no longer necessarily hand-managed. A
theme.jsonpage entry may carry a"bundle": "{category}/{name}"ref pointing at a shipped design-library page bundle, andphp artisan themes:build {slug}(App\Support\Themes\ThemePageComposer) compiles each such page from the bundle's@rows— rendering every row's library template with a frozen, theme-namespaced slug and the standard page scaffold, so the output is byte-for-byte the kind of pagethemes:capturesnapshots and the whole runtime (installer, switcher, preview, instances) is untouched. Frozen ids survive rebuilds through thebuild-map.jsonlockfile (identity:{page} : {row template} : {nth occurrence}→ slug — a kept identity key is never renamed, which is what keepsoverrides.jsonstarter copy bound). The identity is positional per template, so this stability has one boundary: inserting, removing, or reordering rows of the same template by hand-editing a bundle's@rowsshifts thenthcounters — every later same-template row rebinds to its predecessor's frozen slug, and its starter copy silently lands on the wrong instance. Distinct-template edits are always safe. Don't hand-edit same-template row order in a bundle; make that kind of change through the stub editor (whose save runs capture-back, regenerating bundle + lockfile together in one consistent step). The lockfile also stores a sha1 per compiled page: a page edited directly since its last build refuses to rebuild without--force, and the stub editor shows a "Compiled from …" badge on bundle-backed pages so an author knows the next build overwrites direct edits there. To change a compiled page permanently, edit the bundle's rows in the design library and rebuild. Hand-tuned class heads (SEOTitle, layout description) survive rebuilds; only the row markup and itsROW:phpblocks are recomposed. - Bundle extraction (the migration tool).
php artisan themes:extract-bundles {slug} --category=decomposes an existing theme's compiled pages into shipped design-library rows (deterministic{theme}-{page}-{template}names, carrying@requiresFeature/@requiresContentTypegates from their source templates) plus one page bundle per page, wires thebundlerefs intotheme.json, seedsbuild-map.jsonfrom the pages' existing frozen slugs, then runs a verification build and asserts parity (identical slug set per page, byte-identicaloverrides.json). The four shipped themes were migrated this way — their home/about/contact/terms/404 pages now compile from bundles filed undercustom(Default),law(Legal),ecommerce(Storefront), andservice(Realty). Pages containing shared/role rows (each theme's privacy page) can't be expressed by an@rowsbundle and stay hand-managed. - Author Mode. Only the repo-owning authoring install (
CMS_AUTHOR_MODE=true) can edit tracked theme/design-library source files from the dashboard, and it's what redirects "save as variant/theme" to the committable source tree. On a normal install those writes go to the gitignored custom trees instead, so the working tree stays clean and CMS updates apply without conflict.
Full implementation detail and build history live in docs/theme-system-plan.md.