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

Configuration

Every option accepted by createCMS, and what it returns.

createCMS(definition) builds a configured CMS instance. The definition is typed as CMSDefinition (see packages/cms/src/core/types/definitions.ts).

import { createCMS } from '@createcms/core';

export const cms = createCMS({
  db,
  collections,
  media,
  authMiddleware,
});

Options

OptionTypeRequiredDefaultDescription
dbDrizzleInstanceyesYour Drizzle (PostgreSQL) database instance.
collectionsRecord<string, CollectionDefinition>yesThe collections, usually grouped with defineCollections.
mediaMediaConfigyesS3-compatible storage config (see below).
authMiddlewareCMSMiddlewarenoResolves the user and permissions per request.
middlewareCMSMiddlewarenoAlias for authMiddleware.
pluginsCMSPlugin[]no[]Server plugins.
basePathstringno'/api/cms'Base path the router mounts under.
dataRetentionDataRetentionConfignoPruning policy for old commits and archived roots.
forceCommitMessagebooleannofalseRequire a non-empty message on every content mutation (create/update/delete/move/duplicate of roots and blocks). An empty or whitespace-only message is rejected with COMMIT_MESSAGE_REQUIRED instead of auto-generating one.
defaultBranchNamestringno'main'Name of the default branch every root is seeded with.
branchProtectionBranchProtectionConfignoBranch-protection and approval gates (see below).
mergeStrategy'fast-forward' | 'merge-commit'no'fast-forward'Default integration strategy for executeMerge when a fast-forward is possible. 'fast-forward' moves the target head with no merge commit; 'merge-commit' always records an explicit merge commit (git's --no-ff). A diverged target always produces a merge commit regardless. Override per call with executeMerge({ noFastForward }).
userCMSUserConfignoLinks a user table so reads can enrich with author data.
hooks{ before?, after? }noInline before/after hooks on CMS actions.
schema{ output?: string }noCLI-only: the output path for createcms generate (default ./cms-schema.ts). Not read at runtime.
onRevalidateRevalidateHandler | RevalidateConfignoCalled after content changes, for cache revalidation.
onNotificationOnNotificationHandlernoCalled when the CMS emits a notification.
notificationsbooleannotrueSet false to fully disable notifications — no tables, no routes, and client.notifications / cms.notify absent from the types (see below).
realtime{ url, token }noUpstash Redis credentials. Enables the /realtime SSE route, per-user notification push, and A/B live results (see below).

media

MediaConfig is a discriminated union on provider. These base fields apply to every provider:

FieldTypeDefaultDescription
accessKeyIdstringAccess key ID.
secretAccessKeystringSecret access key.
sessionTokenstringOptional token for temporary credentials.
bucketNamestringBucket for stored assets.
publicUrlstringPublic base URL assets are served from.
maxFileSizenumber4194304 (4 MB)Maximum upload size in bytes.
maxFilesnumber10Maximum files per upload batch.
allowedMimeTypesstring[]['image/*', 'video/*', 'application/pdf']Allowed MIME patterns.
signedUrlExpiresInnumber120Signed URL lifetime in seconds.

Each provider adds its own fields:

  • provider: 'aws': region (required), forcePathStyle (default false).
  • provider: 'digitalOcean': region (required), forcePathStyle (default true).
  • provider: 'cloudflare': accountId (required), jurisdiction, forcePathStyle (default true).
  • provider: 'custom': hostname (required), region (required), secure (default true), forcePathStyle (default true).
const media = {
  provider: 'cloudflare',
  accountId: process.env.R2_ACCOUNT_ID!,
  bucketName: process.env.R2_BUCKET!,
  accessKeyId: process.env.R2_KEY!,
  secretAccessKey: process.env.R2_SECRET!,
  publicUrl: process.env.R2_PUBLIC_URL!,
};

authMiddleware

A function that receives the request context and returns a result object. userId is optional (anonymous requests omit it); any extra fields you return extend the context available to scoping and hooks.

import { defineAuthMiddleware } from '@createcms/core';

const authMiddleware = defineAuthMiddleware(async (ctx) => {
  // ctx.operation, ctx.request (a curated request; the raw Request is at
  // ctx.request?.request), and ctx.collection (collection-scoped calls only).
  return { userId: await resolveUser(ctx) };
});

realtime

Enables live server-to-client delivery — notification push and A/B live results — over Server-Sent Events. Upstash-only: pass your Upstash Redis credentials. @upstash/realtime and @upstash/redis are optional peer dependencies. See Realtime.

const realtime = {
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
};

Requires authMiddleware to resolve the user from the request cookie — the realtime route authenticates each connection by session, so it must be same-origin with your app. Omit realtime to disable; notifications stay available through the listNotifications poll.

notifications

Set false to fully disable the notifications feature. When disabled, the notification tables are not generated (regenerate your schema after toggling), the routes never register, and client.notifications plus cms.notify are absent from the inferred types. Default: enabled. Use a literal false — a widened boolean keeps the types enabled. Independent of realtime.

const cms = createCMS({ /* … */ notifications: false });

dataRetention

The whole object is optional, but when you provide it, keepDays and keepMinCommits are required.

FieldTypeRequiredDefaultDescription
keepDaysnumberyesHow long to keep old commits.
keepMinCommitsnumberyesMinimum commits to keep per branch regardless of age.
archiveKeepDaysnumbernokeepDaysGrace period before an archived root is hard-deleted.

branchProtection

Governance for published content and the merge/publish approval gates. Every field is opt-in; an empty config preserves current behavior. Each field can be overridden per collection via the collection's own branchProtection (a Partial<BranchProtectionConfig>): a collection's value wins over this global one field-by-field, and unset fields inherit the global value. For example a reusableBlock collection can set branchProtection: { protectPublishedBranches: false } to stay directly editable while pages keep the global protection.

FieldTypeDefaultDescription
protectPublishedBranchesbooleanfalseLock a branch against direct content mutations (create/update/delete/move/duplicate of blocks, updateRoot, and reverting the branch) for exactly as long as it is published — the live tree is immutable in place. Changes go via another branch + merge, then a re-publish; unpublishing makes the branch directly editable again. Applies to any published branch (a root can have several at once, e.g. A/B variants), not just the default one. A never-published branch is freely editable. Throws PROTECTED_BRANCH.
requireApprovalToMergebooleanfalseRequire approvals before executeMerge. Note: previously merges always required approval — this now defaults to off; set true to keep the gate.
requireApprovalBeforePublishbooleanfalseRequire approvals before publishBranch, even when none were explicitly requested. false keeps the existing conditional behavior (gate only when approvals exist).
requiredReviewersnumber1Minimum distinct approved reviewers for the merge / publish gates, on top of "all requested reviewers approved".

user

FieldTypeDescription
tableAnyPgTableYour Drizzle user table.
idColumnAnyColumnThe user id column referenced by content.
exposeColumns(keyof TTable['$inferSelect'] & string)[]Allowlist of user-table columns (camelCase keys) that may be returned via the withUser query flag.

Return value

createCMS returns:

PropertyDescription
routerHTTP router; destructure handler to mount it.
apiType-safe server-side callers, namespaced by collection (and admin).
collectionsYour collections, each augmented with its name.
notifySend a notification through the CMS.
notificationServiceThe underlying notification service.
revalidateManual revalidation trigger. Always present; its value is undefined unless onRevalidate is set.
$ERROR_CODESMerged error-code map (core plus plugins).

Define collections with defineCollection. Call the API at Server API.

On this page