Runtime API reference
@flexweg/cms-runtime is the bridge between the admin and external plugins / themes. It exposes:
- React + family (so external bundles share the admin's React instance)
- i18next (so translation state is shared)
- The plugin API (filters, actions, block / card / regen target registration)
- Core helpers (slug, media, markdown, social icons, render)
- Flexweg Files API client (uploadFile, deleteFile, listFiles, …)
- Firestore CRUD helpers (fetchAllPosts, createPost, updateMedia, …)
- Publisher entry points (publishPost, buildPublishContext)
- Settings helpers (updatePluginConfig, updateThemeConfig)
- UI primitives (EntityCombobox, FontSelect, MediaPicker)
- Hooks + contexts (useCmsData, useAuth, useAllPosts)
This page is the complete export list with what each does. For practical guidance see Creating plugins and Creating themes.
API versioning
The runtime exposes two version constants:
import { FLEXWEG_API_VERSION, FLEXWEG_API_MIN_VERSION } from "@flexweg/cms-runtime";
// FLEXWEG_API_VERSION = "1.1.0"
// FLEXWEG_API_MIN_VERSION = "1.0.0"
External plugins / themes declare their apiVersion in manifest.json. The admin loads only bundles with MIN_API ≤ apiVersion ≤ CURRENT_API.
Versioning timeline
- 1.0.0 — first stable contract: pluginApi (filters/actions/blocks/cards), theme manifest shape, runtime React/i18n exports.
- 1.1.0 — expanded surface: core helpers (slug, media, markdown), Flexweg Files API, Firestore CRUD, publisher, menuPublisher, settings, toast. Backwards compatible — 1.0.0 bundles still load.
React family
import * as React from "@flexweg/cms-runtime"; // not actually how — see below
External bundles import React via the import-map. So they write:
import { useState, useEffect } from "react";
…and the admin's import-map redirects "react" → /admin/runtime/react.js which re-exports from window.__FLEXWEG_RUNTIME__.react. One React instance in the page.
Same for react/jsx-runtime, react-dom, react-dom/client, react-i18next.
Plugin API
import { pluginApi } from "@flexweg/cms-runtime";
// Inside register(api), `api === pluginApi`. Both forms work.
Five primitives — see Plugin manifest reference:
pluginApi.addFilter<T>(hook, fn, priority?);
pluginApi.addAction(hook, fn, priority?);
pluginApi.registerBlock(manifest);
pluginApi.registerDashboardCard(def);
pluginApi.registerRegenerationTarget(def);
External bundle authors typically don't touch pluginApi directly — the manifest's register(api) callback receives it.
External registration
For the boot loader to recognise an external bundle, it must call:
import { registerExternalPlugin, registerExternalTheme } from "@flexweg/cms-runtime";
// In the bundle's top-level code:
registerExternalPlugin(manifest);
// or
registerExternalTheme(manifest);
The example bundles in examples/external-plugin/ and examples/external-theme/ do this in their entry file's top scope. Most authors won't touch this directly — the example scaffolds handle it.
Core helpers
slug.ts
import {
slugify,
isValidSlug,
findAvailableSlug,
buildPostUrl,
buildTermUrl,
pathToPublicUrl,
detectPathCollision,
detectTermSlugCollision,
normalizeMediaSlug,
} from "@flexweg/cms-runtime";
slugify(text: string): string— convert arbitrary text to a slug (lowercase ASCII + dashes). Used by the admin when auto-generating slugs from titles.isValidSlug(slug: string): boolean— predicate.findAvailableSlug(baseSlug, posts, terms, type): string— append-2,-3, … until the slug is free.buildPostUrl(post, terms, settings): string— compute the post's path on Flexweg (e.g.news/launch.html).buildTermUrl(term, settings): string— compute a term's archive path.pathToPublicUrl(baseUrl, path): string— combinebaseUrl+ path → absolute URL.detectPathCollision(path, posts, pages, terms, excludeId?): { entityType, entityId } | null— check if a candidate path conflicts with any existing entity.detectTermSlugCollision(slug, terms, type, excludeId?): Term | null— same for term slugs.normalizeMediaSlug(filename: string): string— slugify + append 6-char hex suffix for collision-free media uploads.
media.ts
import { mediaToView, pickFormat, pickMediaUrl } from "@flexweg/cms-runtime";
mediaToView(media: Media | undefined): MediaView | undefined— convert raw Firestore media doc to the typedMediaViewshape themes consume.pickFormat(view: MediaView, name?: string): string— pick a variant URL with fallback chain (requested → default → largest → empty).pickMediaUrl(view: MediaView): string— pick a single best URL (legacy-friendly).
markdown.ts
import { markdownToPlainText, renderMarkdown } from "@flexweg/cms-runtime";
markdownToPlainText(md: string, maxLength?: number): string— strip Markdown formatting, return plain text. Used by RSS / search / SEO description generators.renderMarkdown(md: string): string— Markdown → safe HTML (marked + DOMPurify). Used by the publisher; rarely needed in plugin code (the publisher already runs it).
socialIcons.ts
import { SocialIcon, socialLabel } from "@flexweg/cms-runtime";
// React component:
<SocialIcon network="twitter" size={24} />
// Pure helper:
const label = socialLabel("twitter"); // → "Twitter / X"
postSort.ts
import { postSortMillis } from "@flexweg/cms-runtime";
const sorted = posts.sort((a, b) => postSortMillis(b) - postSortMillis(a));
// Sorts by publishedAt, with updatedAt + createdAt fallbacks.
render.tsx
import { renderPageToHtml } from "@flexweg/cms-runtime";
Used by plugins that render their own pages (flexweg-archives generates archive pages this way). Most plugins don't need it.
Flexweg Files API
import {
uploadFile,
deleteFile,
deleteFolder,
renameFile,
renameFolder,
createFolder,
getFile,
listFiles,
publicUrlFor,
fileToBase64,
getStorageLimits,
FlexwegApiError,
} from "@flexweg/cms-runtime";
uploadFile({ path, content, contentType? }): Promise<void>— upload a string orUint8Arrayto a Flexweg path.deleteFile(path): Promise<void>— 404 silent.deleteFolder(path): Promise<void>— recursive.renameFile(oldPath, newPath): Promise<void>renameFolder(oldPath, newPath): Promise<void>createFolder(path): Promise<void>getFile(path): Promise<string>— read file content.listFiles(path): Promise<ListResponse>— directory listing.publicUrlFor(path): string— compute the public URL for a Flexweg path.fileToBase64(file: File): Promise<string>— browser-side helper for upload payloads.getStorageLimits(): Promise<StorageLimitsResponse>— current usage + plan limit.FlexwegApiError— thrown class.instanceofchecks supported.
All functions go through the unified performRequest helper that handles toast notifications and HTTP error mapping. Don't call fetch against Flexweg directly.
Firestore CRUD
import {
fetchAllPosts,
createPost,
updatePost,
uploadMedia,
createTerm,
buildAuthorLookup,
} from "@flexweg/cms-runtime";
fetchAllPosts({ type? }): Promise<Post[]>— fetch every post / page (cached for 30s, dedup'd in-flight). Index-free query.createPost(data): Promise<string>— create a post; returns id.updatePost(id, patch): Promise<void>— partial update.uploadMedia(file, options): Promise<Media>— full image upload pipeline (variant generation + Firestore record).createTerm(data): Promise<string>— create a category / tag.buildAuthorLookup(): Promise<Map<string, AuthorView>>— resolve all users into the author-view shape themes use.
Publisher
import { publishPost, buildPublishContext, buildSiteContext } from "@flexweg/cms-runtime";
publishPost(postId, options?): Promise<void>— publish a post. Same flow as the admin's Publish button.buildPublishContext(): Promise<PublishContext>— load posts/pages/terms/media/settings into a context object. Used by plugins that need to render pages without committing them to Firestore.buildSiteContext(settings, terms): SiteContext— build theSiteContexttemplate prop without a full publish context.
Most plugins don't call these — they react to lifecycle actions which already provide a ctx.
Menu publisher
import { publishMenuJson } from "@flexweg/cms-runtime";
await publishMenuJson(settings, posts, pages, terms);
Re-uploads /menu.json. Used by flexweg-rss after settings save to immediately reflect footer changes without waiting for the Firestore subscription roundtrip.
Settings
import { updatePluginConfig, updateThemeConfig } from "@flexweg/cms-runtime";
await updatePluginConfig("my-plugin", { /* MyConfig */ });
await updateThemeConfig("my-theme", { /* MyConfig */ });
Both do nested-merge updates — partial saves don't wipe sibling fields.
Lib helpers
import { toast, sha256Hex, formatDateTime, cn } from "@flexweg/cms-runtime";
toast.success(msg)/toast.error(msg)/toast.info(msg)— global notification queue. Visible from any module (services, hooks, components).sha256Hex(content): Promise<string>— hash a string. Used by the publisher's hash-skip optimisation.formatDateTime(timestamp, locale): string— locale-aware date formatting viaIntl.DateTimeFormat.cn(...classes): string— Tailwind-style classname concat (truthy values only). Same as the popularclsxlibrary.
Hooks + contexts
import { useCmsData, useAuth, useAllPosts } from "@flexweg/cms-runtime";
function MyComponent() {
const { posts, pages, terms, media, settings } = useCmsData();
const { user } = useAuth();
const allPosts = useAllPosts("post");
// …
}
useCmsData()— live posts/pages/terms/media/settings (inglobalpagination mode) or partial views (inpaginatedmode). Always returns something safe to render against.useAuth()— current user (admin or editor) and auth state.useAllPosts(type?)— all posts of a type (live in global mode, snapshot in paginated mode).
i18n
import { i18n, pickPublicLocale, setActiveLocale } from "@flexweg/cms-runtime";
i18n— the i18next instance. Plugins can register namespaces directly viai18n.addResourceBundle(locale, namespace, bundle).pickPublicLocale(siteLanguage): string— resolvesettings.languageto one of the supported public locales.setActiveLocale(locale)— change the admin UI language.
UI components
import { EntityCombobox, FontSelect, MediaPicker } from "@flexweg/cms-runtime";
<EntityCombobox>— autocomplete combobox for picking posts / pages / terms in settings forms.<FontSelect>— Google Fonts picker matching the curated list themes use.<MediaPicker>— modal media library picker; returns selected media on close.
These are the same primitives admin pages use, exposed for parity.
Theme support
import { getActiveTheme, getCurrentPublishContext, logoPath, uploadThemeLogo, removeThemeLogo } from "@flexweg/cms-runtime";
Used by plugins that interact with theme assets (e.g. flexweg-archives reads the active theme's BaseLayout to render archive pages).
Continue
- Hooks reference
- Types reference
- Creating plugins → External bundle — how to consume this API from an external bundle