Skip to main content

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 — combine baseUrl + 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 typed MediaView shape 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 or Uint8Array to 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. instanceof checks 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 the SiteContext template prop without a full publish context.

Most plugins don't call these — they react to lifecycle actions which already provide a ctx.

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 via Intl.DateTimeFormat.
  • cn(...classes): string — Tailwind-style classname concat (truthy values only). Same as the popular clsx library.

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 (in global pagination mode) or partial views (in paginated mode). 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 via i18n.addResourceBundle(locale, namespace, bundle).
  • pickPublicLocale(siteLanguage): string — resolve settings.language to 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​