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 pollEach 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.
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 };
}
}