⚠️ Work in progress — createCMS is pre-1.0 and not production-ready (not tested in production). Expect breaking changes.
createCMS
Reference

React

Block rendering, variant, and tracking exports for React.

These exports turn published content into React UI and wire up event tracking. The rendering and variant helpers come from the @createcms/core/react barrel; the tracking helpers come from the @createcms/core/react/tracking subpath (they are not re-exported by the barrel).

Rendering

createBlocksMap

Builds a typed map from block type to component. Each component's properties are typed from the collection definition.

function createBlocksMap(collection, components): BlocksMap;
const pageBlocks = createBlocksMap(collections.pages, {
  hero: ({ properties }) => <h1>{properties.headline}</h1>,
});

The returned BlocksMap also carries the collection definition it was built from (typed), so a single object can drive both rendering and an editor (components + schema/placement/grouping) — no separate collection handoff. BlocksRenderer only reads the component map; the bundled collection is there for editor tooling.

BlocksRenderer

Renders a block tree with a block map.

function BlocksRenderer(props: { blocks: BlocksMap; tree: BlockTreeNode }): ReactNode;

createBlocksRenderer

Shorthand that combines createBlocksMap and BlocksRenderer into one component.

function createBlocksRenderer(collection, components): (props: { tree: BlockTreeNode }) => ReactNode;

createContentRenderer

Like BlocksRenderer, but accepts a partial component map (provide components only for the block types you render). Both renderers resolve inline references; this one differs by the partial map.

function createContentRenderer(collection, components): (props: { tree: BlockTreeNode }) => ReactNode;

pickVariant

Server-side variant picker. Returns the tree of the variant matching branchId (or the control), with A/B metadata stripped.

function pickVariant(
  variants: readonly { branchId: string; tree: BlockTreeNode }[],
  branchId: string | null,
  controlBranchId?: string,
): BlockTreeNode | null;

extractBlockEvents

Extracts per-block-type event declarations from a collection's blocks.

function extractBlockEvents(
  blocks: Record<string, AnyBlockDefinition> | undefined,
): Record<string, Record<string, EventDeclaration>>;

useStore

Subscribes a component to a nanostores atom.

function useStore<T>(store: ReadableAtom<T>): T;

Tracking

Imported from @createcms/core/react/tracking (not the @createcms/core/react barrel). See Events & tracking for the model.

createTrackedBlocks

Builds a typed event firer for a collection. useTrackedBlock(blockType) returns a fire narrowed to that block's declared events, so an unknown event name or wrong-typed param is a compile error.

function createTrackedBlocks(collection): {
  useTrackedBlock(blockType): { fire: BlockEventFire };
};

TrackingRuntimeProvider

Supplies the dispatch function and ambient A/B context to every BlockTracker below it.

function TrackingRuntimeProvider(props: { runtime: TrackingRuntime; children: ReactNode }): ReactNode;

BlockTracker

Scopes a block's tracking identity (type, id, trackingId, events) to its children. The renderer adds this around functional blocks automatically.

function BlockTracker(props: BlockTrackingCtx & { children: ReactNode }): ReactNode;

useBlockTrackerRaw

The untyped escape hatch. Returns fire and fireInteraction to dispatch events from inside a tracked block (expectedBlockType is a dev-time label, not a type-narrowing selector). Prefer createTrackedBlocks for compile-time checking.

function useBlockTrackerRaw(expectedBlockType?: string): {
  fire: (name: string, params?: Record<string, string | number | boolean>) => void;
  fireInteraction: (
    name: string,
    interactionId: string,
    params?: Record<string, string | number | boolean>,
  ) => void;
};

TrackedForm

A <form> wrapper that fires an attempt event and a success event around a form action.

function TrackedForm(props: {
  attempt: string;
  success: string;
  action: (formData: FormData) => void | Promise<void>;
  children: ReactNode;
}): ReactNode;

Realtime

Imported from the @createcms/core/react/realtime subpath (which pulls in the optional @upstash/realtime peer — kept out of the main @createcms/core/react barrel). Both hooks ride one shared connection; see Realtime.

RealtimeProvider

Wraps your app in one shared realtime connection that every CMS realtime hook (useNotifications, the A/B useLiveResults) rides. Derives the endpoint + credentials from baseURL.

function RealtimeProvider(props: {
  children: ReactNode;
  baseURL: string; // same value passed to createCMSClient
  maxReconnectAttempts?: number;
}): ReactNode;

useNotifications

Subscribes the current user to their live notification stream over the RealtimeProvider connection and seeds from the listNotifications poll. De-dupes by id; the provider replays anything missed across a reconnect. Realtime-only — requires a mounted RealtimeProvider and notifications enabled (it type-requires client.notifications). client is your typed createCMSClient instance (no cast needed). userId is optional: pass it straight from your auth session (session?.user?.id) — while it's undefined the hook stays poll-only and connects once it resolves (the CMS has no current-user endpoint, so your app supplies the id).

function useNotifications<TActorUser>(
  client: CMSClient,
  options: {
    userId?: string;
    limit?: number;
    withUser?: true;
  },
): {
  notifications: NotificationListItem<TActorUser>[];
  unreadCount: number;
  isLive: boolean;
  refresh: () => void;
};

TActorUser is inferred from your typed client — a partial of your user table's row. With withUser: true, each notification's actorUser (the responsible user) is typed off your config, on both the seed poll and live pushes (the wire event carries actorUser, so the actor shows immediately — no second poll).

const { notifications } = useNotifications(cmsClient, {
  userId: session?.user?.id,
  withUser: true,
});
// notifications[0].actorUser?.name — typed, present live and on poll

Each item carries type, actorId, resourceType, resourceId, collection, and a free-form meta for deep-linking. See createNotificationRouter to turn those into typed links.

createNotificationRouter

Imported from @createcms/core/react (pure — no realtime peer, so it works even without realtime). Maps each notification type to a resolver that builds a deep link (and optional display data) from the item's fields. Each resolver receives meta narrowed to that type; the required fallback keeps routing total for custom and any future type.

function createNotificationRouter<TCms>(routes: {
  [type]?: (n: TypedNotification<type, TCms>) => NotificationRoute;
  fallback: (n: NotificationListItem) => NotificationRoute;
}): { resolve: (n: NotificationListItem) => NotificationRoute };

type NotificationRoute = {
  href?: string | null; // null = non-navigable
  label?: string;
  icon?: string;
  group?: string;
};

Pass your CMS type — createNotificationRouter<typeof cms>(…) — to pick up plugin-contributed notification types (and your actorUser shape) with full meta typing.

lib/notification-routes.ts
import { createNotificationRouter } from '@createcms/core/react';
import type { cms } from './cms';

export const notificationRouter = createNotificationRouter<typeof cms>({
  mention: (n) => ({ href: `/threads/${n.meta.threadId}#${n.meta.messageId}`, icon: 'at-sign' }),
  mergeRequestOpened: (n) => ({ href: `/merge-requests/${n.meta.mergeRequestId}` }),
  published: (n) => ({ href: `/${n.collection}/${n.resourceId}`, icon: 'rocket' }),
  // a type from a plugin's `notificationTypes` — `meta` typed off typeof cms:
  abTestWinner: (n) => ({ href: `/experiments/${n.meta.testId}` }),
  fallback: (n) => ({ href: n.resourceId ? `/${n.collection}/${n.resourceId}` : null }),
});

// at render: const { href, icon } = notificationRouter.resolve(notification);

The built-in core types (mention, comment, mergeRequest*, approval*, published, …) come with typed meta out of the box; plugins add their own types via the notificationTypes seam, inferred through typeof cms. To type a custom (or other app-only) notification raised without a plugin, augment NotificationMetaMap:

declare module '@createcms/core/react' {
  interface NotificationMetaMap {
    custom: { kind: 'invoice'; invoiceId: string };
  }
}

On this page