Plugins overview
Plugins extend Flexweg CMS by hooking into the publish flow, contributing editor blocks, dashboard cards, settings pages, and translations. The system is heavily inspired by WordPress's filters / actions model — plugins register callbacks that the admin invokes at well-known lifecycle points.
Three categories of plugins
| Category | Where they live | Toggleable | Always loaded |
|---|---|---|---|
| Built-in | src/plugins/ (in admin source), shipped with the build | ✓ — Plugins page on/off toggle | Only if enabled |
| Must-use (mu) | src/mu-plugins/ (in admin source) | ✗ — no toggle | Always |
| External | Uploaded ZIP, lives in /admin/plugins/<id>/ on Flexweg | ✓ — same as built-ins, plus uninstallable | Only if enabled and installed |
In production, built-in and external plugins are loaded the same way — both are pre-compiled ESM bundles fetched at boot via the runtime loader. The difference is only how they got there: built-ins ship with the admin's dist/, externals are uploaded by the user.
Must-use plugins are bundled directly into the admin's main JS chunk and run unconditionally — they're for behaviour the CMS can't meaningfully run without.
Built-in plugins
Five plugins ship with Flexweg CMS as opt-in (toggleable) extensions:
| Plugin | What it does |
|---|---|
| core-seo | Twitter Card meta tags + <meta name="generator"> hint |
| flexweg-sitemaps | XML sitemaps + sitemap-index + Google News + robots.txt |
| flexweg-rss | Site-wide RSS 2.0 feed + per-category feeds |
| flexweg-archives | Date-based archive pages (year / month / week) |
| flexweg-search | Static search index + client-side search modal |
Open Plugins in the sidebar to toggle them on/off. Each has its own settings page accessible from the Settings sidebar (tabs auto-appear when the plugin is enabled).
Must-use plugins
Six plugins are always-on, no toggle:
| Plugin | What it does |
|---|---|
| flexweg-favicon | Favicon generation from a single uploaded image (PNG, ICO, SVG, PWA manifest) |
| flexweg-blocks | First-party editor blocks: Custom HTML, Columns |
| flexweg-custom-code | Site-wide injection: head + body-end zones for analytics / chat / fonts |
| flexweg-embeds | Embed blocks: YouTube, Vimeo, Twitter/X, Spotify |
| flexweg-metrics | Storage + Firestore usage cards on the dashboard |
| flexweg-import | Bulk content import from markdown / WordPress XML |
These appear in the Plugins page under the Must-use tab, but with no enable / disable toggle — just a "Must-use" badge and the Configure / Learn more buttons.
What plugins can do
A plugin's manifest registers any of these via pluginApi:
Filters
Transform a value as it passes through. Multiple filters on the same hook chain together (priority-ordered).
api.addFilter<string>("page.head.extra", (head, baseProps) => {
return head + '<meta name="x-my-plugin" content="hello" />';
});
Common filter hooks:
post.markdown.before— modify Markdown before renderingpost.html.body— modify the post's rendered HTMLpost.template.props— modify props passed to the active theme's templatepage.head.extra— inject markup into<head>page.body.end— inject markup before</body>menu.json.resolved— mutate the resolved menu structure before upload
→ Hooks reference for the full list
Actions
Side-effects on lifecycle events. Multiple actions on the same hook all run.
api.addAction("publish.complete", (post, ctx) => {
console.log(`Published: ${post.id}`);
// Update some external system, regenerate a related file, etc.
});
Common action hooks:
publish.before/publish.after/publish.completepost.unpublishedpost.deleted
Editor blocks
Plugins can contribute blocks that show up in the post / page editor's / inserter. The flexweg-blocks mu-plugin contributes Custom HTML + Columns; flexweg-embeds contributes the four embed providers; themes contribute their layout blocks.
Dashboard cards
Plugins can register cards that appear on the home dashboard. flexweg-metrics contributes the Storage + Firestore cards.
Settings pages
Plugins can declare a settings page reachable at /admin/#/settings/plugin/<plugin-id>. The framework wraps the plugin's component in a route, merges saved config with defaults, and provides a save(next) callback.
Translations
Plugins can ship i18n bundles in their manifest's i18n field. The framework loads them into a dedicated i18next namespace named after the plugin id, so plugin UI calls useTranslation("<plugin-id>") to scope its keys without colliding with admin keys.
→ [Plugin i18n]
Toggling plugins
Open Plugins in the sidebar. The page has two tabs:
- Plugins — built-in + external plugins, with Enable / Disable buttons
- Must-use — must-use plugins, with a Must-use badge and no toggle
Clicking Disable on an enabled plugin:
- Updates
settings.enabledPlugins[id] = falsein Firestore - The settings subscription fires,
applyPluginRegistrationre-runs - The registry resets, all enabled plugins re-register, the disabled one is excluded
- Any dashboard card / hook handler / block from that plugin disappears immediately
Some side-effects don't auto-undo. For example, if core-seo was emitting Twitter Card tags on every published page, disabling it doesn't strip those tags from already-published HTML. New publishes will skip them. Use Themes → Regenerate site → All HTML to retroactively re-render every page without the disabled plugin's output.
Configuring plugins
If a plugin has a settings page, a tab appears in the Settings layout's tab strip when the plugin is enabled. The settings live at /admin/#/settings/plugin/<plugin-id>.
Plugin configs are stored at settings/site.pluginConfigs.<plugin-id> in Firestore. Each plugin's config is preserved when you toggle it off — re-enabling restores the saved config. Resetting requires explicitly clearing the field via Firestore (or via a "Reset" button if the plugin's settings page provides one).
Installing third-party plugins
To install an external plugin from a .zip:
- Plugins → Install plugin → pick the ZIP.
- The admin extracts client-side (via JSZip), validates the manifest, uploads files to
/admin/plugins/<id>/on Flexweg. - Updates the Firestore registry (
settings/externalRegistry). - Reloads. The plugin appears in the Plugins list alongside built-ins.
Uninstalling plugins
You can uninstall:
- External plugins (uploaded by you)
- Built-in plugins (the architecture treats them as externalised at build time, so they're uninstallable too)
Click the Uninstall button on the plugin's card (red, only visible for non-mu-plugins). Confirms, then:
- Removes the entry from the Firestore registry
- Deletes
/admin/plugins/<id>/from Flexweg - Reloads
If you later regret uninstalling a built-in, the Reinstall bundled defaults flow restores it. See Uninstalling.
Authoring custom plugins
If you want to build your own plugin, see:
- Creating plugins overview
- External plugin tutorial — ship as a ZIP
- [In-tree plugin tutorial] — develop in the admin source tree
- Hooks reference
Continue
- Built-in plugin reference — start with core-seo
- Must-use plugin reference — what's always-on
- Installing external plugins
- Uninstalling plugins
- Enabling and configuring — full toggle + settings flow