Skip to main content
Reference

@blinkk/root-cms

The Root.js CMS: the plugin, schemas, RootCMSClient and rich text rendering. This reference is generated from the package's TypeScript types and doc comments.

@blinkk/root-cms

import {…} from '@blinkk/root-cms';

Actioninterface

interface Action<T = any>
Members (5)
action: string;

The name of the action.

by?: string;

The user's email that performed the action (or "system").

timestamp: Timestamp;

Timestamp when the action occurred.

metadata?: T;

Metadata for the action.

links?: {
    label: string;
    url: string;
    target?: string;
}[];

Optional list of quick links to display in the UI.

applyProposalfunction

function applyProposal(cmsClient: RootCMSClient, proposal: Proposal, options?: ApplyProposalOptions): Promise<ApplyProposalResult>;

Resolves and (unless dryRun) applies every change in a proposal.

ApplyProposalOptionsinterface

interface ApplyProposalOptions
Members (4)
dryRun?: boolean;

Resolve and validate everything, but write nothing.

verifyBefore?: boolean;

Also check each op's recorded before against the value currently in the database and fail on drift. Off by default: before is documentation for the human reviewing the proposal, not a precondition.

modifiedBy?: string;

Attributed as the author of the writes.

skipValidation?: boolean;

Skip schema validation of the resulting doc fields.

ApplyProposalResulttype

type ApplyProposalResult = {
    ok: true;
    dryRun: boolean;
    changes: ResolvedChange[];
} | {
    ok: false;
    errors: ProposalApplyError[];
};

ArrayObjectinterface

interface ArrayObject
Members (1)
_array: string[];

Assetinterface

interface Asset

An entry in the CMS asset library, stored at Projects/${projectId}/Assets/${assetId}. Files and folders live in the same collection and are organized by their parent folder path.

Members (10)
id: string;

Unique id of the asset within the project.

type: 'file' | 'folder';
parent: string;

Parent folder path, e.g. '' (root) or 'marketing/q1'.

name: string;

Display name, e.g. hero.png for files or q1 for folders.

file?: UploadedFile;

The uploaded file data (same shape stored in image/file fields).

preserveFilename?: boolean;

For folders, whether uploaded files keep their original filename in the GCS object path ({hash}/{filename}.{ext} instead of {hash}.{ext}).

createdAt: Timestamp;
createdBy: string;
modifiedAt: Timestamp;
modifiedBy: string;

AssetFieldValuetype

type AssetFieldValue = UploadedFile & {
    assetId: string;
};

The value stored in a doc's image/file field when linked to an asset.

AssetSyncResultinterface

interface AssetSyncResult

Result of fanning out an asset update to docs that use it.

Members (2)
updatedDocIds: string[];

Doc ids that were updated.

failedDocIds: string[];

Doc ids that failed to update (eventual consistency; retry by re-syncing).

BatchRequestclass

class BatchRequest
Members (8)
cmsClient: RootCMSClient;
addDoc(docId: string): void;

Adds a doc to the batch request.

addDataSource(dataSourceId: string): void;

Adds a data source to the batch request.

addQuery(queryId: string, collectionId: string, queryOptions?: BatchRequestQueryOptions): void;

Adds a collection-based query to the batch request.

addTranslations(translationsId: string): void;

Adds a translations doc to the request.

fetch(): Promise<BatchResponse>;

Fetches data from the DB.

fetchContent(): Promise<BatchResponse>;

Fetches the docs, queries and data sources in the request, but not the translations. Use this with fetchTranslations() to defer loading translations until the locale is known, e.g.:

const req = cmsClient.createBatchRequest({mode, translate: true});
req.addDoc('Pages/index');
req.addTranslations('common');
const res = await req.fetchContent();
const locale = pickLocale(res.getDoc('Pages/index'));
await req.fetchTranslations(res, {locales: [locale]});
const translations = res.getTranslations(locale);
fetchTranslations(res: BatchResponse, options?: {
    locales?: string[];
}): Promise<BatchResponse>;

Fetches translations into res for the explicitly-added translations ids and, when options.translate is enabled, the docs fetched so far.

options.locales overrides the locales set on the request. Each locale is expanded through its fallback chain.

BatchRequestOptionsinterface

interface BatchRequestOptions
Members (3)
mode: 'draft' | 'published';
translate?: boolean;

Whether to automatically fetch translations for the docs retrieved in the request (including docs returned by queries).

locales?: string[];

Locales to fetch translations for. Each locale is expanded through its fallback chain (per the i18n.fallbacks config). Defaults to the locales configured in i18n.locales.

BatchRequestQueryinterface

interface BatchRequestQuery
Members (3)
queryId: string;
collectionId: string;
queryOptions?: BatchRequestQueryOptions;

BatchRequestQueryOptionsinterface

interface BatchRequestQueryOptions
Members (5)
offset?: number;
limit?: number;
orderBy?: string;
orderByDirection?: 'asc' | 'desc';
query?: (query: Query) => Query;

BatchResponseclass

class BatchResponse
Members (8)
docs: Record<string, Doc>;
queries: Record<string, Doc[]>;
dataSources: Record<string, DataSourceData>;
translations: Record<string, Record<string, TranslationsLocaleDoc>>;

Translations locale docs retrieved in the request, keyed by translations id then by locale. The ids are in precedence order: generic translations (e.g. "common") first, doc-specific translations last.

getDoc<Fields = any>(docId: string): Doc<Fields> | null;

Returns a doc added with addDoc(), or null if it doesn't exist. Slugs with slashes match either form, e.g. "Pages/foo/bar" or "Pages/foo--bar".

const header = res.getDoc<GlobalFields>('Global/header');
getQuery<Fields = any>(queryId: string): Array<Doc<Fields>>;

Returns the docs for a query added with addQuery(), or an empty array if the query has no results.

const posts = res.getQuery<BlogPostsFields>('posts');
getDataSource<T = any>(dataSourceId: string): DataSourceData<T> | null;

Returns a data source added with addDataSource(), or null if it doesn't exist.

const pricing = res.getDataSource<PricingRow[]>('pricing');
getTranslations(locale: string | string[]): LocaleTranslations;

Returns a map of translations for a given locale or locale fallbacks.

The input is either a single locale (e.g. "de"), which is expanded through the fallback chain configured in i18n.fallbacks, or an array of locales representing an explicit fallback chain, e.g. ["en-CA", "en-GB", "en"].

The returned value is a flat map of source string to translated string, e.g.: {"<source>": "<translation>"}

buildTranslationsDbPathfunction

function buildTranslationsDbPath(options: TranslationsDbPathOptions): string;

buildTranslationsLocaleDocDbPathfunction

function buildTranslationsLocaleDocDbPath(options: TranslationsLocaleDocDbPathOptions): string;

chunkArrayfunction

function chunkArray<T>(items: T[], chunkSize: number): T[][];

Splits an array into chunks of (up to) a given size.

clearReadCachesfunction

function clearReadCaches(): Promise<void>;

Clears every ReadCache in the process (including their stores). Called automatically when docs are published or unpublished through a RootCMSClient.

compareSortKeysfunction

function compareSortKeys(a: string, b: string): number;

Compares two sort keys by code point (the same ordering Firestore uses for string fields). Intentionally not localeCompare(), whose locale-aware collation can disagree with Firestore's byte ordering.

createRoutefunction

function createRoute(options: CreateRouteOptions): Route;

Utility for creating Root filesystem routes that are connected to a CMS doc.

CreateRouteOptionsinterface

interface CreateRouteOptions
Members (25)
collection: string;

Collection mapped to the route.

slugParam?: string;

Route param name used for the slug. Used for dynamic routes, e.g. [...slug].tsx. Defaults to "slug".

slugFormat?: string;

Format pattern for slugs that use multiple param values to form the slug, e.g. for experiments you might have something like:

Route: routes/ex/[experimentId]/[...page].tsx Doc ID: ExperimentPages/1234--about--foo URL path: /ex/1234/about/foo/

To grab the correct doc, use {slugFormat: '[experimentId]/[page]'}.

slug?: string;

Slug to use for the route. Used for non-dynamic, single-document routes.

getSlug?: (params: RouteParams) => string;

Custom slug resolution, e.g. for normalizing legacy URLs. Takes precedence over slug, slugFormat and slugParam, and applies to both SSR and SSG. Returning an empty string renders the 404 page.

createRoute({
  collection: 'Pages',
  slugParam: 'path',
  // Map `section/123` to the `section/123/index` doc.
  getSlug: (params) => {
    const slug = params.path || 'index';
    return /^section\/\d+$/.test(slug) ? `${slug}/index` : slug;
  },
});

NOTE: getStaticPaths() maps doc slugs to params with the default logic and doesn't use getSlug().

fetchData?: (context: RouteContext) => Record<string, Promise<any>>;

Callback function that returns a map of Promises that contain fetched data. Once the promise is resolved, the values are injected into page's props for rendering.

batchRequest?: (batchRequest: BatchRequest, context: RouteContext) => void | Promise<void>;

Hook for adding docs, queries, data sources or translations to the route's batch request. Everything added here is fetched in parallel with the route's doc, and translations for any docs or query results are loaded along with the route's translations (for the resolved locale only). The response is available as context.batchResponse.

createRoute({
  collection: 'Pages',
  batchRequest: (req) => {
    req.addDoc('Global/header');
    req.addQuery('posts', 'BlogPosts', {limit: 10});
  },
  preRenderHook: (props, ctx) => ({
    ...props,
    header: ctx.batchResponse!.getDoc('Global/header'),
    posts: ctx.batchResponse!.getQuery('posts'),
  }),
});
resolveDoc?: (ctx: ResolveDocContext) => RootCMSDoc | null | undefined | Promise<RootCMSDoc | null | undefined>;

Replaces the route's doc for a request, e.g. to serve a region-specific or flag-gated variant from another collection on the same URL. Called once the locale is resolved and before translations are loaded. If a doc is returned, it replaces the route's doc (as props.doc and context.doc), and its translations are loaded on top of the route doc's translations. Returning null or undefined keeps the route's doc.

createRoute({
  collection: 'Pages',
  resolveDoc: async (ctx) => {
    if (ctx.req?.get('x-country-code') !== 'DE') {
      return null;
    }
    return ctx.cmsClient.getDoc('PagesDE', ctx.slug, {mode: ctx.mode});
  },
});
notFoundHook?: (req: Request, res: Response) => void | Promise<void>;

Hook that's called when the doc is not found. If not provided, the default 404 handler will be called.

The response status code set by the hook (e.g. res.status(404)) is passed through to req.handlerContext.render().

preRenderHook?: (props: any, context: RouteContext) => any | Promise<any>;

Hook for amending any props values before being passed to the page component.

Set props.$redirect (and optionally props.$redirectCode) to redirect, or props.$statusCode to respond with a different status code (e.g. 403 or 410). A non-200 res.statusCode set by the hook is also passed through to the renderer.

setResponseHeaders?: (req: Request, res: Response) => void;

Hook for setting any response headers. A non-200 res.statusCode set by the hook is passed through to the renderer.

translations?: (context: RouteContext) => {
    tags?: string[];
};

Translations configuration. The returned tags are additional translations ids to load (e.g. "common" or "Global/strings"), on top of the route doc's own translations. Called after the doc is loaded, so context.doc and context.locale are available.

translateReferences?: boolean | 'transitive';

Whether to also load translations for the docs referenced by the route's doc (e.g. via schema.reference() fields), as tracked by the CMS dependency graph. Set to "transitive" to include docs referenced by those docs, recursively. Defaults to true (direct references only). Has no effect when the dependency graph or the v2 translations manager is disabled.

translate?: boolean | ((context: RouteContext) => boolean);

Whether to load translations for the route. Set to false (or a function that returns false) to skip all translation loading, e.g. for embeds or preview-only routes, in which case the page is rendered with {} translations. The function is called once the doc and locale are loaded. Defaults to true.

disableCacheControl?: boolean;

Sets Cache-Control header to private.

cacheControl?: string;

Cache-Control header for published pages. Defaults to public, max-age=15, s-maxage=30. Draft (preview) pages are always private.

notFoundCacheControl?: string;

Cache-Control header for 404 responses. Defaults to private, so that CDNs don't cache a 404 caused by a transient miss or a doc that isn't published yet. A notFoundHook can override it.

cache?: boolean | ReadCache;

Caches published CMS reads in memory, which saves a Firestore round trip on most page renders. Pass true to use a cache shared by every route (reads are cached for 60 seconds), or pass a ReadCache to configure it, e.g. {cache: new ReadCache({ttl: 30 * 1000})}.

Published content can take up to the cache's ttl to show up (plus any CDN cache time, see cacheControl). Draft (preview) requests are never cached. Ignored outside of production (i.e. unless NODE_ENV is "production"). Caches are cleared when docs are published or unpublished in the same process, or manually with clearReadCaches().

During root build, the shared cache (true) is backed by the filesystem (see FileSystemReadCacheStore), so reads are shared by the build's worker threads (--threads) and cached for the whole build.

createCmsClient?: (rootConfig: RootConfig, options: RootCMSClientOptions) => RootCMSClient;

Creates the route's RootCMSClient, e.g. to overlay a change proposal with {proposal}. The options (which include the read cache, if enabled) should be passed along to the client.

createRoute({
  collection: 'Pages',
  createCmsClient: (rootConfig, options) =>
    new RootCMSClient(rootConfig, {...options, proposal}),
});
ssg?: boolean;

Enables SSG mode for sites that serve on SCS or other static servers.

ssgMode?: DocMode;

Overrides the "mode" (draft vs published) for SSG builds. Primarily intended for testing prior to launches.

previewOnly?: boolean;

Whether the route should only be available in draft mode (i.e. with ?preview=true, see getMode).

getMode?: (req: RouteRequest) => DocMode | Promise<DocMode>;

Determines whether a request reads draft or published content. Defaults to the exported getMode(), which uses ?preview=true (and ?preview=true&mode=published). Only used in SSR mode (see ssgMode).

import {createRoute, getMode} from '@blinkk/root-cms';

createRoute({
  collection: 'Pages',
  getMode: (req) => {
    if (!isProd && req.get('x-force-draft') === 'true') {
      return 'draft';
    }
    return getMode(req);
  },
});
defaultLocale?: string;

Overrides the default locale for the route.

resolveLocale?: (ctx: ResolveLocaleContext) => string | ResolvedLocale | null | undefined | Promise<string | ResolvedLocale | null | undefined>;

Custom locale lookup, for sites with custom locale logic (e.g. mapping countries or URL params to locales). Returns the locale to render, which must be one of ctx.docLocales (matched case-insensitively). Returning null or a locale the doc isn't enabled for renders the 404 page (or calls notFoundHook). To render a locale the doc isn't enabled for, return {locale, force: true}.

Call ctx.resolveDefault() to fall back to the built-in logic:

createRoute({
  collection: 'Pages',
  resolveLocale: (ctx) => {
    if (ctx.isDefaultLocale && ctx.country === 'CH') {
      return ctx.docLocales.includes('de-CH') ? 'de-CH' : 'de';
    }
    return ctx.resolveDefault();
  },
});

Also called in SSG mode, with req: undefined, isDefaultLocale: false and routeLocale set to the locale from the build path, where ctx.resolveDefault() returns the build path's locale (if the doc is enabled for it).

CronScheduleTypetype

type CronScheduleType = 'interval' | 'daily' | 'weekly' | 'custom';

Data source sync schedule type.

- interval: sync every N minutes/hours/days (uses interval + unit). - daily / weekly / custom: sync on a specific cron schedule (uses expression + timezone). daily and weekly are UI presets that are stored as standard cron expressions; custom allows an arbitrary expression.

CronUnittype

type CronUnit = 'minutes' | 'hours' | 'days';

DataSourceinterface

interface DataSource
Members (15)
id: string;
description?: string;
type: 'http' | 'gsheet';
url: string;
dataFormat?: GsheetDataFormat;

Currently only used by gsheet. map returns the sheet as an array of objects keyed by the header row, grid returns the sheet as an array of arrays of strings (including the header row).

httpOptions?: {
    method: HttpMethod;
    headers?: Record<string, string>;
    body?: string;
};

Options for HTTP requests.

cron?: DataSourceCron;
createdAt: Timestamp;
createdBy: string;
syncedAt?: Timestamp;
syncedBy?: string;
publishedAt?: Timestamp;
publishedBy?: string;
archivedAt?: Timestamp;
archivedBy?: string;

DataSourceCroninterface

interface DataSourceCron
Members (7)
enabled: boolean;
schedule?: CronScheduleType;

Scheduling mode. Defaults to interval when unset (for backwards compatibility with data sources created before specific schedules were supported).

interval?: number;

Interval value, used when schedule is interval.

unit?: CronUnit;

Interval unit, used when schedule is interval.

expression?: string;

Standard 5-field cron expression, used when schedule is daily, weekly, or custom, e.g. 0 19 * * * for "every day at 7pm".

timezone?: string;

IANA timezone used to evaluate expression, e.g. America/New_York. Defaults to UTC when unset.

autoPublish?: boolean;

DataSourceDatainterface

interface DataSourceData<T = any>
Members (3)
dataSource: DataSource;
data: T;
headers?: string[];

Optional list of column headers (for gsheet sources).

DataSourceGridRowinterface

interface DataSourceGridRow

A single row of grid data as stored in Firestore. Rows are wrapped in a map because Firestore doesn't support arrays nested directly within arrays.

Members (1)
cells: string[];

DataSourceModetype

type DataSourceMode = 'draft' | 'published';

DependencyGraphclass

class DependencyGraph

An in-memory snapshot of the dependency graph for a single doc mode, providing forward (dependencies) and reverse (dependents) lookups.

Members (5)
readonly mode: DocMode;
readonly lastRun: number | null;

Millis timestamp of the last graph update, or null if never built.

readonly edges: Record<string, string[]>;

Map of doc id to the sorted list of doc ids it references.

getDependencies(docIds: string | string[], options?: GetDependenciesOptions): string[];

Returns the doc ids referenced by the given doc(s), i.e. the additional docs that need to be fetched when fetching the given docs. Resolves transitively by default (references of referenced docs are included, recursively; cycles are handled). The input doc ids themselves are excluded from the result.

getDependents(docIds: string | string[], options?: GetDependenciesOptions): string[];

Returns the doc ids that reference the given doc(s). Useful for finding which docs are affected when a doc changes (e.g. for cache invalidation). Resolves transitively by default.

DependencyGraphBatchOptype

type DependencyGraphBatchOp = (batch: WriteBatch) => void;

A single buffered write produced for a caller-managed Firestore batch.

DependencyGraphModeStatusinterface

interface DependencyGraphModeStatus
Members (2)
docsWithRefs: number;

Number of docs with at least one outgoing reference.

refs: number;

Total number of outgoing reference edges.

DependencyGraphRebuildResultinterface

interface DependencyGraphRebuildResult
Members (5)
forced: boolean;

True if a full (force) rebuild was performed.

skipped: boolean;

True if the rebuild short-circuited (no doc changes were found).

refDocCounts: Record<DocMode, number>;
refCounts: Record<DocMode, number>;
durationMs: number;

DependencyGraphServiceclass

class DependencyGraphService

Service for building, updating, and reading the persisted dependency graph.

Members (11)
isEnabled(): boolean;

Returns true if the dependency graph feature is enabled.

isCollectionTracked(collectionId: string): boolean;

Returns true if the given collection passes the include/exclude filter.

listCollectionIds(): Promise<string[]>;

Lists collection ids by globbing <rootDir>/collections/*.schema.ts, filtered by the include/exclude config.

hasChangesSince(meta: {
    lastRun: Timestamp;
    docCounts: Record<string, number>;
}): Promise<boolean>;

Returns true when any doc changed (created, updated, published, or deleted) since lastRun. Uses limit(1) probes for modifications and count() aggregations (compared against the doc counts captured at lastRun) for deletions, so the no-change case stays cheap.

runCronUpdate(options?: {
    minIntervalMs?: number;
}): Promise<DependencyGraphRebuildResult | null>;

Cron entrypoint: incrementally updates the graph when the feature is enabled, the min interval has elapsed, and any doc changed since the last run. Returns null when the update was skipped.

buildPublishBatchOps(docs: Array<{
    collection: string;
    slug: string;
    fields?: any;
}>): Promise<DependencyGraphBatchOp[]>;

Builds the writes that sync the published-mode edges for docs being published, so new references are queryable immediately (without waiting for the next cron tick). Callers weave the returned ops into the same batch as the publish writes. Returns an empty list when the feature is disabled, no tracked docs are affected, or the graph has never been built.

buildUnpublishBatchOps(docs: Array<{
    collection: string;
    slug: string;
}>): Promise<DependencyGraphBatchOp[]>;

Builds the writes that remove the published-mode edges for docs being unpublished. See buildPublishBatchOps().

syncPublishedDocs(docIds: string[]): Promise<{
    synced: number;
}>;

Re-reads the given docs from the Published collection and syncs their published-mode edges: existing docs get their refs re-extracted, missing docs (unpublished or deleted) get their edges removed. Used by the /cms/api/dependency_graph.sync_published endpoint, which the CMS UI calls after client-side publishes. Idempotent — the graph is synced to whatever is currently in the database.

rebuildGraph(opts?: {
    force?: boolean;
}): Promise<DependencyGraphRebuildResult>;

Orchestrates a graph rebuild (incremental by default; full when force: true).

getGraph(mode: DocMode): Promise<DependencyGraph>;

Returns the dependency graph for the given mode, loading it from Firestore if necessary. Loaded graphs are cached in-process and re-validated against _meta.lastRun on every call, so repeated calls only cost a single meta doc read until the graph changes.

getStatus(): Promise<DependencyGraphStatus>;

Lightweight metadata read for status endpoints.

DependencyGraphStatusinterface

interface DependencyGraphStatus
Members (4)
enabled: boolean;

Whether the feature is enabled via the cmsPlugin config.

lastRun: number | null;

Millis timestamp of the last graph update, or null if never built.

draft: DependencyGraphModeStatus;
published: DependencyGraphModeStatus;

Docinterface

interface Doc<Fields = any>
Members (5)
id: string;

The id of the doc, e.g. "Pages/foo-bar".

collection: string;

The collection id of the doc, e.g. "Pages".

slug: string;

The slug of the doc, e.g. "foo-bar".

sys: {
    createdAt: number;
    createdBy: string;
    modifiedAt: number;
    modifiedBy: string;
    firstPublishedAt?: number;
    firstPublishedBy?: string;
    publishedAt?: number;
    publishedBy?: string;
    publishingLocked: {
        lockedAt: string;
        lockedBy: string;
        reason: string;
        until?: Timestamp;
    };
    locales?: string[];
    assets?: string[];
    sortKey?: string;
};
fields: Fields;

DocCreateChangeinterface

interface DocCreateChange

Creation of a brand new doc with a complete set of fields.

Members (4)
kind: 'doc.create';
docId: string;
note?: string;
after: Record<string, any>;

DocDuplicateChangeinterface

interface DocDuplicateChange

Duplication of an existing doc to a new slug.

Members (4)
kind: 'doc.duplicate';
fromDocId: string;
toDocId: string;
note?: string;

DocEditChangeinterface

interface DocEditChange

Edits to an existing doc's draft fields.

Members (4)
kind: 'doc.edit';
docId: string;
note?: string;

Free-form rationale shown to the reviewer.

ops: ProposalOp[];

DocModetype

type DocMode = 'draft' | 'published';

extractRefDocIdsfunction

function extractRefDocIds(fieldsData: any): string[];

Extracts the doc ids referenced by a doc's fields data. Accepts the raw (marshaled) data as stored in Firestore or unmarshaled data — both plain arrays and _array objects are traversed. Returns a sorted, de-duped list.

FileSystemReadCacheStoreclass

class FileSystemReadCacheStore implements ReadCacheStore

A ReadCacheStore that writes cached reads to the filesystem, so that they can be shared across processes and worker threads, e.g. the workers of a root build --threads=N SSG build.

Concurrent misses for the same key are deduped across processes: the first process to miss claims the key with a lock file and fetches it, and the others wait for the result.

const cache = new ReadCache({
  store: new FileSystemReadCacheStore({dir: 'dist/.cache/root-cms'}),
});

During root build, createRoute({cache: true}) uses this store automatically (under dist/.cache/, which is deleted at the start and end of each build).

Members (4)
get(key: string): Promise<ReadCacheStoreEntry | undefined>;

Returns the entry for key, or undefined if it isn't stored.

set(key: string, entry: ReadCacheStoreEntry): Promise<void>;

Stores the entry for key.

delete(key: string): Promise<void>;

Removes the entry for key, e.g. when a fetch fails.

clear(): Promise<void>;

Removes all entries.

FileSystemReadCacheStoreOptionsinterface

interface FileSystemReadCacheStoreOptions

Options for a FileSystemReadCacheStore.

Members (3)
dir: string;

Directory where cached reads are written.

lockTimeout?: number;

How long (in ms) to wait for another process that's fetching the same key before fetching it again. Defaults to 30 seconds.

pollInterval?: number;

How often (in ms) to check whether another process has finished fetching a key. Defaults to 20ms.

generateKeyAfterfunction

function generateKeyAfter(a: string | null): string;

Returns a key that sorts after a (e.g. after the current max key to append an item to the end of the list). Pass null when the list has no keys yet.

generateKeyBetweenfunction

function generateKeyBetween(a: string | null, b: string | null): string;

Returns a key that sorts strictly between a and b (by code point). Pass a = null for "before everything" and b = null for "after everything"; generateKeyBetween(null, null) returns the initial key. Throws if a >= b or if either key is malformed.

generateNKeysBetweenfunction

function generateNKeysBetween(a: string | null, b: string | null, n: number): string[];

Returns n distinct keys in sorted order, all strictly between a and b (same semantics as generateKeyBetween). Used for bulk-assigning positions, e.g. when initializing the custom order of an existing collection.

getCmsPluginfunction

function getCmsPlugin(rootConfig: RootConfig): CMSPlugin;

GetCountOptionsinterface

interface GetCountOptions
Members (2)
mode: DocMode;
query?: (query: Query) => Query;

GetDependenciesOptionsinterface

interface GetDependenciesOptions
Members (1)
transitive?: boolean;

Whether to resolve dependencies transitively (i.e. also include the references of referenced docs, recursively). Defaults to true.

getDocfunction

function getDoc<T>(rootConfig: RootConfig, collectionId: string, slug: string, options: {
    mode: 'draft' | 'published';
}): Promise<T | null>;

Deprecated. Use RootCMSClient.getDoc() instead.

Retrieves a doc from Root.js CMS.

GetDocOptionsinterface

interface GetDocOptions
Members (1)
mode: DocMode;

Mode, either "draft" or "published".

getFirstQueryParamfunction

function getFirstQueryParam(req: Request, key: string): string | null;

Returns the first query param value in a given request.

For example, for a URL like /?foo=bar&foo=baz, calling getFirstQueryParam(req, 'foo') would return "bar".

getModefunction

function getMode(req: Request): Promise<DocMode>;

Returns the CMS document mode associated with the request.

getProposalDocIdsfunction

function getProposalDocIds(proposal: Proposal): string[];

Returns every doc id a proposal touches, in document order, deduped.

getProposedNewDocIdsfunction

function getProposedNewDocIds(proposal: Proposal): string[];

Returns the doc ids a proposal creates (via doc.create/doc.duplicate).

getReferenceDocIdfunction

function getReferenceDocId(value: any): string | null;

Returns the normalized doc id (<collection>/<slug>) when a value has the shape saved by the CMS reference fields ({id, collection, slug} with a consistent id), or null otherwise. Requiring the id to agree with the collection/slug pair avoids false positives on arbitrary user data that happens to use the same keys.

getTranslationsMigrationStatefunction

function getTranslationsMigrationState(cmsClient: RootCMSClient): Promise<TranslationsMigrationState | null>;

Returns the state of the v1 -> v2 translations migration, or null if it has never run for the project.

GsheetDataFormattype

type GsheetDataFormat = 'map' | 'grid';

Format used to store data synced from a csv-style data source.

- map: an array of objects keyed by the sheet's header row, e.g. [{header1: 'foo', header2: 'bar'}]. - grid: an array of arrays of strings, including the header row, e.g. [['header1', 'header2'], ['foo', 'bar']].

hashPasswordfunction

function hashPassword(password: string, options?: HashPasswordOptions): Promise<PasswordHash>;

Hashes a password with a freshly generated random salt.

const stored = await hashPassword('hunter2');
// => {algorithm: 'pbkdf2-sha256', iterations: 600000, salt: '...', hash: '...'}

HashPasswordOptionsinterface

interface HashPasswordOptions
Members (1)
iterations?: number;

The number of PBKDF2 iterations. Defaults to DEFAULT_ITERATIONS. Higher values are slower to compute and slower to brute force.

HttpMethodtype

type HttpMethod = 'GET' | 'POST';

ImportTranslationsFromV1Optionsinterface

interface ImportTranslationsFromV1Options

Options for importTranslationsFromV1().

Members (5)
publish?: boolean;

Publishes the imported translations in the same writes that save the drafts. Defaults to false (drafts only).

modifiedBy?: string;

Value saved to sys.modifiedBy (and sys.publishedBy, when publishing). Defaults to root-cms v1 migration.

getCollectionSchema?: (collectionId: string) => Promise<SchemaWithTypes | null>;

Loads a collection's schema. When provided, strings in doc-backed translations docs (e.g. Pages/index) that are no longer used by any version of the doc are pruned instead of imported. The v1 data isn't modified, so pruned strings can still be recovered from it.

writeCache?: TranslationsWriteCache;

Tracks locale docs that were already saved, so that a retry after a failed import skips them.

onProgress?: (progress: ImportTranslationsFromV1Progress) => void;

Called as the import progresses. When provided, the import doesn't log its own progress messages, so that the caller can render them instead.

ImportTranslationsFromV1Progressinterface

interface ImportTranslationsFromV1Progress

A progress update emitted by importTranslationsFromV1(). Steps run in order: fetch-docs (reading the CMS docs backing the v1 tags), prune (only when pruning unused strings) and write (saving locale docs).

Members (3)
step: 'fetch-docs' | 'prune' | 'write';
completed: number;
total: number;

ImportTranslationsFromV1Resultinterface

interface ImportTranslationsFromV1Result

Stats returned by importTranslationsFromV1().

Members (3)
ids: string[];

Translations doc ids that were created or updated.

stats: {
    numStrings: number;
    numDocs: number;
    numPrunedStrings: number;
    numSkippedStrings: number;
    numLocaleDocs: number;
    numCachedLocaleDocs: number;
};
locales: Record<Locale, {
    numDocs: number;
    numStrings: number;
}>;

Per-locale counts of the saved locale docs and their strings.

isCollectionTrackedfunction

function isCollectionTracked(filters: ResolvedFilters, collectionId: string): boolean;

isFirestoreAuthErrorfunction

function isFirestoreAuthError(err: unknown): boolean;

Returns true if an error looks like it was caused by missing or invalid Google Cloud credentials (e.g. no ADC, expired login, or no IAM access).

isPasswordHashfunction

function isPasswordHash(value: unknown): value is PasswordHash;

Returns whether a value has the shape of a PasswordHash.

isRichTextDatafunction

function isRichTextData(data: any): boolean;

Returns true if the data is a rich text data object.

ListActionsOptionsinterface

interface ListActionsOptions
Members (3)
action?: string;

Filter by a specific action. Defaults to all actions.

by?: string;

Filter by a specific user. Defaults to all users.

limit?: number;

Max number of actions to return. Defaults to 100.

listDocsfunction

function listDocs<T>(rootConfig: RootConfig, collectionId: string, options: {
    mode: 'draft' | 'published';
    offset?: number;
    limit?: number;
    orderBy?: string;
    orderByDirection?: 'asc' | 'desc';
    query?: (query: Query) => Query;
}): Promise<{
    docs: T[];
}>;

Deprecated. Use RootCMSClient.listDocs() instead.

Lists docs from a Root.js CMS collection.

ListDocsOptionsinterface

interface ListDocsOptions
Members (7)
mode: DocMode;
offset?: number;
limit?: number;
orderBy?: string;

DB field path to order results by, e.g. sys.createdAt. Defaults to slug. The default is skipped when a query fn is provided, since the query fn may supply its own ordering.

orderByDirection?: 'asc' | 'desc';
query?: (query: Query) => Query;
raw?: boolean;

Whether to fetch the "raw" version of the doc (for use in conjunction with setRawDoc()).

loadTranslationsfunction

function loadTranslations(rootConfig: RootConfig, options?: LoadTranslationsOptions): Promise<TranslationsMap>;

Deprecated. Use `RootCMSClient.loadTranslations()`.

loadTranslationsForLocalefunction

function loadTranslationsForLocale(rootConfig: RootConfig, locale: string, options?: LoadTranslationsOptions): Promise<LocaleTranslations>;

Deprecated. Use `RootCMSClient.loadTranslationsForLocale()`.

LoadTranslationsOptionsinterface

interface LoadTranslationsOptions
Members (1)
tags?: string[];

Localetype

type Locale = string;

LocaleFallbacksI18nConfiginterface

interface LocaleFallbacksI18nConfig

Utilities for resolving the locale fallback chain used by translations.

The i18n.fallbacks config in root.config.ts maps a locale to an ordered list of fallback locales. When a translation is missing for a locale, each fallback locale is checked (in order) before falling back to i18n.defaultLocale and finally the source string. For example:

i18n: {
  locales: ['en', 'en-GB', 'en-CA'],
  fallbacks: {
    'en-CA': ['en-GB'],
  },
}

With the config above, resolveLocaleFallbacks(i18n, 'en-CA') returns ['en-CA', 'en-GB', 'en']. Fallbacks are resolved recursively (breadth first), and all matching is case-insensitive.

Members (4)
locales?: string[];
defaultLocale?: string;
fallbacks?: Record<string, string[]> | ((locale: string) => string[]);

Map of locale to fallback locales, or a function that returns the fallback locales for a locale.

fallbackToLanguage?: boolean;

Falls back from a <lang>_<country> (or <lang>-<country>) locale to <lang>, e.g. ja_jp -> ja, after any configured fallbacks. When locales is set, only fallbacks to configured locales are added.

LocaleTranslationsinterface

interface LocaleTranslations

marshalArrayvariable

const marshalArray: typeof toArrayObject;

marshalDatafunction

function marshalData(data: any): any;

Walks the data tree and converts any array of objects into "array objects" for storage in firestore.

migrateV1TranslationsIfNeededfunction

function migrateV1TranslationsIfNeeded(cmsClient: RootCMSClient, options?: MigrateV1TranslationsOptions): Promise<MigrateV1TranslationsResult>;

Migrates v1 translations to the v2 TranslationsManager if the migration hasn't already run for this project. Safe to call on every dev server boot and build: after the first successful run, this costs a single Firestore read.

MigrateV1TranslationsOptionsinterface

interface MigrateV1TranslationsOptions
Members (4)
trigger?: 'build' | 'dev' | 'cli';

What triggered the migration (used for logging/state).

force?: boolean;

Re-runs the migration even if it already completed, e.g. to pick up v1 translations that were edited after the first run. Locale docs written by the migration are overwritten, so edits made in the v2 translations manager since then are lost for any string that is also in v1.

getCollectionSchema?: (collectionId: string) => Promise<SchemaWithTypes | null>;

Loads a collection's schema. When provided, strings that are no longer used by the docs they're tagged with are pruned instead of migrated.

cacheDir?: string;

Directory for the local write cache, used to resume a failed migration. When omitted, a retry re-saves everything.

MigrateV1TranslationsResultinterface

interface MigrateV1TranslationsResult
Members (2)
status: TranslationsMigrationStatus;
skipped: boolean;

True if the migration was skipped, either because it already completed or because another process holds the running lock.

MultiLocaleTranslationsMapinterface

interface MultiLocaleTranslationsMap

A translations map containing translations for multiple locales.

Example:

{
  "one": {"es": "uno", "fr": "un"},
  "two": {"es": "dos", "fr": "deux"}
}

normalizeDatafunction

function normalizeData(data: any): any;

Deprecated. Use `unmarshalData()` instead.

numDocsfunction

function numDocs(rootConfig: RootConfig, collectionId: string, options: {
    mode: 'draft' | 'published';
    query?: (query: Query) => Query;
}): Promise<number>;

Deprecated. Use RootCMSClient.getDocsCount() instead.

Returns the number of docs in a Root.js CMS collection.

parseDocIdfunction

function parseDocId(docId: string): {
    collection: string;
    slug: string;
};

Parses a docId (e.g. Pages/foo) and returns the collection and slug.

parseProposalfunction

function parseProposal(yamlText: string): ParseProposalResult;

Parses a YAML change proposal.

Syntax errors are reported by the YAML parser with real line numbers; structural errors come from the schema and are mapped back onto source lines by walking the parsed document.

ParseProposalResulttype

type ParseProposalResult = {
    ok: true;
    proposal: Proposal;
} | {
    ok: false;
    errors: ProposalParseError[];
};

PasswordHashinterface

interface PasswordHash

The value stored in the db for a password field. The salt and the derived hash are stored base64-encoded alongside the parameters used to compute them, so the algorithm or its cost can change without invalidating existing hashes.

Members (4)
algorithm: PasswordHashAlgorithm;

The key derivation algorithm used, e.g. pbkdf2-sha256.

iterations: number;

The number of PBKDF2 iterations used to derive the hash.

salt: string;

The random salt used to derive the hash, base64-encoded.

hash: string;

The derived hash, base64-encoded.

PasswordHashAlgorithmtype

type PasswordHashAlgorithm = 'pbkdf2-sha256';

The hashing algorithms supported by hashPassword.

Proposalinterface

interface Proposal

A parsed change proposal.

Members (8)
version: number;
id: string;

Stable id for the proposal, e.g. "2026-08-20-hero-refresh".

title?: string;
project?: string;

CMS project id the proposal targets. Advisory only.

generated?: string;

ISO-8601 timestamp of when the proposal was generated.

author?: string;
summary?: string;
changes: ProposalChange[];

PROPOSAL_CHANGE_KINDSvariable

const PROPOSAL_CHANGE_KINDS: readonly [
    'doc.edit',
    'doc.create',
    'doc.duplicate',
    'release.create',
    'release.update',
    'translations'
];

Change kinds a proposal can describe.

PROPOSAL_VERSIONvariable

const PROPOSAL_VERSION = 1;

The only proposal format version understood by this module.

ProposalApplyErrorinterface

interface ProposalApplyError

A problem that prevented a change from being applied.

Members (7)
changeIndex: number;

Zero-based index of the change in proposal.changes.

kind: string;
target: string;

The target of the change, e.g. a doc id or release id.

message: string;
path?: string;

Dotted field path, when the failure is tied to one.

expected?: string;
received?: any;

ProposalChangetype

type ProposalChange = DocEditChange | DocCreateChange | DocDuplicateChange | ReleaseChange | TranslationsChange;

ProposalChangeKindtype

type ProposalChangeKind = (typeof PROPOSAL_CHANGE_KINDS)[number];

ProposalOpinterface

interface ProposalOp

A single edit operation within a change. Mirrors DocEditOperation from core/ai-tools.ts, with after in place of value and an extra before that exists purely so a human reviewer can see what is being replaced.

Members (5)
op: 'set' | 'insert_item' | 'remove_item';
path: string;

Dotted path within the target object (no leading "fields." prefix).

index?: number;

Zero-based array index. Required for "remove_item", optional for "insert_item".

after?: any;

The value to write ("set") or insert ("insert_item").

before?: any;

The value as it stood when the proposal was written. Review-only: the applier ignores it unless verifyBefore is explicitly enabled.

ProposalOverlayclass

class ProposalOverlay

Indexes a change proposal so it can be applied cheaply to docs and translations as they are read.

Only the change kinds that affect rendering are handled: doc edits, doc creation, and translations. Release changes affect publishing rather than content, so they are ignored here and only take effect when the proposal is applied.

Members (9)
readonly proposal: Proposal;
applyToDoc(doc: Doc): Doc;

Applies any edit ops for doc.id to an unmarshaled doc.

hasNewDocsInCollection(collectionId: string): boolean;

True if the proposal creates any doc in collectionId.

countNewDocsInCollection(collectionId: string): number;

Number of docs the proposal creates in collectionId.

newDocsInCollection(collectionId: string, existingIds: Set<string>): Doc[];

Builds Doc objects for the docs the proposal creates in collectionId, skipping any that already exist in the db.

buildNewDoc(docId: string): Doc | null;

Synthesizes a Doc for a doc id the proposal creates, or null if the proposal does not create it. Duplicated docs are not resolved here (the source doc is not available synchronously), so they come back with empty fields.

newDocIds(): string[];

Every doc id the proposal creates, in proposal order.

allTranslations(): Record<string, Record<string, string>> | null;

All proposed translations, flattened to {source: {locale: string}}.

localeDocStrings(translationsId: string, locale: string): Record<string, {
    source: string;
    translation: string;
}> | null;

Returns proposed strings for one translations id and locale, shaped like the strings hash map stored on a translations locale doc, or null when the proposal has nothing for it.

ProposalParseErrorinterface

interface ProposalParseError

A structural or syntactic problem found while parsing a proposal.

Members (5)
line?: number;

1-based line number in the source YAML, when known.

path: string;

Dotted path to the offending value, e.g. "changes.0.ops.1.path".

message: string;
expected?: string;
received?: any;

publishScheduledDocsfunction

function publishScheduledDocs(rootConfig: RootConfig): Promise<any[]>;

Deprecated. Use RootCMSClient.publishScheduledDocs() instead.

Publishes scheduled docs.

ReadCacheclass

class ReadCache

An in-memory cache for published CMS reads, which saves a Firestore round trip on most page renders. Pass one to a RootCMSClient with new RootCMSClient(rootConfig, {cache}), or enable it for a route with createRoute({cache: true}).

Published content can take up to ttl to show up, so a cached client shouldn't be used to serve previews.

Members (3)
get<T>(key: string, fetch: () => Promise<T>, options?: ReadCacheReadOptions): Promise<T>;

Returns the cached value for key, or calls fetch() and caches its result. Concurrent reads for the same key share one fetch, and failed fetches aren't cached. Each caller gets its own copy of the value so that a render that mutates it can't affect other requests.

getMany<T>(keys: string[], fetchMany: (keys: string[]) => Promise<T[]>, options?: ReadCacheReadOptions): Promise<T[]>;

Like get(), but for multiple keys. The uncached keys are fetched with a single fetchMany() call, which must return their values in order.

clear(): Promise<void>;

Removes all cached reads, including the ones in the store.

ReadCacheOptionsinterface

interface ReadCacheOptions

Options for a ReadCache.

Members (3)
ttl?: number | ReadCacheTtl;

How long (in ms) a cached read is served before it's fetched again. Defaults to 60 seconds. Pass an object to set the ttl per read type, e.g. {default: 15_000, translations: 120_000}.

maxEntries?: number;

Max number of cached reads held in memory, so that arbitrary slugs can't grow the cache unbounded. The oldest reads are evicted first. Defaults to 1000.

store?: ReadCacheStore;

Secondary storage that's checked on an in-memory miss, e.g. a FileSystemReadCacheStore that's shared across processes.

ReadCacheReadOptionsinterface

interface ReadCacheReadOptions

Options for a single read.

Members (1)
type?: ReadCacheType;

The type of read, used to pick the ttl. Defaults to "doc".

ReadCacheStoreinterface

interface ReadCacheStore

A secondary storage layer for a ReadCache, e.g. for sharing cached reads between the worker threads of an SSG build (see FileSystemReadCacheStore). Reads are always cached in memory first, and the store is only checked on an in-memory miss.

Stored values are plain Firestore data, so a store that serializes them must preserve Timestamp values.

Members (4)
get(key: string): Promise<ReadCacheStoreEntry | undefined>;

Returns the entry for key, or undefined if it isn't stored.

set(key: string, entry: ReadCacheStoreEntry): Promise<void>;

Stores the entry for key.

delete(key: string): Promise<void>;

Removes the entry for key, e.g. when a fetch fails.

clear(): Promise<void>;

Removes all entries.

ReadCacheStoreEntryinterface

interface ReadCacheStoreEntry

A cached value stored in a ReadCacheStore.

Members (2)
expiresAt: number;

Timestamp (in ms) when the entry expires.

value: unknown;

The cached value.

ReadCacheTtltype

type ReadCacheTtl = {
    default?: number;
} & {
    [type in ReadCacheType]?: number;
};

Per-read-type ttls (in ms). Types without a ttl use default, which defaults to 60 seconds.

ReadCacheTypetype

type ReadCacheType = 'doc' | 'query' | 'translations' | 'dataSource';

The type of a cached read, used for per-type ttls (see ReadCacheTtl).

redirectWithQueryfunction

function redirectWithQuery(req: Request, res: Response, redirectCode: number, redirectPath: string): void;

Issues an HTTP redirect, preserving any query params from the original req.

Redirects are never cached (even 301s), so that a redirect that's later removed in the CMS takes effect immediately for browsers and CDNs.

Releaseinterface

interface Release
Members (10)
id: string;
description?: string;
docIds?: string[];
dataSourceIds?: string[];
createdAt?: Timestamp;
createdBy?: string;
scheduledAt?: Timestamp;
scheduledBy?: string;
publishedAt?: Timestamp;
publishedBy?: string;

ReleaseChangeinterface

interface ReleaseChange

Creation or modification of a release. A release is just an object with description / docIds / dataSourceIds, so it uses the same op vocabulary as a doc.

Members (4)
kind: 'release.create' | 'release.update';
releaseId: string;
note?: string;
ops: ProposalOp[];

renderTablefunction

function renderTable(headers: string[], rows: string[][]): string;

Renders a table with box-drawing borders. The first column is left-aligned and the rest are right-aligned (for numbers).

renderTable(['Locale', 'Docs'], [['es', '12']]);
// ┌────────┬──────┐
// │ Locale │ Docs │
// ├────────┼──────┤
// │ es     │   12 │
// └────────┴──────┘

requireCmsLoginfunction

function requireCmsLogin(options?: RequireLoginOptions): RequestMiddleware;

Express middleware that enforces Root CMS login on a custom route, without requiring the ?preview=true query param.

The Root CMS plugin's auth middleware runs before any user middleware and populates req.user on every request that carries a valid session cookie (see core/plugin.ts). This guard simply requires that req.user to exist: authenticated requests fall through to the next handler, while unauthenticated ones are redirected to the CMS login page (or answered with a 401 for API / XHR requests).

It must be used in a project that installs the root-cms plugin, since that plugin is what resolves and attaches req.user.

Usage in root.config.ts — protect everything under /admin:

import {requireCmsLogin} from '@blinkk/root-cms';

export default defineConfig({ server: { middlewares: [requireCmsLogin({match: '/admin'})], }, });

Or, from a plugin with direct server access, scope it with express:

server.use('/admin', requireCmsLogin());

RequireLoginOptionsinterface

interface RequireLoginOptions
Members (2)
match?: string | string[] | ((req: Request) => boolean);

Restricts the guard to matching request paths. Accepts a path prefix (e.g. /admin), a list of prefixes, or a predicate (req) => boolean. When omitted, login is enforced for every request the middleware sees.

This is useful when adding the middleware to root.config.ts's server.middlewares, which are mounted app-wide with no path scope.

loginUrl?: string;

The login page to redirect unauthenticated users to. Defaults to /cms/login. The original URL is added as a continue query param so the user is returned to the requested page after signing in.

ResolvedChangeinterface

interface ResolvedChange

One resolved change, ready to be written (or reported by a dry run).

Members (5)
changeIndex: number;
kind: string;
target: string;
summary: string;

Short human-readable description of what will be written.

paths?: string[];

Field paths the change touches, for doc and release changes.

resolveDependencyGraphConfigfunction

function resolveDependencyGraphConfig(option?: boolean | CMSDependencyGraphConfig): CMSDependencyGraphConfig | null;

Normalizes the dependencyGraph cmsPlugin option. Returns the config object when the feature is enabled, or null when disabled.

resolveDependencyGraphFiltersfunction

function resolveDependencyGraphFilters(config?: CMSDependencyGraphConfig): ResolvedFilters;

ResolvedLocaleinterface

interface ResolvedLocale

A locale returned by the resolveLocale() option.

Members (2)
locale: string;

The locale to render.

force?: boolean;

Renders the locale even if the doc isn't enabled for it (i.e. it isn't one of ctx.docLocales). The locale is used as-is.

ResolveDocContexttype

type ResolveDocContext = RouteContext & {
    doc: RootCMSDoc;
    locale: string;
    data: Record<string, any>;
};

Context passed to the resolveDoc() option.

ResolveLocaleContextinterface

interface ResolveLocaleContext

Context passed to the resolveLocale() option.

Members (9)
req?: RouteRequest;

HTTP request object. Only available in SSR mode.

routeContext: RouteContext;

The route context, including the loaded doc.

doc: RootCMSDoc;

The route's CMS doc.

docLocales: string[];

Locales the doc is enabled for, in the doc's casing.

routeLocale: string;

The locale from the URL, e.g. "de" for /de/about/. For the route's default-locale URL (e.g. /about/), this is the site's default locale. In SSG mode, this is the locale from the build path (params.$locale).

isDefaultLocale: boolean;

Whether the request is for the route's default-locale URL (i.e. the URL has no locale prefix), in which case the locale is typically determined from the user's request (?hl=, accept-language, country, etc.). Always false in SSG mode.

country: string;

Upper-cased country code from ?gl= or the x-country-code / x-appengine-country headers, or an empty string if unknown (always empty in SSG mode).

hl: string | null;

Value of the ?hl= query param, or null if not set (or in SSG mode).

resolveDefault: () => string | null;

Runs the built-in locale resolution. Useful for customizing only some requests and deferring to the default behavior for the rest.

resolveLocaleFallbacksfunction

function resolveLocaleFallbacks(i18nConfig: LocaleFallbacksI18nConfig | undefined, locale: string): string[];

Resolves the ordered locale fallback chain for a locale, starting with the locale itself and ending with the default locale. Fallback chains are followed recursively (breadth first) with cycle protection, and locale keys are matched case-insensitively while preserving the configured casing of each fallback value.

resolvePromisesMapfunction

function resolvePromisesMap(promisesMap: Record<string, Promise<any>>): Promise<Record<string, any>>;

RootCMSClientclass

class RootCMSClient
Members (65)
readonly rootConfig: RootConfig;
readonly cmsPlugin: CMSPlugin;
readonly projectId: string;
readonly app: App;
readonly db: Firestore;
readonly proposal?: ProposalOverlay;

An unapplied change proposal overlaid on top of everything this client reads. Lets a developer preview proposed content against a running site before accepting it. Reads only — writes never fold the proposal in.

readonly user: string;

Identity that writes are attributed to by default.

readonly logActions: boolean;

Whether doc writes are recorded in the action logs.

getAllData(refs: DocumentReference[], options: {
    mode: DocMode;
    cacheType?: ReadCacheType;
}): Promise<any[]>;

Fetches the data for a list of doc refs, in order, with undefined for docs that don't exist. Published reads are cached per doc when the client has a cache, and the uncached docs are fetched in a single getAll().

getQueryData(query: Query, options: {
    mode: DocMode;
    cacheKey?: unknown[] | null;
    cacheType?: ReadCacheType;
}): Promise<any[]>;

Runs a Firestore query and returns the data of each result. Published reads are cached when the client has a cache and a cacheKey is provided, i.e. the query is fully described by the key.

overlayDoc<T = any>(doc: T): T;

Applies the proposal overlay (if any) to an unmarshaled doc.

Called from every non-raw read path. getRawDoc() and listDocs({raw: true}) deliberately skip this: those exist to feed setRawDoc(), and folding proposed content into them would leak it into a write.

proposedDocsInCollection(collectionId: string, existingIds: Set<string>): Doc<any>[];

Returns docs the proposal creates in a collection that do not exist in the database yet, so list and count reads can include them.

getDoc<Fields = any>(collectionId: string, slug: string, options: GetDocOptions): Promise<Doc<Fields> | null>;

Retrieves doc data from Root.js CMS.

getRawDoc(collectionId: string, slug: string, options: GetDocOptions): Promise<any | null>;

Retrieves raw doc data as stored in the database. Only use this if you know what you are doing.

dbCollectionDocsPath(collectionId: string, options: {
    mode: 'draft' | 'published';
}): string;

Firestore path for a collection.

dbDocPath(collectionId: string, slug: string, options: {
    mode: 'draft' | 'published';
}): string;

Firestore path for a content doc.

dbDocRef(collectionId: string, slug: string, options: {
    mode: 'draft' | 'published';
}): DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;

Firestore doc ref for a content doc.

getCollection(collectionId: string): Promise<Collection | null>;

Returns a collection's schema definition as defined in /collections/<id>.schema.ts.

saveDraftData(docId: string, fieldsData: any, options?: SaveDraftOptions): Promise<void>;

Saves draft data to a doc.

Note: this saves data to the "fields" attr of the draft doc. If you need to modify the sys-level attributes of the doc, use setRawDoc().

updateDraftData(docId: string, path: string, fieldValue: any, options?: UpdateDraftOptions): Promise<void>;

Updates a specific field path in a draft doc.

This allows partial updates to nested fields without replacing the entire document. For example: updateDraftData('Pages/home', 'hero.title', 'New Title')

setRawDoc(collectionId: string, slug: string, data: any, options: SetDocOptions): Promise<void>;

Sets the raw document data directly in Firestore.

CAUTION Prefer using saveDraftData('Pages/foo', data) in most cases. Only use this method if you need to manipulate system-level (sys) fields directly or if you're implementing low-level data operations.

## Validation & Normalization

This method automatically validates and normalizes sys fields to prevent data integrity issues that can cause runtime errors like "e.toMillis is not a function".

### Timestamp Fields (auto-converted) The following fields accept multiple formats and are automatically converted to Firestore Timestamp objects: - sys.createdAt - sys.modifiedAt - sys.publishedAt - sys.firstPublishedAt

Accepted formats: - Firestore Timestamp object (unchanged) - number - Interpreted as milliseconds since epoch, converted to Timestamp - Date object - Converted to Timestamp

### Required Fields (auto-populated with defaults if missing) - sys.createdAt - Defaults to current time if not provided - sys.modifiedAt - Defaults to current time if not provided - sys.createdBy - Defaults to the client's user if not provided - sys.modifiedBy - Defaults to the client's user if not provided - sys.locales - Defaults to ['en'] if not provided

### Optional Fields (validated if present) - sys.publishedBy - String identifier - sys.firstPublishedBy - String identifier - sys.publishingLocked - Object with optional until Timestamp

### Computed Fields - sys.assets - Recomputed from the asset library ids embedded in fields, so that asset updates fan out to the doc

### Document Identity The id, collection, and slug fields are always set to match the provided parameters, overwriting any existing values to prevent data inconsistencies.

// Minimal example - sys fields use defaults
await client.setRawDoc('Pages', 'home', {
  sys: {},  // All sys fields will be auto-populated with defaults
  fields: {
    title: 'Home Page'
  }
}, { mode: 'draft' });

// Full example - with number timestamps (auto-converted)
await client.setRawDoc('Pages', 'home', {
  id: 'Pages/home',
  collection: 'Pages',
  slug: 'home',
  sys: {
    createdAt: Date.now(),  // Auto-converted to Timestamp.
    createdBy: 'user@example.com',
    modifiedAt: Date.now(),  // Auto-converted to Timestamp.
    modifiedBy: 'user@example.com',
    locales: ['en', 'es']
  },
  fields: {
    title: 'Home Page'
  }
}, { mode: 'draft' });
listDocs<T>(collectionId: string, options: ListDocsOptions): Promise<{
    docs: T[];
}>;

Lists docs from a Root.js CMS collection.

getDocsCount(collectionId: string, options: GetCountOptions): Promise<number>;

Returns the number of docs in a Root.js CMS collection.

publishDocs(docIds: string[], options?: {
    publishedBy: string;
    batch?: WriteBatch;
    releaseId?: string;
}): Promise<any[]>;

Batch publishes a set of docs by id.

unpublishDocs(docIds: string[], options?: {
    unpublishedBy?: string;
    batch?: WriteBatch;
}): Promise<any[]>;

Batch unpublishes a set of docs by id.

publishScheduledDocs(): Promise<any[]>;

Publishes scheduled docs.

uploadAsset(file: string | Uint8Array, options?: UploadAssetOptions): Promise<UploadAssetResult>;

Uploads a file to the asset library, from a local file path or raw bytes (which require options.filename). The file is uploaded to the project's GCS bucket and, for images when gci is enabled, registered with the Google Cloud Image service. If the destination folder already contains a file with the same name (or options.assetId is set), the existing asset's file is replaced and the change is fanned out to every draft doc that uses the asset.

Example: uploadAsset('./hero.png', {folder: 'marketing', alt: 'Hero image'})

getAsset(assetId: string): Promise<Asset | null>;

Retrieves an asset library file or folder by id, or null if it does not exist.

listAssets(folder?: string): Promise<Asset[]>;

Lists the assets (folders first, then files) directly within an asset library folder, e.g. marketing/q1. Defaults to the root folder.

findDocsUsingAsset(assetId: string): Promise<string[]>;

Returns the ids of the draft docs that embed an asset library file (e.g. ["Pages/home"]), found via each doc's sys.assets index.

syncAssetToDocs(assetId: string, options?: SyncAssetToDocsOptions): Promise<AssetSyncResult>;

Updates the embedded copies of an asset's file data in every draft doc that uses the asset. Called automatically by uploadAsset() when a file is replaced; call it directly to retry docs that failed to update.

dbReleasePath(releaseId: string): string;

Returns the Firestore path for a release doc.

getRelease(releaseId: string): Promise<Release | null>;

Retrieves a release by id, or null if it does not exist.

listReleases(): Promise<Release[]>;

Lists all releases, most recently created first.

setRelease(releaseId: string, release: Partial<Release>, options?: {
    modifiedBy?: string;
}): Promise<void>;

Creates or updates a release.

Only the fields describing the release's contents are written (description, docIds, dataSourceIds); scheduling and publishing state is left untouched, so this can never publish or schedule a release as a side effect.

publishScheduledReleases(): Promise<void>;

Publishes docs in scheduled releases.

syncScheduledDataSources(): Promise<void>;

Syncs data sources that have cron scheduling enabled and are due for sync.

testPublishingLocked(doc: Doc): boolean;

Checks if a doc is currently "locked" for publishing.

getTranslationsManager(): TranslationsManager;

Returns a TranslationsManager object for managing translations.

To get translations:

await tm.loadTranslations({
  ids: ['Global/strings', 'Pages/index'],
  locales: ['es'],
});

NOTE: The TranslationsManager is a v2 feature that will eventually replace the v1 translations system.

isV2TranslationsEnabled(): boolean;

Returns true if the v2 TranslationsManager is enabled via the experiments.v2TranslationsManager plugin config flag.

loadTranslations(options?: LoadTranslationsOptions): Promise<TranslationsMap>;

Loads translations saved in the translations collection, optionally filtered by tag.

Returns a map like:

{
  "<hash>": {"source": "Hello", "es": "Hola", "fr": "Bonjour"},
}
saveTranslations(translations: {
    [source: string]: {
        [locale: string]: string;
    };
}, tags?: string[]): Promise<void>;

Saves a map of translations, e.g.:

await client.saveTranslations({
  "Hello": {"es": "Hola", "fr": "Bonjour"},
});
verifyPassword(stored: PasswordHash | null | undefined, password: string): Promise<boolean>;

Verifies a candidate password against the hashed value stored by a password field. Returns false when the field is empty or malformed.

const doc = await cmsClient.getDoc('Members', 'alice', {mode: 'published'});
const ok = await cmsClient.verifyPassword(doc?.fields.password, input);
getTranslationKey(source: string): string;

Returns the "key" used for a translation as stored in the db. Translations are stored under Projects/<project id>/Translations/<sha1 hash>.

normalizeString(str: string): string;

Cleans a string that's used for translations. Performs the following: - Removes any leading/trailing whitespace - Removes spaces at the end of any line

loadTranslationsForLocale(locale: string, options?: LoadTranslationsOptions): Promise<LocaleTranslations>;

Loads translations for a particular locale.

The locale is expanded through the fallback chain configured in i18n.fallbacks (ending with i18n.defaultLocale), so a string missing a translation for the locale falls through to its fallbacks before the source string is used.

Returns a map like:

{
  "Hello": "Bonjour",
}
getDataSource(dataSourceId: string): Promise<DataSource | null>;

Returns a data source configuration object.

archiveDataSource(dataSourceId: string, options?: {
    archivedBy?: string;
}): Promise<void>;

Archives a data source. Archived data sources cannot be synced or published.

unarchiveDataSource(dataSourceId: string): Promise<void>;

Unarchives a data source.

syncDataSource(dataSourceId: string, options?: {
    syncedBy?: string;
}): Promise<void>;

Syncs a data source to draft state.

publishDataSource(dataSourceId: string, options?: {
    publishedBy?: string;
}): Promise<void>;
unpublishDataSource(dataSourceId: string): Promise<void>;

Unpublishes a data source. Removes the publishedAt/publishedBy metadata from the DataSource doc and deletes the Data/published doc.

publishDataSources(dataSourceIds: string[], options?: {
    publishedBy: string;
    batch?: WriteBatch;
    commitBatch?: boolean;
}): Promise<void>;
getFromDataSource<T = any>(dataSourceId: string, options?: {
    mode?: 'draft' | 'published';
}): Promise<DataSourceData<T> | null>;

Fetches data from a data source.

dbDataSourceDataPath(dataSourceId: string, options: {
    mode: 'draft' | 'published';
}): string;

Firestore path for a datasource data.

dbDataSourceDataRef(dataSourceId: string, options: {
    mode: 'draft' | 'published';
}): DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;

Firestore doc ref for a datasource data.

getUserAcl(email: string): Promise<{
    exists: boolean;
    role: UserRole | null;
}>;

Looks up the user's entry in the project's ACL with a single Firestore read. exists is true if the user (or their domain's wildcard entry, e.g. *@example.com) is in the ACL; role may be null for entries without an assigned role.

getUserRole(email: string): Promise<UserRole | null>;

Gets the user's role from the project's ACL.

userExistsInAcl(email: string): Promise<boolean>;

Verifies user exists in the ACL list.

listActions(options?: ListActionsOptions): Promise<Action[]>;

Lists action logs from the database.

logAction(action: string, options?: {
    by?: string;
    metadata?: any;
    links?: {
        label: string;
        url: string;
        target?: string;
    }[];
}): Promise<void>;
sendEmail(options: SendEmailOptions): Promise<string>;

Queues an email in the Projects/${projectId}/Emails collection in firestore, which is processed by the Root.js email service (apps/root-services). Returns the id of the queued email doc.

Delivery is asynchronous: the email service sends pending emails when its /_/send_emails endpoint is called (typically via a cron). Pass the emailService option to notify the service right away.

getDependencyGraph(options: {
    mode: DocMode;
}): Promise<DependencyGraph>;

Returns the dependency graph for a given mode, which tracks reference field usages between docs. Requires the dependencyGraph option to be enabled on the cmsPlugin config (the graph is kept up to date by the CMS cron job).

Example:

const graph = await cmsClient.getDependencyGraph({mode: 'published'});
const depIds = graph.getDependencies(['Pages/index']);
// => ['Authors/alice', 'BlogPosts/hello-world', ...]
getDocDependencies(docIds: string | string[], options: {
    mode: DocMode;
    transitive?: boolean;
}): Promise<string[]>;

Returns the ids of the docs referenced by the given doc(s), i.e. the additional docs that need to be fetched when fetching the given docs. Dependencies are resolved transitively by default (pass transitive: false for direct references only).

Requires the dependencyGraph option to be enabled on the cmsPlugin config.

Example:

const depIds = await cmsClient.getDocDependencies(['Pages/index'], {
  mode: 'published',
});
const req = cmsClient.createBatchRequest({mode: 'published'});
req.addDoc('Pages/index');
depIds.forEach((docId) => req.addDoc(docId));
const res = await req.fetch();
createBatchRequest(options: BatchRequestOptions): BatchRequest;

Creates a batch request that is capable of fetching one or more docs, corresponding translations, and dataSources.

RootCMSClientOptionsinterface

interface RootCMSClientOptions

Options for constructing a RootCMSClient.

Members (4)
proposal?: Proposal;

An unapplied change proposal to overlay on top of every read. Use this to preview proposed content on a running site before accepting it.

user?: string;

Email (or other identifier) that writes are attributed to when a method is not passed one explicitly, e.g. sys.modifiedBy and action log entries. Defaults to root-cms-client.

logActions?: boolean;

Whether doc writes (saveDraftData(), updateDraftData(), setRawDoc(), publishDocs() and unpublishDocs()) record an entry in the project's action logs, the same way edits made from the CMS UI do. Defaults to true. Set to false to skip logging, e.g. for a one-off bulk migration.

cache?: ReadCache;

Caches published reads in memory (see ReadCache). Covers getDoc(), listDocs() (unless a query fn is used), loadTranslations() and batch requests. Draft reads are never cached, but loadTranslations() has no mode, so don't use a cached client to serve previews.

RootCMSDocinterface

interface RootCMSDoc<Fields = any>
Members (5)
id: string;

The id of the doc, e.g. "Pages/foo-bar".

collection: string;

The collection id of the doc, e.g. "Pages".

slug: string;

The slug of the doc, e.g. "foo-bar".

sys: {
    createdAt: number;
    createdBy: string;
    modifiedAt: number;
    modifiedBy: string;
    firstPublishedAt?: number;
    firstPublishedBy?: string;
    publishedAt?: number;
    publishedBy?: string;
    locales?: string[];
    sortKey?: string;
};

System-level metadata.

fields?: Fields;

User-entered field values from the CMS.

Routeinterface

interface Route
Members (3)
handle: (req: RouteRequest, res: RouteResponse) => Promise<void>;

SSR handler.

getStaticProps?: GetStaticProps;

SSG handler props handler, enabled with {ssg: true}.

getStaticPaths?: GetStaticPaths;

SSG path params provider, enabled with {ssg: true}.

RouteContextinterface

interface RouteContext
Members (8)
req?: RouteRequest;

HTTP request object. Only available in SSR mode.

slug: string;

The slug of the page being requested.

mode: DocMode;

Doc publishing mode.

cmsClient: RootCMSClient;

Client for interacting with Root CMS data.

params: RouteParams;

URL param map from filesystem routing.

doc?: RootCMSDoc;

The route's CMS doc. Set once the doc is loaded, so it's available to translations() and preRenderHook() but not fetchData().

locale?: string;

The locale being rendered. Set once the locale is resolved, so it's available to translations() and preRenderHook() but not fetchData().

batchResponse?: BatchResponse;

The response of the route's batch request, containing anything added via the batchRequest() option. Set once loaded, so it's available to translations() and preRenderHook() but not fetchData().

RouteRequesttype

type RouteRequest = Request & {
    rootConfig: RootConfig;
    cmsClient: RootCMSClient;
};

RouteResponsetype

type RouteResponse = Response;

SaveDraftOptionsinterface

interface SaveDraftOptions
Members (3)
locales?: string[];

Locales to enable.

modifiedBy?: string;

Email of user modifying the doc. If blank, defaults to the client's user.

validate?: boolean;

Whether to validate fieldsData against the collection schema before saving. If validation fails, an error will be thrown with details about the validation errors.

schemanamespace

namespace schema
Exports (58)
function array(field: Omit<ArrayField, 'type'>): ArrayField;
type ArrayField = CommonFieldProps & {
    type: 'array';
    default?: any[];
    itemDefault?: Record<string, any>;
    preview?: string | string[];
    of: ObjectLikeField;
    buttonLabel?: string;
    defaultOpen?: boolean;
};
type AspectRatio = string | number;

An aspect ratio, expressed either as a string like '16:9', '16/9' or '16x9', or as a number like 1.7778 (width divided by height).

function boolean(field: Omit<BooleanField, 'type'>): BooleanField;
type BooleanField = CommonFieldProps & {
    type: 'boolean';
    default?: boolean;
    checkboxLabel?: string;
};
const collection: typeof defineCollection;
type Collection = SchemaWithTypes & {
    id: string;
    group?: string;
    domain?: string;
    url?: string;
    previewUrl?: string;
    preview?: {
        title?: string | string[];
        image?: string | string[];
        defaultImage?: {
            src: string;
        };
        fit?: 'cover' | 'contain';
    };
    slugRegex?: string;
    autoSlug?: string;
    autolock?: boolean;
    autolockReason?: string;
    sortOptions?: Array<{
        id: string;
        label: string;
        field: string;
        direction?: 'asc' | 'desc';
    }>;
    customSorting?: boolean;
    viewOptions?: {
        compact?: boolean;
    };
    publishing?: CollectionPublishingOptions;
};
interface CollectionPublishingOptions

Publishing options configured on a collection.

interface CommonFieldProps
function date(field: Omit<DateField, 'type'>): DateField;
type DateField = CommonFieldProps & {
    type: 'date';
    default?: string;
};
function datetime(field: Omit<DateTimeField, 'type'>): DateTimeField;
type DateTimeField = CommonFieldProps & {
    type: 'datetime';
    default?: string;
    timezone?: string;
};
const define: typeof defineSchema;

Defines the schema for a collection or reusable component.

function defineCollection(collection: Omit<Collection, 'id'>): Omit<Collection, 'id'>;
function definePreset<T = Record<string, any>>(preset: SchemaPreset<T>): SchemaPreset<T>;

Defines a preset for use within schema.define({presets: [...]}). The optional generic parameter T constrains the shape of the preset's data field. Pass an auto-generated fields type to enable type-safety:

import type {HeroFields} from './generated/types.d.ts';

schema.definePreset<HeroFields>({
  id: 'big',
  label: 'Big hero',
  data: {title: 'Welcome'},
});
function defineSchema(schema: Schema): Schema;
interface DocumentSource

Sources the selectable values for a field from a field within another CMS document.

Use this when a set of allowed values is managed centrally in the CMS (e.g. a list of feature flags defined in a global module) and you want other fields to pick from that list instead of typing free-form strings.

Example: source a flag select's options from the name of each item in the flags array of the GlobalModules/flags document.

schema.select({
  id: 'flag',
  source: {
    doc: 'GlobalModules/flags',
    field: 'flags',
    valueKey: 'name',
    helpKey: 'description',
  },
  creatable: true,
});
type Field = StringField | NumberField | PasswordField | DateField | DateTimeField | BooleanField | SelectField | MultiSelectField | ImageField | FileField | ObjectField | ArrayField | OneOfField | RichTextField | ReferenceField | ReferencesField;
type FieldValueSource = DocumentSource;

The source of selectable values for a field. Currently only a CMS document source is supported; other source types may be added in the future.

type FieldWithId = Field;

Similar to Field but with a required id. TODO(stevenle): fix this.

function file(field: Omit<FileField, 'type'>): FileField;
type FileField = CommonFieldProps & {
    type: 'file';
    exts?: string[];
    preserveFilename?: boolean;
    cacheControl?: string;
    alt?: boolean;
    aspectRatio?: AspectRatio | AspectRatio[];
};
function glob(pattern: string, options?: GlobOptions): SchemaPattern;

Creates a schema pattern that is resolved at project load time.

This is the recommended way to reference multiple schemas in a oneOf field, especially for self-referencing schemas like containers. The pattern is resolved after all schemas are loaded, completely avoiding circular import issues.

// Simple usage - include all templates:
export default schema.define({
  name: 'Container',
  fields: [
    schema.array({
      id: 'children',
      of: schema.oneOf({
        types: schema.glob('/templates/*\/*.schema.ts'),
      }),
    }),
  ],
});

// With exclusions:
schema.oneOf({
  types: schema.glob('/templates/*\/*.schema.ts', {
    exclude: ['DeprecatedTemplate'],
  }),
});

// With field omissions (useful for nested contexts):
schema.oneOf({
  types: schema.glob('/blocks/*\/*.schema.ts', {
    omitFields: ['id'],
  }),
});
interface GlobOptions

Options for schema.glob().

function image(field: Omit<ImageField, 'type'>): ImageField;
type ImageField = CommonFieldProps & {
    type: 'image';
    translate?: boolean;
    exts?: string[];
    cacheControl?: string;
    alt?: boolean;
    aspectRatio?: AspectRatio | AspectRatio[];
};
function multiselect(field: Omit<MultiSelectField, 'type'>): MultiSelectField;
type MultiSelectField = Omit<SelectField, 'type'> & {
    type: 'multiselect';
};
function number(field: Omit<NumberField, 'type'>): NumberField;
type NumberField = CommonFieldProps & {
    type: 'number';
    default?: number;
};
function object(field: Omit<ObjectField, 'type'>): ObjectField;
type ObjectField = CommonFieldProps & {
    type: 'object';
    fields: FieldWithId[];
    variant?: 'drawer' | 'inline';
    drawerOptions?: {
        collapsed?: boolean;
        inline?: boolean;
    };
};
type ObjectLikeField = ImageField | FileField | ObjectField | OneOfField | ReferenceField;
function oneOf(field: Omit<OneOfField, 'type'>): OneOfField;
type OneOfField = CommonFieldProps & {
    type: 'oneof';
    variant?: 'dropdown' | 'picker';
    types: Schema[] | string[] | Array<Schema | string> | SchemaPattern;
};
function password(field: Omit<PasswordField, 'type'>): PasswordField;
type PasswordField = CommonFieldProps & {
    type: 'password';
    default?: never;
    minLength?: number;
    iterations?: number;
};

A field for securely storing a password.

The password is hashed in the CMS UI before it is saved, so the plain text value is never written to the db. The stored value is a PasswordHash containing the algorithm, salt and hash. Use verifyPassword() from @blinkk/root-cms (or RootCMSClient.verifyPassword()) to check whether a candidate password matches.

schema.password({id: 'password', label: 'Password'});

// Server-side:
const ok = await cmsClient.verifyPassword(doc.fields.password, input);
type PasswordFieldValue = PasswordHash;

The value stored in the db for a PasswordField.

interface PasswordHash

The value stored in the db for a password field. The salt and the derived hash are stored base64-encoded alongside the parameters used to compute them, so the algorithm or its cost can change without invalidating existing hashes.

type PasswordHashAlgorithm = 'pbkdf2-sha256';

The hashing algorithms supported by hashPassword.

const preset: typeof definePreset;

Alias for definePreset.

interface PublishCheckConfig

A check configured to run as part of a collection's publishing flow.

type PublishCheckLevel = 'required' | 'warning';

Severity level for a check configured on a collection.

- required: an error result halts publishing. - warning: publishing continues, and the message is surfaced afterwards.

function reference(field: Omit<ReferenceField, 'type'>): ReferenceField;
type ReferenceField = CommonFieldProps & {
    type: 'reference';
    collections?: string[];
    initialCollection?: string;
    buttonLabel?: string;
};
function references(field: Omit<ReferencesField, 'type'>): ReferencesField;
type ReferencesField = CommonFieldProps & {
    type: 'references';
    collections?: string[];
    initialCollection?: string;
    buttonLabel?: string;
};
function richtext(field: Omit<RichTextField, 'type'>): RichTextField;
type RichTextField = CommonFieldProps & {
    type: 'richtext';
    translate?: boolean;
    placeholder?: string;
    autosize?: boolean;
    blockComponents?: Schema[];
    inlineComponents?: Schema[];
    paragraphSizes?: Array<RichTextParagraphSizeOption | string>;
};
interface Schema
interface SchemaPattern

A schema pattern that is resolved at project load time. This allows schemas to reference other schemas by glob pattern without causing circular import issues, since resolution is deferred until all schemas are loaded.

interface SchemaPreset<T = Record<string, any>>
type SchemaWithTypes = Schema & {
    types?: Record<string, Schema>;
};
function select(field: Omit<SelectField, 'type'>): SelectField;
type SelectField = CommonFieldProps & {
    type: 'select';
    default?: string;
    options?: Array<{
        value: string;
        label?: string;
    }> | string[];
    source?: FieldValueSource;
    creatable?: boolean;
    translate?: boolean;
    searchable?: boolean;
};
function string(field: Omit<StringField, 'type'>): StringField;
type StringField = CommonFieldProps & {
    type: 'string';
    default?: string;
    translate?: boolean;
    variant?: 'input' | 'textarea' | 'json';
    maxRows?: number;
    autosize?: boolean;
};

SendEmailOptionsinterface

interface SendEmailOptions

Options for RootCMSClient.sendEmail().

Emails are queued in the Projects/${projectId}/Emails collection in firestore and delivered by the Root.js email service (apps/root-services), which sends pending emails using the App Engine Mail API.

Members (7)
to: string | string[];

Recipient email address(es).

from?: string;

Sender email address. The sender must be authorized to send email via the App Engine Mail API, e.g. noreply@<gcp-project-id>.appspotmail.com. Defaults to noreply@<gcp-project-id>.appspotmail.com.

subject: string;

Subject line.

body?: string;

Plain-text body. When omitted, a plain-text body is derived from htmlBody.

htmlBody?: string;

Optional HTML body.

expiresAt?: Date;

Optional expiration date. If the email is still unsent when the email service processes the queue after this date (e.g. the service was down), the email is marked as expired and skipped instead of being delivered late.

emailService?: string | boolean;

Email service used to trigger delivery immediately after the email is queued. Setting this to true uses the default hosted service at https://services.rootjs.dev. Set to a base URL to use a self-hosted deployment of the service (apps/root-services). When unset, the email remains queued until the email service's cron next processes the queue.

serializeProposalfunction

function serializeProposal(proposal: Proposal): string;

Serializes a proposal back to YAML.

Deterministic by construction: keys are emitted in a fixed order, and every string is quoted or written as a block scalar so it can never be re-read as a boolean, number, or date. parse -> serialize -> parse is a fixed point.

SetDocOptionsinterface

interface SetDocOptions
Members (1)
mode: DocMode;

Mode, either "draft" or "published".

SingleLocaleTranslationsMapinterface

interface SingleLocaleTranslationsMap

A translations map containing translations for a single locale.

Example:

{
  "one": "uno",
  "two": "dos"
}

SourceStringtype

type SourceString = string;

SyncAssetToDocsOptionsinterface

interface SyncAssetToDocsOptions

Options for RootCMSClient.syncAssetToDocs().

Members (2)
previousFile?: UploadedFile;

The asset's file data before the change. Used to detect doc-level alt text and canvas bg color customizations, which are preserved.

modifiedBy?: string;

Email of the user making the change. Defaults to the client's user.

toArrayObjectfunction

function toArrayObject(arr: any[]): ArrayObject;

Serializes an array into an ArrayObject, e.g.:

marshalArray([1, 2, 3])
// => {a: 1, b: 2, c: 3, _array: ['a', 'b', 'c']}

This database storage method makes it easier to update a single field in a deeply nested array object.

toDocEditOperationsfunction

function toDocEditOperations(ops: ProposalOp[]): Array<{
    op: string;
    path: string;
    value?: any;
    index?: number;
}>;

Maps a change's ops onto the DocEditOperation shape that applyDocEdits() consumes, dropping the review-only before values.

TranslatedStringtype

type TranslatedString = string;

Translationinterface

interface Translation
Members (1)
source: string;

TranslationsChangeinterface

interface TranslationsChange

Translation changes for one translations doc id.

Members (4)
kind: 'translations';
translationsId: string;
note?: string;
entries: TranslationsEntry[];

TranslationsDocModetype

type TranslationsDocMode = 'draft' | 'published';

TranslationsEntryinterface

interface TranslationsEntry

A single source string and its proposed translations, keyed by locale.

Members (2)
source: string;
locales: Record<string, {
    before?: string | null;
    after: string;
}>;

translationsForLocalefunction

function translationsForLocale(translationsMap: TranslationsMap, locale: string | string[]): LocaleTranslations;

Converts a translations map from loadTranslations() to a map of source to translated string for a particular locale.

The locale is either a single locale (e.g. "fr"), which falls back to en and then the source string, or an ordered fallback chain (e.g. ["en-CA", "en-GB", "en"]) where the first locale with a translation wins before falling back to the source string. Use resolveLocaleFallbacks() to build the chain from the i18n.fallbacks config:

const fallbackLocales = resolveLocaleFallbacks(rootConfig.i18n, 'en-CA');
const translations = translationsForLocale(translationsMap, fallbackLocales);

Returns a map like:

{
  "Hello": "Bonjour",
}

translationsForLocaleV2function

function translationsForLocaleV2(multiLocaleStrings: MultiLocaleTranslationsMap, fallbackLocales: Locale[]): SingleLocaleTranslationsMap;

Converts a multi-locale translations map to a flat single-locale map using a locale fallback chain. For each source string, the first locale in the chain with a non-empty translation wins; if no locale matches, the source string is returned.

const multiLocaleStrings = {
  'one': {'en-GB': 'one!', es: 'uno'},
  'two': {es: 'dos'}
};
translationsForLocaleV2(multiLocaleStrings, ['en-CA', 'en-GB', 'en']);
// =>
// {
//   "one": "one!",
//   "two": "two",
// }

TranslationsLinkedSheetinterface

interface TranslationsLinkedSheet
Members (4)
spreadsheetId: string;
gid: number;
linkedAt: Timestamp;
linkedBy: string;

TranslationsLocaleDocinterface

interface TranslationsLocaleDoc

The TranslationsLocaleDoc is the internal doc type stored in the DB. For a translations doc, the translations for each locale is stored in a separate doc represented by this type.

This type is not meant to be used by external callers since this is primarily an internal implementation detail.

Members (5)
id: string;

Translations id. In most cases, this is the same as the doc id, e.g. Pages/foo--bar.

locale: string;
tags: string[];
strings: TranslationsLocaleDocHashMap;
sys: {
    modifiedAt: Timestamp;
    modifiedBy: string;
    publishedAt?: Timestamp;
    publishedBy?: string;
    linkedSheet?: TranslationsLinkedSheet;
};

TranslationsLocaleDocEntryinterface

interface TranslationsLocaleDocEntry
Members (2)
source: SourceString;
translation: TranslatedString;

TranslationsLocaleDocHashMapinterface

interface TranslationsLocaleDocHashMap

TranslationsLocaleDocWithRefinterface

interface TranslationsLocaleDocWithRef

A translations locale doc paired with its Firestore doc ref.

Members (2)
ref: DocumentReference;
data: TranslationsLocaleDoc;

TranslationsManagerclass

class TranslationsManager
Members (9)
cmsClient: RootCMSClient;
saveTranslations(id: string, strings: MultiLocaleTranslationsMap, options?: {
    tags?: string[];
    modifiedBy?: string;
    linkedSheet?: TranslationsLinkedSheet;
}): Promise<void>;

Saves draft translations for a translations doc id.

Example:

const strings = {
  'one': {es: 'uno', fr: 'un'},
  'two': {es: 'dos', fr: 'deux'},
};
await tm.saveTranslations('Pages/index', strings);
publishTranslations(id: string, options?: {
    batch?: WriteBatch;
    publishedBy?: string;
}): Promise<void>;

Publishes a translations doc.

publishTranslationsBulk(ids: string[], options?: {
    publishedBy?: string;
}): Promise<{
    publishedIds: string[];
}>;

Publishes multiple translations docs by id, e.g.:

await tm.publishTranslationsBulk(['Pages/index', 'common']);
addPublishTranslationsOps(localeDocs: TranslationsLocaleDocWithRef[], batch: WriteBatch, options?: {
    publishedBy?: string;
}): number;

Adds the write ops for publishing a set of draft translations locale docs to a batch. For each locale doc, the draft doc's sys is updated with publishedAt/By and a copy is saved to the published collection. Returns the number of ops added to the batch (2 per locale doc).

getTranslationsLocaleDocs(ids: string[], mode: TranslationsDocMode): Promise<Record<string, TranslationsLocaleDocWithRef[]>>;

Fetches the translations locale docs (with their doc refs) for a set of translations doc ids, grouped by id.

loadTranslations(options?: {
    ids?: string[];
    tags?: string[];
    locales?: Locale[];
    mode?: TranslationsDocMode;
}): Promise<MultiLocaleTranslationsMap>;

Fetches translations from one or more translations docs in the translations manager.

Example:

await tm.loadTranslations();
// =>
// {
//   "one": {"es": "uno", "fr": "un"},
//   "two": {"es": "dos", "fr": "deux"}
// }

To load a specific set of translations docs by id:

const translationsToLoad = ['Global/strings', 'Global/header', 'Global/footer', 'Pages/index'];
await tm.loadTranslations({ids: translationsToLoad});
// =>
// {
//   "one": {"es": "uno", "fr": "un"},
//   "two": {"es": "dos", "fr": "deux"}
// }

To load a subset of locales (more performant):

await tm.loadTranslations({locales: ['es']});
// =>
// {
//   "one": {"es": "uno"},
//   "two": {"es": "dos"}
// }
loadTranslationsForLocale(locale: Locale, options?: {
    mode?: TranslationsDocMode;
    fallbackLocales?: Locale[];
}): Promise<SingleLocaleTranslationsMap>;

Fetches translations for a given locale, with optional fallbacks. The return value is a map of source string to translated string.

If no fallbackLocales are provided, the fallback chain is resolved from the project's i18n.fallbacks config.

Example:

await translationsDoc.loadTranslationsForLocale('es');
// =>
// {
//   "one": "uno",
//   "two": "dos",
// }
importTranslationsFromV1(options?: ImportTranslationsFromV1Options): Promise<ImportTranslationsFromV1Result>;

Imports translations from the v1 system to the TranslationsManager.

Each v1 string is grouped into a v2 translations doc per tag (e.g. a string tagged Pages/index is saved to the Pages/index translations doc). Collection-level tags (e.g. Pages, which v1 added alongside each doc tag) are ignored, since a translations doc with every string in a collection can exceed Firestore's doc size limit. Strings that are only tagged with a collection were removed from their docs and are skipped. Untagged strings are grouped into a v1-untagged doc so that nothing is dropped. By default, the imported translations are saved as drafts; use publishTranslationsBulk() to publish them, or pass {publish: true} to publish them as part of the import.

See ImportTranslationsFromV1Options for pruning unused strings and resuming a previously failed import.

TranslationsMapinterface

interface TranslationsMap

TranslationsMigrationStateinterface

interface TranslationsMigrationState
Members (8)
version: number;
status: TranslationsMigrationStatus;
runId?: string;

Identifies a migration across retries. Kept when a failed or stale run is retried, so the retry can resume from the local write cache.

startedAt?: Timestamp;
startedBy?: string;
finishedAt?: Timestamp;
error?: string;
stats?: {
    numStrings: number;
    numDocs: number;
    numPrunedStrings?: number;
    numSkippedStrings?: number;
    numLocaleDocs?: number;
    numCachedLocaleDocs?: number;
};

TranslationsMigrationStatustype

type TranslationsMigrationStatus = 'running' | 'complete' | 'error';

TranslationsWriteCacheinterface

interface TranslationsWriteCache

Stores the cache keys of translations locale docs that have been saved. Keys are content hashes, so a locale doc whose strings changed since it was saved is written again.

Members (2)
has(key: string): boolean;
add(keys: string[]): Promise<void>;

unmarshalArrayfunction

function unmarshalArray(arrObject: ArrayObject): any[];

Converts an ArrayObject to a normal array.

unmarshalDatafunction

function unmarshalData(data: any): any;

Walks the data tree and converts any Timestamp objects to millis and any _array maps to normal arrays.

E.g.:

normalizeData({ sys: {modifiedAt: Timestamp(123)}, fields: { _array: ['asdf'], asdf: {title: 'hello'} } }) // => {sys: {modifiedAt: 123}, fields: {foo: [{title: 'hello'}]}}

UpdateDraftOptionsinterface

interface UpdateDraftOptions
Members (2)
modifiedBy?: string;

Email of user modifying the doc. If blank, defaults to the client's user.

validate?: boolean;

Whether to validate the updated field against the collection schema. If validation fails, an error will be thrown with details about the validation errors.

UploadAssetOptionsinterface

interface UploadAssetOptions

Options for RootCMSClient.uploadAsset().

Members (10)
folder?: string;

Asset library folder to upload into, e.g. marketing/q1. Missing folders are created. Defaults to the root folder. Ignored when assetId is set.

name?: string;

Asset display name. Defaults to the filename. If the folder already contains a file with this name, that asset's file is replaced.

filename?: string;

The uploaded file's name, e.g. hero.png. Defaults to the basename of the file path. Required when uploading raw bytes.

assetId?: string;

Id of an existing asset whose file should be replaced, regardless of its name or folder.

alt?: string;

Alt text for the file. When replacing a file and this is omitted, the asset's existing alt text is kept.

namingMode?: 'hash' | 'hash-path' | 'clean';

GCS object naming strategy. Defaults to hash ({hash}.{ext}), or hash-path ({hash}/{filename}) when the destination folder has the "preserve filename" setting enabled. clean keeps the original filename and can overwrite existing files.

cacheControl?: string;

Cache-Control header for the GCS object. Defaults to 365 days.

disableGci?: boolean;

Skips registering images with the Google Cloud Image service.

syncDocs?: boolean;

Whether to fan out a replaced file to the draft docs that use the asset. Defaults to true.

modifiedBy?: string;

Email of the user making the change. Defaults to the client's user.

UploadAssetResultinterface

interface UploadAssetResult

Result of RootCMSClient.uploadAsset().

Members (3)
asset: Asset;

The created or updated asset.

status: 'created' | 'replaced';

Whether a new asset was created or an existing asset's file was replaced.

sync?: AssetSyncResult;

Docs updated with the new file, when an existing asset was replaced.

UploadedFileinterface

interface UploadedFile

File data stored in image/file fields and on asset library files.

Members (12)
src: string;
filename?: string;
gcsPath?: string;
width?: number;
height?: number;
alt?: string;
uploadedBy?: string;
uploadedAt?: string | number;
canvasBgColor?: 'light' | 'dark';
altDisabled?: boolean;

When true, alt text handling is disabled for the file (e.g. decorative images). The alt text input is hidden in docs that use the file and no alt text is propagated from the asset library.

originalSrc?: string;

The original source URL if the image has been edited.

assetId?: string;

When the file was selected from the asset library, the id of the asset at Projects/<projectId>/Assets/<assetId>. Updates to the asset fan out to docs that embed it (see ui/utils/assets.ts and core/assets.ts).

UserRoletype

type UserRole = 'ADMIN' | 'EDITOR' | 'CONTRIBUTOR' | 'VIEWER';

verifyPasswordfunction

function verifyPassword(stored: PasswordHash | null | undefined, password: string): Promise<boolean>;

Verifies a candidate password against a stored PasswordHash.

Returns false (never throws) when the stored value is missing or malformed, so callers can pass a field value straight from a doc.

const ok = await verifyPassword(doc.fields.password, req.body.password);

@blinkk/root-cms/browser-client

import {…} from '@blinkk/root-cms/browser-client';

AIEventstype

type AIEvents = Pick<EmbedWindowEvents, 'ready' | 'error' | 'close'>;

Events emitted by EmbeddedAI.

EditorEventstype

type EditorEvents = EmbedWindowEvents;

Events emitted by EmbeddedEditor.

EmbeddedAIclass

class EmbeddedAI

Handle to a headless Root AI chat opened via RootCMSBrowserClient.openRootAI.

Members (6)
get element(): HTMLIFrameElement | null;

The iframe element (iframe mode only).

get window(): Window | null;

The pop-up window (popup mode only).

on<K extends keyof AIEvents>(type: K, cb: (payload: AIEvents[K]) => void): () => void;

Subscribes to an event. Returns an unsubscribe function.

reload(): void;

Reloads the embedded chat (starts a fresh chat).

close(): void;

Closes the embedded chat.

closeAndReload(): void;

Closes the embedded chat and reloads the host page.

EmbeddedEditorclass

class EmbeddedEditor

Handle to a headless doc editor opened via RootCMSBrowserClient.openEditor.

Note: the embedded editor does not autosave; closing it discards any unsaved changes.

Members (8)
readonly docId: string;

The doc being edited, e.g. Pages/about.

get element(): HTMLIFrameElement | null;

The iframe element (iframe mode only).

get window(): Window | null;

The pop-up window (popup mode only).

on<K extends keyof EditorEvents>(type: K, cb: (payload: EditorEvents[K]) => void): () => void;

Subscribes to an editor event. Returns an unsubscribe function.

focusField(deepKey: string): void;

Scrolls the editor to (focuses) a field, e.g. hero.title.

reload(): void;

Reloads the embedded editor.

close(): void;

Closes the embedded editor, discarding any unsaved changes.

closeAndReload(): void;

Closes the embedded editor and reloads the host page.

EmbedWindowEventsinterface

interface EmbedWindowEvents

Events emitted by an embedded CMS window (iframe or pop-up).

Members (5)
ready: {
    docId?: string;
};

The embedded page finished loading.

saved: {
    docId?: string;
    saveState?: SaveState;
};

The doc was saved.

published: {
    docId?: string;
    publishedAt?: number;
};

The doc was published.

error: {
    error?: string;
};

An error occurred (e.g. the pop-up was blocked).

close: void;

The embedded window was closed (via close() or by the user).

HighlightNodeMessageinterface

interface HighlightNodeMessage

Requests that the preview page highlight the node associated with a field. A null deepKey clears all highlights.

Members (1)
highlightNode: {
    deepKey: string | null;
    options?: {
        scroll: boolean;
    };
};

NavigateToDocMessageinterface

interface NavigateToDocMessage

Requests that the doc editor switch to another document.

Posted by a page rendered inside the CMS preview pane to say which doc it is showing, so the editor can follow the preview as the user navigates the site. The page knows its own doc id, which is why the CMS doesn't try to derive one from the url.

The doc id is untrusted input: the CMS checks the collection exists and the slug is well-formed before routing anywhere.

Members (1)
navigateToDoc: {
    docId: string;
    confirm?: boolean;
};

OpenEditorOptionsinterface

interface OpenEditorOptions
Members (5)
mode?: 'popup' | 'iframe';

How to open the editor. Defaults to popup.

container?: HTMLElement;

Container the iframe is appended to. Required when mode is iframe.

deeplink?: string;

Deep key of a field to scroll to on load, e.g. hero.title.

width?: number;

Pop-up window width. Defaults to 480.

height?: number;

Pop-up window height. Defaults to 720.

OpenRootAIOptionsinterface

interface OpenRootAIOptions extends Omit<OpenEditorOptions, 'deeplink'>
Members (1)
docId?: string;

Doc to provide as context to the AI chat, e.g. Pages/about.

PreviewConnectionclass

class PreviewConnection

Channel between a site rendered inside the in-CMS preview pane and the doc editor that frames it. The preview iframe is same-origin with the CMS, so no configuration is needed. Safe to construct unconditionally: outside the preview pane the connection is inert (isEmbedded is false).

Members (5)
readonly isEmbedded: boolean;

Whether this page is rendered inside the in-CMS preview pane.

focusField(deepKey: string): void;

Requests that the doc editor scroll to (focus) a field, e.g. hero.title. Used to implement "click to edit".

navigateToDoc(docId: string, options?: {
    confirm?: boolean;
}): void;

Tells the doc editor to switch to a document, so the editor follows the preview as the user navigates the site.

Call it with the page's own doc id when the page loads. isEmbedded is false outside the CMS preview pane, so the call is a no-op on the live site. The CMS only acts on it when the plugin's preview.channel enables messages from the preview.

The editor asks the user before switching by default, since a doc changing on its own is disorienting for someone who doesn't know the page can do that. Pass {confirm: false} to switch straight away, for sites that have their own affordance for it (a toggle the user turned on, say).

Usage:

const preview = new PreviewConnection();
if (preview.isEmbedded) {
  preview.navigateToDoc(doc.id);
}
on(type: 'highlight', cb: (event: PreviewHighlightEvent) => void): () => void;

Subscribes to field highlight requests from the doc editor (sent when the user hovers/focuses a field). Returns an unsubscribe function.

disconnect(): void;

Removes the message listener and all subscriptions.

PreviewHighlightEventinterface

interface PreviewHighlightEvent

Event emitted when the doc editor requests a field highlight.

Members (2)
deepKey: string | null;

Deep key of the field to highlight, or null to clear highlights.

scroll: boolean;

Whether to scroll the highlighted node into view.

RootCMSBrowserClientclass

class RootCMSBrowserClient

Client for embedding Root CMS into another site.

Members (5)
readonly cmsOrigin: string;

Normalized origin of the Root CMS server.

openEditor(docId: string, options?: OpenEditorOptions): EmbeddedEditor;

Opens the headless doc editor for a doc (e.g. Pages/about) in a pop-up or iframe. In popup mode, call from a user gesture (e.g. a click handler) to avoid pop-up blockers.

The slug portion of the docId is normalized the same way the CMS normalizes slugs, so a URL-like path also works, e.g. Pages/about/foo -> Pages/about--foo.

openRootAI(options?: OpenRootAIOptions): EmbeddedAI;

Opens the headless Root AI chat in a pop-up or iframe, optionally with a doc as context. In popup mode, call from a user gesture (e.g. a click handler) to avoid pop-up blockers.

static connectPreview(): PreviewConnection;

Connects to the doc editor from a site rendered inside the in-CMS preview pane, enabling "click to edit" (PreviewConnection.focusField) and field highlighting (on('highlight', ...)).

static isInPreviewIframe(): boolean;

Returns whether this page is rendered inside the in-CMS preview pane.

The parent's location is the reliable signal: the preview iframe is same-origin, so the CMS path is readable. The referrer only names the CMS for the first page the pane loads -- once the user follows a link inside the preview it names the previous page of the site instead -- so it is kept as a fallback for the cases where the parent can't be read.

RootCMSBrowserClientOptionsinterface

interface RootCMSBrowserClientOptions
Members (1)
cmsOrigin: string;

Origin of the Root CMS server, e.g. https://cms.example.com.

RootEmbedMessageinterface

interface RootEmbedMessage

Lifecycle messages posted from the headless (embedded) pages (the doc editor and the Root AI panel) to the parent window that frames them. All messages are namespaced under root to avoid colliding with the un-namespaced {scrollToDeeplink} / {highlightNode} messages used by the in-CMS preview channel.

Members (1)
root: {
    type: 'ready' | 'saved' | 'published' | 'error';
    docId?: string;
    saveState?: SaveState;
    publishedAt?: number;
    error?: string;
};

SaveStateenum

enum SaveState {
    NO_CHANGES = "NO_CHANGES",
    UPDATES_PENDING = "UPDATE_PENDING",
    SAVING = "SAVING",
    SAVED = "SAVED",
    ERROR = "ERROR"
}

Save state of the draft doc.

ScrollToDeeplinkMessageinterface

interface ScrollToDeeplinkMessage

Requests that the doc editor scroll to (focus) a specific field.

Members (1)
scrollToDeeplink: {
    deepKey: string;
};

@blinkk/root-cms/client

import {…} from '@blinkk/root-cms/client';

Actioninterface

interface Action<T = any>
Members (5)
action: string;

The name of the action.

by?: string;

The user's email that performed the action (or "system").

timestamp: Timestamp;

Timestamp when the action occurred.

metadata?: T;

Metadata for the action.

links?: {
    label: string;
    url: string;
    target?: string;
}[];

Optional list of quick links to display in the UI.

ArrayObjectinterface

interface ArrayObject
Members (1)
_array: string[];

Assetinterface

interface Asset

An entry in the CMS asset library, stored at Projects/${projectId}/Assets/${assetId}. Files and folders live in the same collection and are organized by their parent folder path.

Members (10)
id: string;

Unique id of the asset within the project.

type: 'file' | 'folder';
parent: string;

Parent folder path, e.g. '' (root) or 'marketing/q1'.

name: string;

Display name, e.g. hero.png for files or q1 for folders.

file?: UploadedFile;

The uploaded file data (same shape stored in image/file fields).

preserveFilename?: boolean;

For folders, whether uploaded files keep their original filename in the GCS object path ({hash}/{filename}.{ext} instead of {hash}.{ext}).

createdAt: Timestamp;
createdBy: string;
modifiedAt: Timestamp;
modifiedBy: string;

AssetFieldValuetype

type AssetFieldValue = UploadedFile & {
    assetId: string;
};

The value stored in a doc's image/file field when linked to an asset.

AssetSyncResultinterface

interface AssetSyncResult

Result of fanning out an asset update to docs that use it.

Members (2)
updatedDocIds: string[];

Doc ids that were updated.

failedDocIds: string[];

Doc ids that failed to update (eventual consistency; retry by re-syncing).

BatchRequestclass

class BatchRequest
Members (8)
cmsClient: RootCMSClient;
addDoc(docId: string): void;

Adds a doc to the batch request.

addDataSource(dataSourceId: string): void;

Adds a data source to the batch request.

addQuery(queryId: string, collectionId: string, queryOptions?: BatchRequestQueryOptions): void;

Adds a collection-based query to the batch request.

addTranslations(translationsId: string): void;

Adds a translations doc to the request.

fetch(): Promise<BatchResponse>;

Fetches data from the DB.

fetchContent(): Promise<BatchResponse>;

Fetches the docs, queries and data sources in the request, but not the translations. Use this with fetchTranslations() to defer loading translations until the locale is known, e.g.:

const req = cmsClient.createBatchRequest({mode, translate: true});
req.addDoc('Pages/index');
req.addTranslations('common');
const res = await req.fetchContent();
const locale = pickLocale(res.getDoc('Pages/index'));
await req.fetchTranslations(res, {locales: [locale]});
const translations = res.getTranslations(locale);
fetchTranslations(res: BatchResponse, options?: {
    locales?: string[];
}): Promise<BatchResponse>;

Fetches translations into res for the explicitly-added translations ids and, when options.translate is enabled, the docs fetched so far.

options.locales overrides the locales set on the request. Each locale is expanded through its fallback chain.

BatchRequestOptionsinterface

interface BatchRequestOptions
Members (3)
mode: 'draft' | 'published';
translate?: boolean;

Whether to automatically fetch translations for the docs retrieved in the request (including docs returned by queries).

locales?: string[];

Locales to fetch translations for. Each locale is expanded through its fallback chain (per the i18n.fallbacks config). Defaults to the locales configured in i18n.locales.

BatchRequestQueryinterface

interface BatchRequestQuery
Members (3)
queryId: string;
collectionId: string;
queryOptions?: BatchRequestQueryOptions;

BatchRequestQueryOptionsinterface

interface BatchRequestQueryOptions
Members (5)
offset?: number;
limit?: number;
orderBy?: string;
orderByDirection?: 'asc' | 'desc';
query?: (query: Query) => Query;

BatchResponseclass

class BatchResponse
Members (8)
docs: Record<string, Doc>;
queries: Record<string, Doc[]>;
dataSources: Record<string, DataSourceData>;
translations: Record<string, Record<string, TranslationsLocaleDoc>>;

Translations locale docs retrieved in the request, keyed by translations id then by locale. The ids are in precedence order: generic translations (e.g. "common") first, doc-specific translations last.

getDoc<Fields = any>(docId: string): Doc<Fields> | null;

Returns a doc added with addDoc(), or null if it doesn't exist. Slugs with slashes match either form, e.g. "Pages/foo/bar" or "Pages/foo--bar".

const header = res.getDoc<GlobalFields>('Global/header');
getQuery<Fields = any>(queryId: string): Array<Doc<Fields>>;

Returns the docs for a query added with addQuery(), or an empty array if the query has no results.

const posts = res.getQuery<BlogPostsFields>('posts');
getDataSource<T = any>(dataSourceId: string): DataSourceData<T> | null;

Returns a data source added with addDataSource(), or null if it doesn't exist.

const pricing = res.getDataSource<PricingRow[]>('pricing');
getTranslations(locale: string | string[]): LocaleTranslations;

Returns a map of translations for a given locale or locale fallbacks.

The input is either a single locale (e.g. "de"), which is expanded through the fallback chain configured in i18n.fallbacks, or an array of locales representing an explicit fallback chain, e.g. ["en-CA", "en-GB", "en"].

The returned value is a flat map of source string to translated string, e.g.: {"<source>": "<translation>"}

clearReadCachesfunction

function clearReadCaches(): Promise<void>;

Clears every ReadCache in the process (including their stores). Called automatically when docs are published or unpublished through a RootCMSClient.

CronScheduleTypetype

type CronScheduleType = 'interval' | 'daily' | 'weekly' | 'custom';

Data source sync schedule type.

- interval: sync every N minutes/hours/days (uses interval + unit). - daily / weekly / custom: sync on a specific cron schedule (uses expression + timezone). daily and weekly are UI presets that are stored as standard cron expressions; custom allows an arbitrary expression.

CronUnittype

type CronUnit = 'minutes' | 'hours' | 'days';

DataSourceinterface

interface DataSource
Members (15)
id: string;
description?: string;
type: 'http' | 'gsheet';
url: string;
dataFormat?: GsheetDataFormat;

Currently only used by gsheet. map returns the sheet as an array of objects keyed by the header row, grid returns the sheet as an array of arrays of strings (including the header row).

httpOptions?: {
    method: HttpMethod;
    headers?: Record<string, string>;
    body?: string;
};

Options for HTTP requests.

cron?: DataSourceCron;
createdAt: Timestamp;
createdBy: string;
syncedAt?: Timestamp;
syncedBy?: string;
publishedAt?: Timestamp;
publishedBy?: string;
archivedAt?: Timestamp;
archivedBy?: string;

DataSourceCroninterface

interface DataSourceCron
Members (7)
enabled: boolean;
schedule?: CronScheduleType;

Scheduling mode. Defaults to interval when unset (for backwards compatibility with data sources created before specific schedules were supported).

interval?: number;

Interval value, used when schedule is interval.

unit?: CronUnit;

Interval unit, used when schedule is interval.

expression?: string;

Standard 5-field cron expression, used when schedule is daily, weekly, or custom, e.g. 0 19 * * * for "every day at 7pm".

timezone?: string;

IANA timezone used to evaluate expression, e.g. America/New_York. Defaults to UTC when unset.

autoPublish?: boolean;

DataSourceDatainterface

interface DataSourceData<T = any>
Members (3)
dataSource: DataSource;
data: T;
headers?: string[];

Optional list of column headers (for gsheet sources).

DataSourceGridRowinterface

interface DataSourceGridRow

A single row of grid data as stored in Firestore. Rows are wrapped in a map because Firestore doesn't support arrays nested directly within arrays.

Members (1)
cells: string[];

DataSourceModetype

type DataSourceMode = 'draft' | 'published';

Docinterface

interface Doc<Fields = any>
Members (5)
id: string;

The id of the doc, e.g. "Pages/foo-bar".

collection: string;

The collection id of the doc, e.g. "Pages".

slug: string;

The slug of the doc, e.g. "foo-bar".

sys: {
    createdAt: number;
    createdBy: string;
    modifiedAt: number;
    modifiedBy: string;
    firstPublishedAt?: number;
    firstPublishedBy?: string;
    publishedAt?: number;
    publishedBy?: string;
    publishingLocked: {
        lockedAt: string;
        lockedBy: string;
        reason: string;
        until?: Timestamp;
    };
    locales?: string[];
    assets?: string[];
    sortKey?: string;
};
fields: Fields;

DocModetype

type DocMode = 'draft' | 'published';

FileSystemReadCacheStoreclass

class FileSystemReadCacheStore implements ReadCacheStore

A ReadCacheStore that writes cached reads to the filesystem, so that they can be shared across processes and worker threads, e.g. the workers of a root build --threads=N SSG build.

Concurrent misses for the same key are deduped across processes: the first process to miss claims the key with a lock file and fetches it, and the others wait for the result.

const cache = new ReadCache({
  store: new FileSystemReadCacheStore({dir: 'dist/.cache/root-cms'}),
});

During root build, createRoute({cache: true}) uses this store automatically (under dist/.cache/, which is deleted at the start and end of each build).

Members (4)
get(key: string): Promise<ReadCacheStoreEntry | undefined>;

Returns the entry for key, or undefined if it isn't stored.

set(key: string, entry: ReadCacheStoreEntry): Promise<void>;

Stores the entry for key.

delete(key: string): Promise<void>;

Removes the entry for key, e.g. when a fetch fails.

clear(): Promise<void>;

Removes all entries.

FileSystemReadCacheStoreOptionsinterface

interface FileSystemReadCacheStoreOptions

Options for a FileSystemReadCacheStore.

Members (3)
dir: string;

Directory where cached reads are written.

lockTimeout?: number;

How long (in ms) to wait for another process that's fetching the same key before fetching it again. Defaults to 30 seconds.

pollInterval?: number;

How often (in ms) to check whether another process has finished fetching a key. Defaults to 20ms.

getCmsPluginfunction

function getCmsPlugin(rootConfig: RootConfig): CMSPlugin;

GetCountOptionsinterface

interface GetCountOptions
Members (2)
mode: DocMode;
query?: (query: Query) => Query;

GetDocOptionsinterface

interface GetDocOptions
Members (1)
mode: DocMode;

Mode, either "draft" or "published".

GsheetDataFormattype

type GsheetDataFormat = 'map' | 'grid';

Format used to store data synced from a csv-style data source.

- map: an array of objects keyed by the sheet's header row, e.g. [{header1: 'foo', header2: 'bar'}]. - grid: an array of arrays of strings, including the header row, e.g. [['header1', 'header2'], ['foo', 'bar']].

hashPasswordfunction

function hashPassword(password: string, options?: HashPasswordOptions): Promise<PasswordHash>;

Hashes a password with a freshly generated random salt.

const stored = await hashPassword('hunter2');
// => {algorithm: 'pbkdf2-sha256', iterations: 600000, salt: '...', hash: '...'}

HashPasswordOptionsinterface

interface HashPasswordOptions
Members (1)
iterations?: number;

The number of PBKDF2 iterations. Defaults to DEFAULT_ITERATIONS. Higher values are slower to compute and slower to brute force.

HttpMethodtype

type HttpMethod = 'GET' | 'POST';

isPasswordHashfunction

function isPasswordHash(value: unknown): value is PasswordHash;

Returns whether a value has the shape of a PasswordHash.

isRichTextDatafunction

function isRichTextData(data: any): boolean;

Returns true if the data is a rich text data object.

ListActionsOptionsinterface

interface ListActionsOptions
Members (3)
action?: string;

Filter by a specific action. Defaults to all actions.

by?: string;

Filter by a specific user. Defaults to all users.

limit?: number;

Max number of actions to return. Defaults to 100.

ListDocsOptionsinterface

interface ListDocsOptions
Members (7)
mode: DocMode;
offset?: number;
limit?: number;
orderBy?: string;

DB field path to order results by, e.g. sys.createdAt. Defaults to slug. The default is skipped when a query fn is provided, since the query fn may supply its own ordering.

orderByDirection?: 'asc' | 'desc';
query?: (query: Query) => Query;
raw?: boolean;

Whether to fetch the "raw" version of the doc (for use in conjunction with setRawDoc()).

LoadTranslationsOptionsinterface

interface LoadTranslationsOptions
Members (1)
tags?: string[];

LocaleFallbacksI18nConfiginterface

interface LocaleFallbacksI18nConfig

Utilities for resolving the locale fallback chain used by translations.

The i18n.fallbacks config in root.config.ts maps a locale to an ordered list of fallback locales. When a translation is missing for a locale, each fallback locale is checked (in order) before falling back to i18n.defaultLocale and finally the source string. For example:

i18n: {
  locales: ['en', 'en-GB', 'en-CA'],
  fallbacks: {
    'en-CA': ['en-GB'],
  },
}

With the config above, resolveLocaleFallbacks(i18n, 'en-CA') returns ['en-CA', 'en-GB', 'en']. Fallbacks are resolved recursively (breadth first), and all matching is case-insensitive.

Members (4)
locales?: string[];
defaultLocale?: string;
fallbacks?: Record<string, string[]> | ((locale: string) => string[]);

Map of locale to fallback locales, or a function that returns the fallback locales for a locale.

fallbackToLanguage?: boolean;

Falls back from a <lang>_<country> (or <lang>-<country>) locale to <lang>, e.g. ja_jp -> ja, after any configured fallbacks. When locales is set, only fallbacks to configured locales are added.

LocaleTranslationsinterface

interface LocaleTranslations

marshalArrayvariable

const marshalArray: typeof toArrayObject;

marshalDatafunction

function marshalData(data: any): any;

Walks the data tree and converts any array of objects into "array objects" for storage in firestore.

normalizeDatafunction

function normalizeData(data: any): any;

Deprecated. Use `unmarshalData()` instead.

parseDocIdfunction

function parseDocId(docId: string): {
    collection: string;
    slug: string;
};

Parses a docId (e.g. Pages/foo) and returns the collection and slug.

PasswordHashinterface

interface PasswordHash

The value stored in the db for a password field. The salt and the derived hash are stored base64-encoded alongside the parameters used to compute them, so the algorithm or its cost can change without invalidating existing hashes.

Members (4)
algorithm: PasswordHashAlgorithm;

The key derivation algorithm used, e.g. pbkdf2-sha256.

iterations: number;

The number of PBKDF2 iterations used to derive the hash.

salt: string;

The random salt used to derive the hash, base64-encoded.

hash: string;

The derived hash, base64-encoded.

PasswordHashAlgorithmtype

type PasswordHashAlgorithm = 'pbkdf2-sha256';

The hashing algorithms supported by hashPassword.

ProposalOverlayclass

class ProposalOverlay

Indexes a change proposal so it can be applied cheaply to docs and translations as they are read.

Only the change kinds that affect rendering are handled: doc edits, doc creation, and translations. Release changes affect publishing rather than content, so they are ignored here and only take effect when the proposal is applied.

Members (9)
readonly proposal: Proposal;
applyToDoc(doc: Doc): Doc;

Applies any edit ops for doc.id to an unmarshaled doc.

hasNewDocsInCollection(collectionId: string): boolean;

True if the proposal creates any doc in collectionId.

countNewDocsInCollection(collectionId: string): number;

Number of docs the proposal creates in collectionId.

newDocsInCollection(collectionId: string, existingIds: Set<string>): Doc[];

Builds Doc objects for the docs the proposal creates in collectionId, skipping any that already exist in the db.

buildNewDoc(docId: string): Doc | null;

Synthesizes a Doc for a doc id the proposal creates, or null if the proposal does not create it. Duplicated docs are not resolved here (the source doc is not available synchronously), so they come back with empty fields.

newDocIds(): string[];

Every doc id the proposal creates, in proposal order.

allTranslations(): Record<string, Record<string, string>> | null;

All proposed translations, flattened to {source: {locale: string}}.

localeDocStrings(translationsId: string, locale: string): Record<string, {
    source: string;
    translation: string;
}> | null;

Returns proposed strings for one translations id and locale, shaped like the strings hash map stored on a translations locale doc, or null when the proposal has nothing for it.

ReadCacheclass

class ReadCache

An in-memory cache for published CMS reads, which saves a Firestore round trip on most page renders. Pass one to a RootCMSClient with new RootCMSClient(rootConfig, {cache}), or enable it for a route with createRoute({cache: true}).

Published content can take up to ttl to show up, so a cached client shouldn't be used to serve previews.

Members (3)
get<T>(key: string, fetch: () => Promise<T>, options?: ReadCacheReadOptions): Promise<T>;

Returns the cached value for key, or calls fetch() and caches its result. Concurrent reads for the same key share one fetch, and failed fetches aren't cached. Each caller gets its own copy of the value so that a render that mutates it can't affect other requests.

getMany<T>(keys: string[], fetchMany: (keys: string[]) => Promise<T[]>, options?: ReadCacheReadOptions): Promise<T[]>;

Like get(), but for multiple keys. The uncached keys are fetched with a single fetchMany() call, which must return their values in order.

clear(): Promise<void>;

Removes all cached reads, including the ones in the store.

ReadCacheOptionsinterface

interface ReadCacheOptions

Options for a ReadCache.

Members (3)
ttl?: number | ReadCacheTtl;

How long (in ms) a cached read is served before it's fetched again. Defaults to 60 seconds. Pass an object to set the ttl per read type, e.g. {default: 15_000, translations: 120_000}.

maxEntries?: number;

Max number of cached reads held in memory, so that arbitrary slugs can't grow the cache unbounded. The oldest reads are evicted first. Defaults to 1000.

store?: ReadCacheStore;

Secondary storage that's checked on an in-memory miss, e.g. a FileSystemReadCacheStore that's shared across processes.

ReadCacheReadOptionsinterface

interface ReadCacheReadOptions

Options for a single read.

Members (1)
type?: ReadCacheType;

The type of read, used to pick the ttl. Defaults to "doc".

ReadCacheStoreinterface

interface ReadCacheStore

A secondary storage layer for a ReadCache, e.g. for sharing cached reads between the worker threads of an SSG build (see FileSystemReadCacheStore). Reads are always cached in memory first, and the store is only checked on an in-memory miss.

Stored values are plain Firestore data, so a store that serializes them must preserve Timestamp values.

Members (4)
get(key: string): Promise<ReadCacheStoreEntry | undefined>;

Returns the entry for key, or undefined if it isn't stored.

set(key: string, entry: ReadCacheStoreEntry): Promise<void>;

Stores the entry for key.

delete(key: string): Promise<void>;

Removes the entry for key, e.g. when a fetch fails.

clear(): Promise<void>;

Removes all entries.

ReadCacheStoreEntryinterface

interface ReadCacheStoreEntry

A cached value stored in a ReadCacheStore.

Members (2)
expiresAt: number;

Timestamp (in ms) when the entry expires.

value: unknown;

The cached value.

ReadCacheTtltype

type ReadCacheTtl = {
    default?: number;
} & {
    [type in ReadCacheType]?: number;
};

Per-read-type ttls (in ms). Types without a ttl use default, which defaults to 60 seconds.

ReadCacheTypetype

type ReadCacheType = 'doc' | 'query' | 'translations' | 'dataSource';

The type of a cached read, used for per-type ttls (see ReadCacheTtl).

Releaseinterface

interface Release
Members (10)
id: string;
description?: string;
docIds?: string[];
dataSourceIds?: string[];
createdAt?: Timestamp;
createdBy?: string;
scheduledAt?: Timestamp;
scheduledBy?: string;
publishedAt?: Timestamp;
publishedBy?: string;

resolveLocaleFallbacksfunction

function resolveLocaleFallbacks(i18nConfig: LocaleFallbacksI18nConfig | undefined, locale: string): string[];

Resolves the ordered locale fallback chain for a locale, starting with the locale itself and ending with the default locale. Fallback chains are followed recursively (breadth first) with cycle protection, and locale keys are matched case-insensitively while preserving the configured casing of each fallback value.

RootCMSClientclass

class RootCMSClient
Members (65)
readonly rootConfig: RootConfig;
readonly cmsPlugin: CMSPlugin;
readonly projectId: string;
readonly app: App;
readonly db: Firestore;
readonly proposal?: ProposalOverlay;

An unapplied change proposal overlaid on top of everything this client reads. Lets a developer preview proposed content against a running site before accepting it. Reads only — writes never fold the proposal in.

readonly user: string;

Identity that writes are attributed to by default.

readonly logActions: boolean;

Whether doc writes are recorded in the action logs.

getAllData(refs: DocumentReference[], options: {
    mode: DocMode;
    cacheType?: ReadCacheType;
}): Promise<any[]>;

Fetches the data for a list of doc refs, in order, with undefined for docs that don't exist. Published reads are cached per doc when the client has a cache, and the uncached docs are fetched in a single getAll().

getQueryData(query: Query, options: {
    mode: DocMode;
    cacheKey?: unknown[] | null;
    cacheType?: ReadCacheType;
}): Promise<any[]>;

Runs a Firestore query and returns the data of each result. Published reads are cached when the client has a cache and a cacheKey is provided, i.e. the query is fully described by the key.

overlayDoc<T = any>(doc: T): T;

Applies the proposal overlay (if any) to an unmarshaled doc.

Called from every non-raw read path. getRawDoc() and listDocs({raw: true}) deliberately skip this: those exist to feed setRawDoc(), and folding proposed content into them would leak it into a write.

proposedDocsInCollection(collectionId: string, existingIds: Set<string>): Doc<any>[];

Returns docs the proposal creates in a collection that do not exist in the database yet, so list and count reads can include them.

getDoc<Fields = any>(collectionId: string, slug: string, options: GetDocOptions): Promise<Doc<Fields> | null>;

Retrieves doc data from Root.js CMS.

getRawDoc(collectionId: string, slug: string, options: GetDocOptions): Promise<any | null>;

Retrieves raw doc data as stored in the database. Only use this if you know what you are doing.

dbCollectionDocsPath(collectionId: string, options: {
    mode: 'draft' | 'published';
}): string;

Firestore path for a collection.

dbDocPath(collectionId: string, slug: string, options: {
    mode: 'draft' | 'published';
}): string;

Firestore path for a content doc.

dbDocRef(collectionId: string, slug: string, options: {
    mode: 'draft' | 'published';
}): DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;

Firestore doc ref for a content doc.

getCollection(collectionId: string): Promise<Collection | null>;

Returns a collection's schema definition as defined in /collections/<id>.schema.ts.

saveDraftData(docId: string, fieldsData: any, options?: SaveDraftOptions): Promise<void>;

Saves draft data to a doc.

Note: this saves data to the "fields" attr of the draft doc. If you need to modify the sys-level attributes of the doc, use setRawDoc().

updateDraftData(docId: string, path: string, fieldValue: any, options?: UpdateDraftOptions): Promise<void>;

Updates a specific field path in a draft doc.

This allows partial updates to nested fields without replacing the entire document. For example: updateDraftData('Pages/home', 'hero.title', 'New Title')

setRawDoc(collectionId: string, slug: string, data: any, options: SetDocOptions): Promise<void>;

Sets the raw document data directly in Firestore.

CAUTION Prefer using saveDraftData('Pages/foo', data) in most cases. Only use this method if you need to manipulate system-level (sys) fields directly or if you're implementing low-level data operations.

## Validation & Normalization

This method automatically validates and normalizes sys fields to prevent data integrity issues that can cause runtime errors like "e.toMillis is not a function".

### Timestamp Fields (auto-converted) The following fields accept multiple formats and are automatically converted to Firestore Timestamp objects: - sys.createdAt - sys.modifiedAt - sys.publishedAt - sys.firstPublishedAt

Accepted formats: - Firestore Timestamp object (unchanged) - number - Interpreted as milliseconds since epoch, converted to Timestamp - Date object - Converted to Timestamp

### Required Fields (auto-populated with defaults if missing) - sys.createdAt - Defaults to current time if not provided - sys.modifiedAt - Defaults to current time if not provided - sys.createdBy - Defaults to the client's user if not provided - sys.modifiedBy - Defaults to the client's user if not provided - sys.locales - Defaults to ['en'] if not provided

### Optional Fields (validated if present) - sys.publishedBy - String identifier - sys.firstPublishedBy - String identifier - sys.publishingLocked - Object with optional until Timestamp

### Computed Fields - sys.assets - Recomputed from the asset library ids embedded in fields, so that asset updates fan out to the doc

### Document Identity The id, collection, and slug fields are always set to match the provided parameters, overwriting any existing values to prevent data inconsistencies.

// Minimal example - sys fields use defaults
await client.setRawDoc('Pages', 'home', {
  sys: {},  // All sys fields will be auto-populated with defaults
  fields: {
    title: 'Home Page'
  }
}, { mode: 'draft' });

// Full example - with number timestamps (auto-converted)
await client.setRawDoc('Pages', 'home', {
  id: 'Pages/home',
  collection: 'Pages',
  slug: 'home',
  sys: {
    createdAt: Date.now(),  // Auto-converted to Timestamp.
    createdBy: 'user@example.com',
    modifiedAt: Date.now(),  // Auto-converted to Timestamp.
    modifiedBy: 'user@example.com',
    locales: ['en', 'es']
  },
  fields: {
    title: 'Home Page'
  }
}, { mode: 'draft' });
listDocs<T>(collectionId: string, options: ListDocsOptions): Promise<{
    docs: T[];
}>;

Lists docs from a Root.js CMS collection.

getDocsCount(collectionId: string, options: GetCountOptions): Promise<number>;

Returns the number of docs in a Root.js CMS collection.

publishDocs(docIds: string[], options?: {
    publishedBy: string;
    batch?: WriteBatch;
    releaseId?: string;
}): Promise<any[]>;

Batch publishes a set of docs by id.

unpublishDocs(docIds: string[], options?: {
    unpublishedBy?: string;
    batch?: WriteBatch;
}): Promise<any[]>;

Batch unpublishes a set of docs by id.

publishScheduledDocs(): Promise<any[]>;

Publishes scheduled docs.

uploadAsset(file: string | Uint8Array, options?: UploadAssetOptions): Promise<UploadAssetResult>;

Uploads a file to the asset library, from a local file path or raw bytes (which require options.filename). The file is uploaded to the project's GCS bucket and, for images when gci is enabled, registered with the Google Cloud Image service. If the destination folder already contains a file with the same name (or options.assetId is set), the existing asset's file is replaced and the change is fanned out to every draft doc that uses the asset.

Example: uploadAsset('./hero.png', {folder: 'marketing', alt: 'Hero image'})

getAsset(assetId: string): Promise<Asset | null>;

Retrieves an asset library file or folder by id, or null if it does not exist.

listAssets(folder?: string): Promise<Asset[]>;

Lists the assets (folders first, then files) directly within an asset library folder, e.g. marketing/q1. Defaults to the root folder.

findDocsUsingAsset(assetId: string): Promise<string[]>;

Returns the ids of the draft docs that embed an asset library file (e.g. ["Pages/home"]), found via each doc's sys.assets index.

syncAssetToDocs(assetId: string, options?: SyncAssetToDocsOptions): Promise<AssetSyncResult>;

Updates the embedded copies of an asset's file data in every draft doc that uses the asset. Called automatically by uploadAsset() when a file is replaced; call it directly to retry docs that failed to update.

dbReleasePath(releaseId: string): string;

Returns the Firestore path for a release doc.

getRelease(releaseId: string): Promise<Release | null>;

Retrieves a release by id, or null if it does not exist.

listReleases(): Promise<Release[]>;

Lists all releases, most recently created first.

setRelease(releaseId: string, release: Partial<Release>, options?: {
    modifiedBy?: string;
}): Promise<void>;

Creates or updates a release.

Only the fields describing the release's contents are written (description, docIds, dataSourceIds); scheduling and publishing state is left untouched, so this can never publish or schedule a release as a side effect.

publishScheduledReleases(): Promise<void>;

Publishes docs in scheduled releases.

syncScheduledDataSources(): Promise<void>;

Syncs data sources that have cron scheduling enabled and are due for sync.

testPublishingLocked(doc: Doc): boolean;

Checks if a doc is currently "locked" for publishing.

getTranslationsManager(): TranslationsManager;

Returns a TranslationsManager object for managing translations.

To get translations:

await tm.loadTranslations({
  ids: ['Global/strings', 'Pages/index'],
  locales: ['es'],
});

NOTE: The TranslationsManager is a v2 feature that will eventually replace the v1 translations system.

isV2TranslationsEnabled(): boolean;

Returns true if the v2 TranslationsManager is enabled via the experiments.v2TranslationsManager plugin config flag.

loadTranslations(options?: LoadTranslationsOptions): Promise<TranslationsMap>;

Loads translations saved in the translations collection, optionally filtered by tag.

Returns a map like:

{
  "<hash>": {"source": "Hello", "es": "Hola", "fr": "Bonjour"},
}
saveTranslations(translations: {
    [source: string]: {
        [locale: string]: string;
    };
}, tags?: string[]): Promise<void>;

Saves a map of translations, e.g.:

await client.saveTranslations({
  "Hello": {"es": "Hola", "fr": "Bonjour"},
});
verifyPassword(stored: PasswordHash | null | undefined, password: string): Promise<boolean>;

Verifies a candidate password against the hashed value stored by a password field. Returns false when the field is empty or malformed.

const doc = await cmsClient.getDoc('Members', 'alice', {mode: 'published'});
const ok = await cmsClient.verifyPassword(doc?.fields.password, input);
getTranslationKey(source: string): string;

Returns the "key" used for a translation as stored in the db. Translations are stored under Projects/<project id>/Translations/<sha1 hash>.

normalizeString(str: string): string;

Cleans a string that's used for translations. Performs the following: - Removes any leading/trailing whitespace - Removes spaces at the end of any line

loadTranslationsForLocale(locale: string, options?: LoadTranslationsOptions): Promise<LocaleTranslations>;

Loads translations for a particular locale.

The locale is expanded through the fallback chain configured in i18n.fallbacks (ending with i18n.defaultLocale), so a string missing a translation for the locale falls through to its fallbacks before the source string is used.

Returns a map like:

{
  "Hello": "Bonjour",
}
getDataSource(dataSourceId: string): Promise<DataSource | null>;

Returns a data source configuration object.

archiveDataSource(dataSourceId: string, options?: {
    archivedBy?: string;
}): Promise<void>;

Archives a data source. Archived data sources cannot be synced or published.

unarchiveDataSource(dataSourceId: string): Promise<void>;

Unarchives a data source.

syncDataSource(dataSourceId: string, options?: {
    syncedBy?: string;
}): Promise<void>;

Syncs a data source to draft state.

publishDataSource(dataSourceId: string, options?: {
    publishedBy?: string;
}): Promise<void>;
unpublishDataSource(dataSourceId: string): Promise<void>;

Unpublishes a data source. Removes the publishedAt/publishedBy metadata from the DataSource doc and deletes the Data/published doc.

publishDataSources(dataSourceIds: string[], options?: {
    publishedBy: string;
    batch?: WriteBatch;
    commitBatch?: boolean;
}): Promise<void>;
getFromDataSource<T = any>(dataSourceId: string, options?: {
    mode?: 'draft' | 'published';
}): Promise<DataSourceData<T> | null>;

Fetches data from a data source.

dbDataSourceDataPath(dataSourceId: string, options: {
    mode: 'draft' | 'published';
}): string;

Firestore path for a datasource data.

dbDataSourceDataRef(dataSourceId: string, options: {
    mode: 'draft' | 'published';
}): DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;

Firestore doc ref for a datasource data.

getUserAcl(email: string): Promise<{
    exists: boolean;
    role: UserRole | null;
}>;

Looks up the user's entry in the project's ACL with a single Firestore read. exists is true if the user (or their domain's wildcard entry, e.g. *@example.com) is in the ACL; role may be null for entries without an assigned role.

getUserRole(email: string): Promise<UserRole | null>;

Gets the user's role from the project's ACL.

userExistsInAcl(email: string): Promise<boolean>;

Verifies user exists in the ACL list.

listActions(options?: ListActionsOptions): Promise<Action[]>;

Lists action logs from the database.

logAction(action: string, options?: {
    by?: string;
    metadata?: any;
    links?: {
        label: string;
        url: string;
        target?: string;
    }[];
}): Promise<void>;
sendEmail(options: SendEmailOptions): Promise<string>;

Queues an email in the Projects/${projectId}/Emails collection in firestore, which is processed by the Root.js email service (apps/root-services). Returns the id of the queued email doc.

Delivery is asynchronous: the email service sends pending emails when its /_/send_emails endpoint is called (typically via a cron). Pass the emailService option to notify the service right away.

getDependencyGraph(options: {
    mode: DocMode;
}): Promise<DependencyGraph>;

Returns the dependency graph for a given mode, which tracks reference field usages between docs. Requires the dependencyGraph option to be enabled on the cmsPlugin config (the graph is kept up to date by the CMS cron job).

Example:

const graph = await cmsClient.getDependencyGraph({mode: 'published'});
const depIds = graph.getDependencies(['Pages/index']);
// => ['Authors/alice', 'BlogPosts/hello-world', ...]
getDocDependencies(docIds: string | string[], options: {
    mode: DocMode;
    transitive?: boolean;
}): Promise<string[]>;

Returns the ids of the docs referenced by the given doc(s), i.e. the additional docs that need to be fetched when fetching the given docs. Dependencies are resolved transitively by default (pass transitive: false for direct references only).

Requires the dependencyGraph option to be enabled on the cmsPlugin config.

Example:

const depIds = await cmsClient.getDocDependencies(['Pages/index'], {
  mode: 'published',
});
const req = cmsClient.createBatchRequest({mode: 'published'});
req.addDoc('Pages/index');
depIds.forEach((docId) => req.addDoc(docId));
const res = await req.fetch();
createBatchRequest(options: BatchRequestOptions): BatchRequest;

Creates a batch request that is capable of fetching one or more docs, corresponding translations, and dataSources.

RootCMSClientOptionsinterface

interface RootCMSClientOptions

Options for constructing a RootCMSClient.

Members (4)
proposal?: Proposal;

An unapplied change proposal to overlay on top of every read. Use this to preview proposed content on a running site before accepting it.

user?: string;

Email (or other identifier) that writes are attributed to when a method is not passed one explicitly, e.g. sys.modifiedBy and action log entries. Defaults to root-cms-client.

logActions?: boolean;

Whether doc writes (saveDraftData(), updateDraftData(), setRawDoc(), publishDocs() and unpublishDocs()) record an entry in the project's action logs, the same way edits made from the CMS UI do. Defaults to true. Set to false to skip logging, e.g. for a one-off bulk migration.

cache?: ReadCache;

Caches published reads in memory (see ReadCache). Covers getDoc(), listDocs() (unless a query fn is used), loadTranslations() and batch requests. Draft reads are never cached, but loadTranslations() has no mode, so don't use a cached client to serve previews.

SaveDraftOptionsinterface

interface SaveDraftOptions
Members (3)
locales?: string[];

Locales to enable.

modifiedBy?: string;

Email of user modifying the doc. If blank, defaults to the client's user.

validate?: boolean;

Whether to validate fieldsData against the collection schema before saving. If validation fails, an error will be thrown with details about the validation errors.

SendEmailOptionsinterface

interface SendEmailOptions

Options for RootCMSClient.sendEmail().

Emails are queued in the Projects/${projectId}/Emails collection in firestore and delivered by the Root.js email service (apps/root-services), which sends pending emails using the App Engine Mail API.

Members (7)
to: string | string[];

Recipient email address(es).

from?: string;

Sender email address. The sender must be authorized to send email via the App Engine Mail API, e.g. noreply@<gcp-project-id>.appspotmail.com. Defaults to noreply@<gcp-project-id>.appspotmail.com.

subject: string;

Subject line.

body?: string;

Plain-text body. When omitted, a plain-text body is derived from htmlBody.

htmlBody?: string;

Optional HTML body.

expiresAt?: Date;

Optional expiration date. If the email is still unsent when the email service processes the queue after this date (e.g. the service was down), the email is marked as expired and skipped instead of being delivered late.

emailService?: string | boolean;

Email service used to trigger delivery immediately after the email is queued. Setting this to true uses the default hosted service at https://services.rootjs.dev. Set to a base URL to use a self-hosted deployment of the service (apps/root-services). When unset, the email remains queued until the email service's cron next processes the queue.

SetDocOptionsinterface

interface SetDocOptions
Members (1)
mode: DocMode;

Mode, either "draft" or "published".

SyncAssetToDocsOptionsinterface

interface SyncAssetToDocsOptions

Options for RootCMSClient.syncAssetToDocs().

Members (2)
previousFile?: UploadedFile;

The asset's file data before the change. Used to detect doc-level alt text and canvas bg color customizations, which are preserved.

modifiedBy?: string;

Email of the user making the change. Defaults to the client's user.

toArrayObjectfunction

function toArrayObject(arr: any[]): ArrayObject;

Serializes an array into an ArrayObject, e.g.:

marshalArray([1, 2, 3])
// => {a: 1, b: 2, c: 3, _array: ['a', 'b', 'c']}

This database storage method makes it easier to update a single field in a deeply nested array object.

Translationinterface

interface Translation
Members (1)
source: string;

translationsForLocalefunction

function translationsForLocale(translationsMap: TranslationsMap, locale: string | string[]): LocaleTranslations;

Converts a translations map from loadTranslations() to a map of source to translated string for a particular locale.

The locale is either a single locale (e.g. "fr"), which falls back to en and then the source string, or an ordered fallback chain (e.g. ["en-CA", "en-GB", "en"]) where the first locale with a translation wins before falling back to the source string. Use resolveLocaleFallbacks() to build the chain from the i18n.fallbacks config:

const fallbackLocales = resolveLocaleFallbacks(rootConfig.i18n, 'en-CA');
const translations = translationsForLocale(translationsMap, fallbackLocales);

Returns a map like:

{
  "Hello": "Bonjour",
}

TranslationsMapinterface

interface TranslationsMap

unmarshalArrayfunction

function unmarshalArray(arrObject: ArrayObject): any[];

Converts an ArrayObject to a normal array.

unmarshalDatafunction

function unmarshalData(data: any): any;

Walks the data tree and converts any Timestamp objects to millis and any _array maps to normal arrays.

E.g.:

normalizeData({ sys: {modifiedAt: Timestamp(123)}, fields: { _array: ['asdf'], asdf: {title: 'hello'} } }) // => {sys: {modifiedAt: 123}, fields: {foo: [{title: 'hello'}]}}

UpdateDraftOptionsinterface

interface UpdateDraftOptions
Members (2)
modifiedBy?: string;

Email of user modifying the doc. If blank, defaults to the client's user.

validate?: boolean;

Whether to validate the updated field against the collection schema. If validation fails, an error will be thrown with details about the validation errors.

UploadAssetOptionsinterface

interface UploadAssetOptions

Options for RootCMSClient.uploadAsset().

Members (10)
folder?: string;

Asset library folder to upload into, e.g. marketing/q1. Missing folders are created. Defaults to the root folder. Ignored when assetId is set.

name?: string;

Asset display name. Defaults to the filename. If the folder already contains a file with this name, that asset's file is replaced.

filename?: string;

The uploaded file's name, e.g. hero.png. Defaults to the basename of the file path. Required when uploading raw bytes.

assetId?: string;

Id of an existing asset whose file should be replaced, regardless of its name or folder.

alt?: string;

Alt text for the file. When replacing a file and this is omitted, the asset's existing alt text is kept.

namingMode?: 'hash' | 'hash-path' | 'clean';

GCS object naming strategy. Defaults to hash ({hash}.{ext}), or hash-path ({hash}/{filename}) when the destination folder has the "preserve filename" setting enabled. clean keeps the original filename and can overwrite existing files.

cacheControl?: string;

Cache-Control header for the GCS object. Defaults to 365 days.

disableGci?: boolean;

Skips registering images with the Google Cloud Image service.

syncDocs?: boolean;

Whether to fan out a replaced file to the draft docs that use the asset. Defaults to true.

modifiedBy?: string;

Email of the user making the change. Defaults to the client's user.

UploadAssetResultinterface

interface UploadAssetResult

Result of RootCMSClient.uploadAsset().

Members (3)
asset: Asset;

The created or updated asset.

status: 'created' | 'replaced';

Whether a new asset was created or an existing asset's file was replaced.

sync?: AssetSyncResult;

Docs updated with the new file, when an existing asset was replaced.

UploadedFileinterface

interface UploadedFile

File data stored in image/file fields and on asset library files.

Members (12)
src: string;
filename?: string;
gcsPath?: string;
width?: number;
height?: number;
alt?: string;
uploadedBy?: string;
uploadedAt?: string | number;
canvasBgColor?: 'light' | 'dark';
altDisabled?: boolean;

When true, alt text handling is disabled for the file (e.g. decorative images). The alt text input is hidden in docs that use the file and no alt text is propagated from the asset library.

originalSrc?: string;

The original source URL if the image has been edited.

assetId?: string;

When the file was selected from the asset library, the id of the asset at Projects/<projectId>/Assets/<assetId>. Updates to the asset fan out to docs that embed it (see ui/utils/assets.ts and core/assets.ts).

UserRoletype

type UserRole = 'ADMIN' | 'EDITOR' | 'CONTRIBUTOR' | 'VIEWER';

verifyPasswordfunction

function verifyPassword(stored: PasswordHash | null | undefined, password: string): Promise<boolean>;

Verifies a candidate password against a stored PasswordHash.

Returns false (never throws) when the stored value is missing or malformed, so callers can pass a field value straight from a doc.

const ok = await verifyPassword(doc.fields.password, req.body.password);

@blinkk/root-cms/functions

import {…} from '@blinkk/root-cms/functions';

cronfunction

function cron(options?: CronOptions): import("firebase-functions/scheduler").ScheduleFunction;

Runs offline CMS tasks, such as publishing of scheduled docs.

CronOptionsinterface

interface CronOptions
Members (2)
rootDir?: string;
scheduleOptions?: Partial<ScheduleOptions>;

@blinkk/root-cms/plugin

import {…} from '@blinkk/root-cms/plugin';

AiConfiginterface

interface AiConfig

Full AI config registered on the cms plugin.

Members (6)
models: AiModelConfig[];

Models exposed in the model picker. The first entry is the default.

defaultModel?: string;

Id of the default model. Defaults to the first model in models.

imageModels?: AiModelConfig[];

Image generation models. Used by the image generator and any other features that produce images. Supported providers are openai (e.g. gpt-image-*), and google or google-vertex with a Gemini image model (e.g. gemini-3-pro-image-preview). Imagen models are not supported.

defaultImageModel?: string;

Id of the default image model. Defaults to the first entry in imageModels.

systemPrompt?: string;

Optional system prompt prepended to every conversation. If a ROOT.md file exists at the project root, its contents are appended to this prompt automatically.

maxSteps?: number;

Maximum tool-loop steps before stopping. Defaults to 10.

AiModelConfiginterface

interface AiModelConfig

Configuration for a single chat model.

Inspired by Ollama's Modelfile and LiteLLM's model config: each entry maps a CMS-facing id to a provider, model id and credentials.

Members (11)
id: string;

Stable id used by the CMS UI and stored alongside chat history.

label?: string;

Optional human-readable label rendered in the model picker.

description?: string;

Optional description shown under the label.

provider: AiProvider;

AI provider/family to route requests to.

modelId?: string;

Provider-specific model id (e.g. gpt-4o, claude-opus-4-5, gemini-2.5-pro). Defaults to id if omitted.

apiKey?: string;

API key for the provider. For google-vertex this is an optional express-mode API key; omit it to authenticate with Google Cloud Application Default Credentials instead.

baseURL?: string;

Override the provider's base URL (required for openai-compatible).

headers?: Record<string, string>;

Custom headers to send with each request.

project?: string;

Google Cloud project id (google-vertex only). Defaults to the CMS's firebaseConfig.projectId, then to the Application Default Credentials project.

location?: string;

Vertex AI location (google-vertex only), e.g. us-central1. Defaults to global.

capabilities?: AiModelCapabilities;

Capabilities advertised to the UI.

AiProvidertype

type AiProvider = 'openai' | 'openai-compatible' | 'anthropic' | 'google' | 'google-vertex';

Provider type for an AI model. Use openai-compatible for any OpenAI-style endpoint (e.g. local Ollama, vLLM, OpenRouter). Use google-vertex to call Gemini through Vertex AI with Google Cloud credentials instead of a Gemini API key.

buildCommentEmailTemplateDatafunction

function buildCommentEmailTemplateData(action: Action, ctx: NotificationServiceContext, options?: Pick<CommentEmailNotificationsOptions, 'cmsUrl'>): CommentEmailTemplateData | null;

Builds the template data for a comment action. Returns null when the action isn't a field comment action or is missing required metadata.

CheckContextinterface

interface CheckContext

Context passed to a check function during execution.

Members (6)
rootConfig: RootConfig;

The Root.js config.

cmsClient: RootCMSClient;

The Root CMS client for accessing the database.

docId: string;

The document ID, e.g. Pages/index.

collectionId: string;

The collection ID, e.g. Pages.

slug: string;

The slug, e.g. index.

collectionSchema: schema.Collection | null;

The collection schema.

CheckResultinterface

interface CheckResult

Result returned by a check function after execution.

Members (3)
status: CheckStatus;

Whether the check succeeded, warned, or failed.

message: string;

A message describing the result. Supports markdown.

metadata?: Record<string, any>;

Optional metadata to include with the result.

CheckStatustype

type CheckStatus = 'success' | 'warning' | 'error';

The result status of a check.

CMSAIConfiginterface

interface CMSAIConfig

Deprecated. The `experiments.ai` flag is now used only as a sidebar toggle. Configure chat models on the top-level `ai` plugin option instead.

Members (2)
endpoint?: string;

Custom API endpoint for chat prompts.

model?: string;

Gen AI model to use.

CMSBuiltInSidebarTooltype

type CMSBuiltInSidebarTool = 'home' | 'content' | 'releases' | 'data' | 'assets' | 'translations' | 'ai' | 'settings';

Built-in sidebar tools that can be toggled on/off in the CMS UI.

CMSCheckinterface

interface CMSCheck

Configuration for defining a CMS check.

Members (5)
id: string;

Unique ID for the check.

label: string;

Human-readable label displayed in the UI.

description?: string;

Optional description explaining what the check does.

collections?: string[];

Optional list of collection IDs to restrict this check to. When set, the check is only shown and runnable for documents belonging to one of the listed collections. When omitted, the check applies to all collections.

run: (ctx: CheckContext) => Promise<CheckResult>;

Function that runs the check on the server-side and returns a result.

CMSDependencyGraphConfiginterface

interface CMSDependencyGraphConfig

Configuration options for the dependency graph. Pass true (or an empty object) to the dependencyGraph plugin option to enable the feature with default options.

Members (2)
includeCollections?: string[];

Collections to track in the dependency graph. If specified, only docs in these collections are scanned for outgoing references. If unset, all collections are scanned (subject to excludeCollections).

Note that these filters only control which docs are scanned for outgoing references — referenced doc ids are always recorded as-is, even when the referenced doc lives in a collection that is not scanned.

excludeCollections?: string[];

Collections to exclude from the dependency graph. Applied after includeCollections.

CMSNotificationServiceinterface

interface CMSNotificationService extends CMSService

Configuration for defining a CMS notification service.

Notification services react to actions in the CMS (publishes, schema changes, comments, etc.) and dispatch them to an external channel. Initially this is intended for email, with Slack, webhooks, and other transports planned. See emailNotifications() for a built-in email implementation backed by the Root.js email service.

Multiple notification services may be registered; each independently decides whether and how to handle a given action.

Example:

cmsPlugin({
  services: {
    notifications: [
      {
        id: 'sendgrid',
        label: 'SendGrid',
        onAction: async (ctx, action) => {
          if (action.action === 'doc.publish') {
            await sendgrid.send({ ... });
          }
        },
      },
    ],
  },
});
Members (1)
onAction?: (ctx: NotificationServiceContext, action: Action) => Promise<void | NotificationResult>;

Async function called when an action occurs in the CMS. Receives the action and may dispatch a notification (e.g. send email, post to Slack) via the service's underlying transport. Can optionally return a NotificationResult describing delivery status.

cmsPluginfunction

function cmsPlugin(options: CMSPluginOptions): CMSPlugin;

CMSPlugintype

type CMSPlugin = Plugin & {
    name: 'root-cms';
    getConfig: () => CMSPluginOptions;
    getFirebaseApp: () => App;
    getFirestore: (options?: {
        databaseId?: string;
    }) => Firestore;
};

CMSPluginOptionstype

type CMSPluginOptions = {
    id?: string;
    name?: string;
    firebaseConfig: {
        [key: string]: string | undefined;
        apiKey: string;
        authDomain: string;
        projectId: string;
        storageBucket: string;
        databaseId?: string;
    };
    gapi?: {
        apiKey: string;
        clientId: string;
    };
    cookieSecret?: string | string[];
    isUserAuthorized?: (req: Request, user: CMSUser) => boolean | Promise<boolean>;
    isLoginRequired?: (req: Request) => boolean;
    allowedIframeOrigins?: string[];
    gci?: string | boolean;
    sidebar?: {
        tools?: Record<string, CMSSidebarTool>;
        hiddenBuiltInTools?: CMSBuiltInSidebarTool[];
    };
    favicon?: string;
    minimalBranding?: boolean;
    themes?: CMSTheme[];
    defaultTheme?: string;
    onAction?: (action: Action) => any;
    ai?: AiConfig;
    experiments?: {
        ai?: boolean | CMSAIConfig;
        v2TranslationsManager?: boolean;
        taskManager?: boolean;
    };
    logLevel?: 'info' | 'warn' | 'error' | 'silent' | 'debug';
    watch?: boolean;
    preview?: {
        channel: boolean | 'to-preview' | 'from-preview';
    };
    checks?: CMSCheck[];
    translations?: CMSTranslationService[];
    notifications?: CMSNotificationService[];
    searchIndex?: CMSSearchIndexConfig;
    dependencyGraph?: boolean | CMSDependencyGraphConfig;
    defaultOneOfVariant?: 'dropdown' | 'picker';
    excludeLocalesFromTranslations?: string[];
};

CMSSearchIndexConfiginterface

interface CMSSearchIndexConfig

Filters applied to the spotlight / global search indexer. By default every collection and every doc is indexed. Use these options to scope the index to a subset of the project's content.

Doc ids use the <collectionId>/<slug> format (e.g. Pages/about), which matches the format used elsewhere in the CMS.

Members (4)
includeCollections?: string[];

Collections to include in the search index. If specified, only these collections are indexed. If unset, all collections are indexed (subject to excludeCollections).

excludeCollections?: string[];

Collections to exclude from the search index. Applied after includeCollections.

includeDocIds?: string[];

Doc ids to include in the search index. If specified, only these docs are indexed (within collections that pass the collection filter). Format: <collectionId>/<slug>.

excludeDocIds?: string[];

Doc ids to exclude from the search index. Applied after includeDocIds. Format: <collectionId>/<slug>.

CMSServiceinterface

interface CMSService

Base shape for a service registered with the CMS plugin.

Services are extension points that let plugins provide capabilities (e.g. email delivery, cache, translations) that root-cms can call into. Every service shares the same identifying fields — id, label, optional icon — and is registered via the services option on cmsPlugin().

Concrete service interfaces (e.g. CMSEmailService, CMSTranslationService) extend this base and add the handler functions specific to their capability.

Members (3)
id: string;

Unique ID for the service (e.g. 'sendgrid', 'crowdin').

label: string;

Human-readable label displayed in the UI (e.g. "SendGrid").

icon?: string;

Optional icon URL displayed in the UI next to the label. Similar to the sidebar tools icon option, this should be a URL to an image.

CMSServiceContextinterface

interface CMSServiceContext

Base context passed to service handler functions. Concrete services may extend this with additional fields specific to the call site (e.g. the translation context adds docId, collectionId, etc.).

Members (3)
rootConfig: RootConfig;

The Root.js config.

cmsClient: RootCMSClient;

The Root CMS client for accessing the database.

user?: {
    email: string;
};

Email of the user that triggered the action, if any. Some service calls are initiated by the system rather than a user, in which case this is undefined.

CMSSidebarToolinterface

interface CMSSidebarTool
Members (5)
icon?: string;

Sidebar icon URL. For consistency with other icons used by Root CMS, we recommend picking an icon from https://tabler.io/icons with the stroke weight set to "1.5". Paste the "Data URI" or "Base64 Data URI" from the icon's download modal.

label?: string;

Label.

iframeUrl?: string;

Iframe URL to render for the tool.

cmsUrl?: string;

CMS URL that should be opened when the tool is selected. The url must start with /cms/. Use this to create shortcuts to CMS pages instead of rendering the tool inside an iframe.

externalUrl?: string;

External URL that should open in a new tab when the tool is selected.

CMSThemeinterface

interface CMSTheme

A theme for the CMS UI: a stylesheet loaded after the CMS's own, styling whatever the CSS reaches. Themes are registered by the project (a plugin or a package can export one); each user chooses which of them to use.

Members (4)
id: string;

Identifies the theme in config, in the URL and in a user's preference.

name?: string;

Shown in the theme picker. Defaults to the id.

file?: string;

Path to a CSS file, relative to the project root (root.config.ts).

css?: string;

CSS appended after the file, or the whole theme on its own.

CMSTranslationServiceinterface

interface CMSTranslationService extends CMSService

Configuration for defining a CMS translation service.

Members (2)
onImport?: (ctx: TranslationServiceContext, data: TranslationRow[]) => Promise<TranslationRow[] | TranslationImportResult>;

Async function to import translations from the service. Should return an array of translation rows that will be merged into the CMS translations database, or a TranslationImportResult object to display a notification without importing any rows.

onExport?: (ctx: TranslationServiceContext, data: TranslationRow[]) => Promise<void | TranslationExportResult>;

Async function to export translations to the service. Receives the current translation rows for the document. Can optionally return an object with a message to display in the success notification.

CMSUserinterface

interface CMSUser
Members (2)
email: string;
role?: UserRole | null;

The user's role within the project's ACL, or null if unassigned. This is populated on req.user for every authenticated request.

CollectionPublishingOptionsinterface

interface CollectionPublishingOptions

Publishing options configured on a collection.

Members (1)
checks?: Array<string | PublishCheckConfig>;

Checks to run before docs in this collection are published or scheduled. Accepts either a check ID (which defaults to the required level) or a {id, level} config object.

commentEmailNotificationsfunction

function commentEmailNotifications(options?: CommentEmailNotificationsOptions): CMSNotificationService;

Creates a CMSNotificationService that emails users when field comments are added, resolved, or reopened. By default the recipients are the users @mentioned in the comment plus everyone who previously commented on the same field, excluding the user performing the action.

Emails are queued in Projects/{projectId}/Emails and delivered by the Root.js email service, the same as emailNotifications().

Example:

cmsPlugin({
  notifications: [
    commentEmailNotifications({
      emailService: true,
      // Also notify a shared inbox for comments on the "Pages" collection.
      to: (data) => (data.collectionId === 'Pages' ? ['web@example.com'] : []),
    }),
  ],
});

CommentEmailNotificationsOptionsinterface

interface CommentEmailNotificationsOptions

Options for the commentEmailNotifications service.

Members (14)
id?: string;

Unique ID for the service. Defaults to 'comment-email'.

label?: string;

Human-readable label displayed in the UI. Defaults to 'Comment emails'.

icon?: string;

Optional icon URL displayed in the UI next to the label.

actions?: FieldCommentAction[];

Comment actions that trigger an email. Defaults to new comments, resolves and reopens (doc.comment.add, doc.comment.resolve, doc.comment.reopen).

notifyMentions?: boolean;

Whether to notify users mentioned in the comment via @mention. Defaults to true.

notifyParticipants?: boolean;

Whether to notify previous participants of the thread (everyone who has commented on the field). Defaults to true.

notifySelf?: boolean;

Whether to include the user that performed the action in the recipients. Defaults to false.

to?: string[] | ((data: CommentEmailTemplateData, ctx: NotificationServiceContext) => string[] | Promise<string[]>);

Additional recipients, either a static list or a function returning the list for a given action (e.g. doc watchers). Merged with the mentioned users and participants.

filter?: (data: CommentEmailTemplateData, ctx: NotificationServiceContext) => boolean | Promise<boolean>;

Optional filter called before sending. Return false to skip the notification, e.g. to ignore comments on certain collections.

from?: string;

Sender email address. Defaults to noreply@<gcp-project-id>.appspotmail.com.

cmsUrl?: string;

Base URL of the site hosting the CMS, used to build links to the commented field (e.g. https://example.com). Defaults to rootConfig.domain.

template?: EmailNotificationTemplate | ((data: CommentEmailTemplateData, ctx: NotificationServiceContext) => EmailNotificationTemplate | Promise<EmailNotificationTemplate>);

Custom email content. Either {placeholder} string templates resolved against CommentEmailTemplateData (e.g. {fieldLabel}, {by}, {content}, {url}) or a function returning the final email. When unset, a default subject and body describing the comment are used.

emailService?: string | boolean;

Email service used to trigger delivery immediately after the email is queued. See emailNotifications() for details.

expireAfterMinutes?: number;

Number of minutes after which an unsent email expires.

CommentEmailTemplateDatainterface

interface CommentEmailTemplateData

Template data passed to CommentEmailNotificationsOptions.template.

Members (13)
action: Action<FieldCommentActionMetadata>;

The action log entry that triggered the notification.

actionName: FieldCommentAction;

Action name, e.g. doc.comment.add.

by: string;

Email of the user that performed the action.

docId: string;

Doc id in the form <collection>/<slug>.

collectionId: string;
slug: string;
fieldKey: string;

Deep key of the commented field.

fieldLabel: string;

Human-readable label of the field, falling back to the deep key.

content: string;

Plain-text content of the comment (empty for resolve/reopen actions).

mentions: string[];

Lower-cased emails mentioned in the comment.

participants: string[];

Lower-cased emails of everyone who has commented on the thread.

summary: string;

Short human-readable description of what happened.

url: string;

Link to the field in the CMS, built from rootConfig.domain (or the cmsUrl option). Empty when neither is configured.

DEFAULT_COMMENT_EMAIL_TEMPLATEvariable

const DEFAULT_COMMENT_EMAIL_TEMPLATE: Readonly<Required<EmailNotificationTemplate>>;

Default subject and body templates used by commentEmailNotifications.

DEFAULT_EMAIL_TEMPLATEvariable

const DEFAULT_EMAIL_TEMPLATE: Readonly<Required<EmailNotificationTemplate>>;

Default templates used by emailNotifications when no custom template content is provided. Exported so custom templates can compose with the defaults, e.g. template: {...DEFAULT_EMAIL_TEMPLATE, subject: 'Custom subject'}.

emailNotificationsfunction

function emailNotifications<T = any>(options: EmailNotificationsOptions<T>): CMSNotificationService;

Creates a CMSNotificationService that sends email notifications when actions occur in the CMS (publishes, schema changes, etc.).

Emails are queued in the Projects/${projectId}/Emails collection in firestore and delivered by the Root.js email service (apps/root-services) using the App Engine Mail API.

Example:

cmsPlugin({
  notifications: [
    emailNotifications({
      actions: ['doc.publish', 'release.*'],
      to: ['cms-alerts@example.com'],
      template: {
        subject: '[cms] {metadata.docId} published by {by}',
      },
      emailService: true,
    }),
  ],
});

EmailNotificationsOptionsinterface

interface EmailNotificationsOptions<T = any>

Options for the emailNotifications service.

Members (11)
id?: string;

Unique ID for the service. Defaults to 'email'.

label?: string;

Human-readable label displayed in the UI. Defaults to 'Email'.

icon?: string;

Optional icon URL displayed in the UI next to the label.

to: string[] | ((action: Action, ctx: NotificationServiceContext) => string[] | Promise<string[]>);

Recipients of the email notification. Either a static list of email addresses or a function that returns the recipients for a given action (e.g. to look up watchers of a doc). Returning an empty list skips the notification.

from?: string;

Sender email address. The sender must be authorized to send email via the App Engine Mail API, e.g. noreply@<gcp-project-id>.appspotmail.com. Defaults to noreply@<gcp-project-id>.appspotmail.com.

actions?: string[];

Actions that trigger an email notification, e.g. ['doc.publish', 'release.*']. Patterns support wildcards, where * matches any number of characters and ? matches a single character. Matching is case-insensitive. When unset, all actions trigger an email notification.

filter?: (action: Action, ctx: NotificationServiceContext) => boolean | Promise<boolean>;

Optional filter called after the actions patterns match. Return false to skip the notification (e.g. to ignore actions performed by certain users).

transformData?: (action: Action, ctx: NotificationServiceContext) => T | Promise<T>;

Optional transformation applied to the action log data before the email templates are rendered. The returned value is passed to the templates as the template data. When unset, the action log data object is used as-is.

template?: EmailNotificationTemplate | ((data: T, action: Action, ctx: NotificationServiceContext) => EmailNotificationTemplate | Promise<EmailNotificationTemplate>);

Templates used to render the email. Either an object with {placeholder} string templates or a function that returns the final email content for full control. Function results are used verbatim, i.e. no placeholder rendering or HTML escaping is applied.

A missing subject falls back to the default subject template. The default body and html templates (see DEFAULT_EMAIL_TEMPLATE) apply only when neither body nor html is provided. Providing only body sends a plain-text-only email; providing only html derives the plain-text body from the html.

emailService?: string | boolean;

Email service used to trigger delivery immediately after the email is queued. Setting this to true uses the default hosted service at https://services.rootjs.dev. Set to a base URL to use a self-hosted deployment of the service (apps/root-services). When unset, the email remains queued until the email service's cron next processes the queue.

expireAfterMinutes?: number;

Number of minutes after which an unsent email expires. Expired emails are skipped by the email service instead of being delivered late. When unset, queued emails never expire.

EmailNotificationTemplateinterface

interface EmailNotificationTemplate

Email content used by the emailNotifications service.

When provided as string templates (via the template option), each value supports {placeholder} tokens that are resolved against the template data — the action log data object, or the result of transformData() when configured. Tokens support dot notation for nested values, e.g. {metadata.docId}. Unknown placeholders are left untouched.

Members (3)
subject?: string;

Subject line.

body?: string;

Plain-text body.

html?: string;

HTML body. Values injected into an HTML string template are HTML-escaped. Omit (while providing body) to send a plain-text-only email.

FieldCommentinterface

interface FieldComment

A single entry in a field comment thread.

Members (12)
id: string;

Unique id of the entry within the thread.

type?: FieldCommentType;

Kind of entry. Defaults to comment when missing.

body?: RichTextData | null;

Rich text body of the comment.

content?: string;

Plain-text rendering of the body, used for previews and notifications.

mentions?: string[];

Lower-cased emails of users mentioned via @mention in the body.

createdAt: CommentTimestamp;
createdBy: string;
updatedAt?: CommentTimestamp;
updatedBy?: string;
deleted?: boolean;

Set when the comment was deleted by its author. The body is cleared.

deletedAt?: CommentTimestamp;
deletedBy?: string;

FieldCommentActiontype

type FieldCommentAction = (typeof FIELD_COMMENT_ACTIONS)[keyof typeof FIELD_COMMENT_ACTIONS];

FieldCommentActionMetadatainterface

interface FieldCommentActionMetadata

Metadata attached to field comment actions in the action log. Notification services (e.g. commentEmailNotifications()) read these values to decide who to notify and what to say.

Members (10)
docId: string;
collectionId: string;
slug: string;
fieldKey: string;
fieldLabel?: string;
threadId: string;
commentId?: string;

Id of the comment entry, for add, edit and delete actions.

content?: string;

Plain-text content of the comment, truncated for the log.

mentions?: string[];

Lower-cased emails of users mentioned in the comment.

participants?: string[];

Lower-cased emails of everyone who has commented on the thread.

FieldCommentThreadinterface

interface FieldCommentThread

A thread of comments attached to a single field of a doc.

Members (13)
id: string;

Thread id. Open threads use the id derived from the field key; resolved threads carry a timestamp suffix.

docId: string;

Doc id in the form <collection>/<slug>.

fieldKey: string;

Deep key of the field within the doc, e.g. fields.hero.title.

fieldLabel?: string;

Human-readable label of the field at the time of the first comment.

status: FieldCommentThreadStatus;
comments: FieldComment[];

Chronological history of comments and status changes.

participants: string[];

Lower-cased emails of everyone who has commented on the thread.

createdAt: CommentTimestamp;
createdBy: string;
updatedAt?: CommentTimestamp;
updatedBy?: string;
resolvedAt?: CommentTimestamp | null;
resolvedBy?: string | null;

FieldCommentThreadStatustype

type FieldCommentThreadStatus = 'open' | 'resolved';

Lifecycle status of a field comment thread.

isPlaceholderFirebaseProjectfunction

function isPlaceholderFirebaseProject(firebaseConfig?: Record<string, any>): boolean;

Returns true if the Firebase project ID is still the placeholder from the starter template (e.g. YOUR_FIREBASE_PROJECT_ID), i.e. the project hasn't been connected to a Firebase project yet.

NotificationResultinterface

interface NotificationResult

Result returned by a notification service onAction handler.

Members (2)
status?: 'success' | 'info' | 'error';

Delivery status. - 'success': the notification was delivered. - 'error': delivery failed; message should describe why. - 'info' (default): the service handled the action but did not deliver a notification (e.g. the action was filtered out).

message?: string;

Optional human-readable message describing the result.

NotificationServiceContextinterface

interface NotificationServiceContext extends CMSServiceContext

Context passed to notification service handler functions.

PublishCheckConfiginterface

interface PublishCheckConfig

A check configured to run as part of a collection's publishing flow.

Members (2)
id: string;

ID of a check registered via cmsPlugin({checks: [...]}).

level?: PublishCheckLevel;

Severity level for the check. Defaults to required.

PublishCheckLeveltype

type PublishCheckLevel = 'required' | 'warning';

Severity level for a check configured on a collection.

- required: an error result halts publishing. - warning: publishing continues, and the message is surfaced afterwards.

PublishCheckResultinterface

interface PublishCheckResult

The result of running a single check against a single doc.

Members (7)
docId: string;

The doc the check ran against, e.g. Pages/index.

checkId: string;

ID of the check that produced this result.

label: string;

Human-readable label for the check.

level: PublishCheckLevel;

The severity level the check was configured with.

status: PublishCheckStatus;

Outcome of the check run.

message: string;

A message describing the result. Supports markdown.

metadata?: Record<string, any>;

Optional metadata returned by the check.

PublishCheckStatustype

type PublishCheckStatus = 'success' | 'warning' | 'error';

The result status of a check.

renderEmailTemplatefunction

function renderEmailTemplate(template: string, data: any, options?: {
    escapeHtml?: boolean;
}): string;

Renders a {placeholder} template string using values from data. Placeholders support dot notation for nested lookups, e.g. {metadata.docId}. Unknown placeholders are left untouched. Values are stringified based on their type: firestore timestamps and dates are converted to ISO strings and objects are converted to formatted JSON. When escapeHtml is set, values are HTML-escaped before injection.

RootLocaletype

type RootLocale = string;

A site-defined locale identifier, as configured in i18n.locales. Root is agnostic to its format.

TranslationExportResultinterface

interface TranslationExportResult

Result returned by an onExport handler.

Members (4)
title?: string;

Optional title displayed in the notification after export.

message?: string;

Optional message displayed in the notification after export.

link?: {
    url: string;
    label?: string;
};

Optional link displayed in the notification (e.g. to the translation service).

status?: 'success' | 'info' | 'error';

Notification status controlling the color of the notification. - 'success': green - 'error': red - 'info' (default): neutral

TranslationImportResultinterface

interface TranslationImportResult

Result returned by an onImport handler when no rows are imported.

Members (4)
title?: string;

Optional title displayed in the notification after import.

message?: string;

Optional message displayed in the notification after import.

link?: {
    url: string;
    label?: string;
};

Optional link displayed in the notification (e.g. to the translation service).

status?: 'success' | 'info' | 'error';

Notification status controlling the color of the notification. - 'success': green - 'error': red - 'info' (default): neutral

TranslationLanguagetype

type TranslationLanguage = string;

The language identifier used by translation systems (CSV/Sheets columns, translation services, CMS translations pages), mapped from a root locale via i18n.translationLanguages. Defaults to the root locale id.

TranslationRowinterface

interface TranslationRow

A row of translation data keyed by translation language.

Members (3)
source: string;

The source string.

translations: Record<TranslationLanguage, string>;

Map of translation language to translated string. Translation languages are derived from the project's locales via the i18n.translationLanguages config in root.config.ts; when no mapping is configured, the keys are the locale ids themselves.

description?: string;

Optional translator notes/context for the source string.

translationsCheckfunction

function translationsCheck(options?: TranslationsCheckOptions): CMSCheck;

A first-party CMS check that verifies all translatable strings in a document have translations for each of the document's enabled locales.

TranslationsCheckOptionsinterface

interface TranslationsCheckOptions
Members (1)
collections?: string[];

Optional list of collection IDs to restrict this check to. When set, the check is only shown and runnable for documents in these collections.

TranslationServiceContextinterface

interface TranslationServiceContext extends CMSServiceContext

Context passed to translation service import/export functions.

Members (6)
docId: string;

The document ID, e.g. Pages/index.

collectionId: string;

The collection ID, e.g. Pages.

slug: string;

The slug, e.g. index.

locales: RootLocale[];

The locales configured for the project.

translationLanguages: TranslationLanguage[];

The translation languages for the project, derived from locales via the i18n.translationLanguages config in root.config.ts (deduped, since multiple locales may share a translation language). Equal to locales when no mapping is configured. The translations keys in TranslationRow data use these languages.

user: {
    email: string;
};

The email of the user performing the action.

@blinkk/root-cms/project

import {…} from '@blinkk/root-cms/project';

convertOneOfTypesfunction

function convertOneOfTypes(collection: schema.Collection, schemaModules?: Record<string, SchemaModule>): schema.Collection;

Converts all oneof field type definitions into a map keyed by the type name. The field definitions are replaced with an array of type names.

String references (used for self-referencing schemas) are resolved from the project's schema modules. SchemaPatterns are resolved by matching file paths.

Schemas pulled in via SchemaPattern or string-name reference are always deep-cloned before being walked. The walk rewrites oneof fields in place, and skipping the clone would mutate the shared entries in SCHEMA_MODULES and corrupt subsequent calls (e.g. when building multiple collections).

getCollectionSchemafunction

function getCollectionSchema(collectionId: string): schema.Collection | null;

Returns a collection's schema definition as defined in /collections/<id>.schema.ts.

getProjectSchemasfunction

function getProjectSchemas(): Record<string, schema.Schema>;

Returns a map of all schema.ts files defined in the project as fileId => schema. This is used by generate-types.ts to build the root-cms.d.ts file and by the CMS app to list collections.

resolveOneOfPatternsfunction

function resolveOneOfPatterns(schemaObj: schema.Schema, schemaModules?: Record<string, SchemaModule>): schema.Schema;

Resolves SchemaPattern objects in oneOf fields to arrays of Schema objects. This is needed for type generation which expects field.types to be an array.

SCHEMA_MODULESvariable

const SCHEMA_MODULES: Record<string, SchemaModule>;

SchemaModuleinterface

interface SchemaModule
Members (1)
default: schema.Schema;

@blinkk/root-cms/richtext

import {…} from '@blinkk/root-cms/richtext';

RichTextfunction

function RichText(props: RichTextProps): import("preact").JSX.Element | null;
namespace RichText {
    var Block: (props: RichTextBlock) => import("preact").JSX.Element | null;
    var ParagraphBlock: (props: RichTextParagraphBlockProps) => import("preact").JSX.Element | null;
    var HeadingBlock: (props: RichTextHeadingBlockProps) => import("preact").JSX.Element | null;
    var ListBlock: (props: RichTextListBlockProps) => import("preact").JSX.Element | null;
    var ImageBlock: (props: RichTextImageBlockProps) => import("preact").JSX.Element | null;
    var HtmlBlock: (props: RichTextHtmlBlockProps) => import("preact").JSX.Element | null;
    var TableBlock: (props: RichTextTableBlockProps) => import("preact").JSX.Element | null;
}

Renders data from the "richtext" field.

RichTextBlockinterface

interface RichTextBlock
Members (2)
type: string;
data?: any;

RichTextBlockComponenttype

type RichTextBlockComponent = RichTextComponent;

RichTextComponenttype

type RichTextComponent = FunctionalComponent<any>;

RichTextComponentMaptype

type RichTextComponentMap = Record<string, RichTextComponent>;

RichTextContextvariable

const RichTextContext: import("preact").Context<RichTextContextProps>;

RichTextContextPropsinterface

interface RichTextContextProps
Members (2)
components?: RichTextComponentMap;

Rich text components override for both inline and block level components.

t?: (msg: string, params?: Record<string, string | number>) => string;

Translator function override.

RichTextDatainterface

interface RichTextData
Members (1)
blocks: RichTextBlock[];

RichTextHeadingBlockPropsinterface

interface RichTextHeadingBlockProps
Members (2)
type: 'heading';
data?: {
    level?: number;
    text?: string;
};

RichTextHtmlBlockPropsinterface

interface RichTextHtmlBlockProps
Members (2)
type: 'html';
data?: {
    html?: string;
};

RichTextImageBlockPropsinterface

interface RichTextImageBlockProps
Members (2)
type: 'image';
data?: {
    caption?: string;
    file?: {
        url: string;
        width: string | number;
        height: string | number;
        alt: string;
    };
};

RichTextInlineComponenttype

type RichTextInlineComponent = RichTextComponent;

RichTextListBlockPropsinterface

interface RichTextListBlockProps
Members (2)
type: 'orderedList' | 'unorderedList';
data?: {
    style?: 'ordered' | 'unordered';
    items?: ListItem[];
};

RichTextParagraphBlockPropsinterface

interface RichTextParagraphBlockProps
Members (2)
type: 'paragraph';
data?: {
    size?: RichTextParagraphSize;
    text?: string;
    components?: Record<string, any>;
};

RichTextPropsinterface

interface RichTextProps
Members (3)
data: RichTextData | undefined;
components?: Record<string, RichTextBlockComponent>;
translate?: boolean;

Deprecated.

RichTextTableBlockPropsinterface

interface RichTextTableBlockProps
Members (2)
type: 'table';
data?: {
    rows?: Array<{
        cells: Array<{
            blocks: RichTextBlock[];
            type: 'header' | 'data';
        }>;
    }>;
};

testContentfunction

function testContent(data: RichTextData): boolean;

Returns whether the rich text value is truthy.

useRichTextContextfunction

function useRichTextContext(): RichTextContextProps;
1
2
3
4
5
6
7
8
9
10
11
12
Breakpoint: