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 /

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).

TS
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 rendering
  • post.html.body — modify the post's rendered HTML
  • post.template.props — modify props passed to the active theme's template
  • page.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.

TS
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.complete
  • post.unpublished
  • post.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.

Block authoring

Dashboard cards

Plugins can register cards that appear on the home dashboard. flexweg-metrics contributes the Storage + Firestore cards.

Dashboard 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.

Plugin settings pages

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:

  1. Updates settings.enabledPlugins[id] = false in Firestore
  2. The settings subscription fires, applyPluginRegistration re-runs
  3. The registry resets, all enabled plugins re-register, the disabled one is excluded
  4. 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:

  1. Plugins → Install plugin → pick the ZIP.
  2. The admin extracts client-side (via JSZip), validates the manifest, uploads files to /admin/plugins/<id>/ on Flexweg.
  3. Updates the Firestore registry (settings/externalRegistry).
  4. Reloads. The plugin appears in the Plugins list alongside built-ins.

Installing external plugins

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:

  1. Removes the entry from the Firestore registry
  2. Deletes /admin/plugins/<id>/ from Flexweg
  3. 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:

Continue