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