Skip to main content

Installing external themes

External themes are third-party themes packaged as .zip files. You upload the ZIP via the admin's Install theme button; the admin extracts it client-side, uploads files to Flexweg, and registers the theme so it shows up alongside the built-ins.

The same mechanism powers all themes — the built-in default / magazine / corporate themes are technically built as "external bundles" by the admin's build pipeline. Third-party themes work identically.

Prerequisites

You need a .zip file containing:

  • manifest.json — installation metadata (id, name, version, apiVersion, entry)
  • bundle.js — the ESM bundle with the theme's React templates
  • theme.css — the compiled CSS for the public site
  • README.md — optional, surfaced via "Learn more"

→ For theme authors: see External theme tutorial for the format.

The install flow

  1. Open Themes in the sidebar.
  2. Click Install theme at the top.
  3. The install modal opens. Click Choose ZIP or drag a .zip onto the picker.
  4. The admin extracts client-side via JSZip. Validation runs:
    • manifest.json exists
    • manifest.id is sanitised lowercase ASCII + dashes
    • manifest.apiVersion is in [FLEXWEG_API_MIN_VERSION, FLEXWEG_API_VERSION]
    • bundle.js (or whatever manifest.entry points to) exists in the ZIP
    • Not already installed — if the id matches an existing entry, the install fails with "duplicate id" — uninstall the existing one first
  5. If validation passes, every file in the ZIP is uploaded to /admin/themes/<id>/<file> on Flexweg (via flexwegApi.uploadFile).
  6. The Firestore registry doc (settings/externalRegistry) is updated with the new entry.
  7. The admin reloads. The theme appears as a card in the Themes list.

Activating the new theme

Installing doesn't make the theme active — it just makes it available. Click the theme's card → Activate to switch.

The activation flow is the same as for built-ins: confirmation modal → regenerate every published page with the new templates + CSS. See Switching themes.

What gets uploaded where

Your Flexweg site
└── /admin/themes/<theme-id>/
├── manifest.json ← copied from the ZIP, used by re-zip / re-install
├── bundle.js ← the ESM module — admin imports this at boot
├── theme.css ← uploaded as /theme-assets/<id>.css when synced
└── README.md ← optional

The bundle.js is the runtime artefact — the admin's external loader does import("/admin/themes/<id>/bundle.js") to load the theme's manifest.

The theme.css is the theme's baseline CSS — uploaded to /theme-assets/<id>.css when you click Sync theme assets (or when you switch to the theme). Without that step, the public site would 404 on the theme CSS.

Upgrading an installed theme

There's no native "Update" button. To upgrade:

  1. Uninstall the existing version (Themes → click the theme's card → Uninstall)
  2. Install the new ZIP (Themes → Install theme → pick the new ZIP)

Theme settings (your saved colour overrides, logo, etc.) are stored at settings/site.themeConfigs.<theme-id> in Firestore — they're keyed by theme id, so re-installing the same id preserves them. Uninstalling doesn't delete the config.

If the new version has a breaking change to the config shape, the theme should provide migration in its manifest's register() callback or settings page.

Manual install (without the upload UI)

If you can't use the upload UI (e.g. you're scripting deploys), you can do it manually:

  1. Unzip the theme into a folder named <theme-id>/
  2. Upload that folder to /admin/themes/<theme-id>/ on Flexweg (via Flexweg's File manager or the Files API)
  3. Add the entry to settings/externalRegistry in Firestore (via the Firebase Console):
// settings/externalRegistry
{
themes: [
{
"id": "your-theme-id",
"version": "1.0.0",
"apiVersion": "1.0.0",
"entryPath": "themes/your-theme-id/bundle.js"
}
],
// existing plugins...
}
  1. Reload the admin. The theme appears.

Common install errors

"duplicate id"

A theme with this id is already installed. Uninstall it first, then try again.

"manifest.json missing"

The ZIP doesn't have a manifest.json at its root. The author may have zipped a wrapper folder by mistake. Common pattern: mytheme-1.0.0/manifest.json instead of manifest.json. The admin's install detects single-folder wrapping and peels it transparently — but if there are multiple wrapping folders, manual flattening is needed.

"bundle.js missing"

Same — the entry file declared in manifest.json (defaults to bundle.js) is not in the ZIP. The author probably forgot to run their build before zipping.

"apiVersion outside supported range"

The theme was built against an admin API version your admin doesn't support. Either:

  • The theme is too old — the author needs to release a newer version
  • The theme is too new — your admin is older than the theme requires; upgrade the admin

"Authentication failed. Check your Flexweg API key in Settings."

The Flexweg API rejected the upload. Check Settings → General → Flexweg API key (or re-run the first-run setup if config is broken).

Continue