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
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
db | DrizzleInstance | yes | Your Drizzle (PostgreSQL) database instance. | |
collections | Record<string, CollectionDefinition> | yes | The collections, usually grouped with defineCollections. | |
media | MediaConfig | yes | S3-compatible storage config (see below). | |
authMiddleware | CMSMiddleware | no | Resolves the user and permissions per request. | |
middleware | CMSMiddleware | no | Alias for authMiddleware. | |
plugins | CMSPlugin[] | no | [] | Server plugins. |
basePath | string | no | '/api/cms' | Base path the router mounts under. |
dataRetention | DataRetentionConfig | no | Pruning policy for old commits and archived roots. | |
forceCommitMessage | boolean | no | false | Require 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. |
defaultBranchName | string | no | 'main' | Name of the default branch every root is seeded with. |
branchProtection | BranchProtectionConfig | no | Branch-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 }). |
user | CMSUserConfig | no | Links a user table so reads can enrich with author data. | |
hooks | { before?, after? } | no | Inline before/after hooks on CMS actions. | |
schema | { output?: string } | no | CLI-only: the output path for createcms generate (default ./cms-schema.ts). Not read at runtime. | |
onRevalidate | RevalidateHandler | RevalidateConfig | no | Called after content changes, for cache revalidation. | |
onNotification | OnNotificationHandler | no | Called when the CMS emits a notification. | |
notifications | boolean | no | true | Set false to fully disable notifications — no tables, no routes, and client.notifications / cms.notify absent from the types (see below). |
realtime | { url, token } | no | Upstash 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:
| Field | Type | Default | Description |
|---|---|---|---|
accessKeyId | string | Access key ID. | |
secretAccessKey | string | Secret access key. | |
sessionToken | string | Optional token for temporary credentials. | |
bucketName | string | Bucket for stored assets. | |
publicUrl | string | Public base URL assets are served from. | |
maxFileSize | number | 4194304 (4 MB) | Maximum upload size in bytes. |
maxFiles | number | 10 | Maximum files per upload batch. |
allowedMimeTypes | string[] | ['image/*', 'video/*', 'application/pdf'] | Allowed MIME patterns. |
signedUrlExpiresIn | number | 120 | Signed URL lifetime in seconds. |
Each provider adds its own fields:
provider: 'aws':region(required),forcePathStyle(defaultfalse).provider: 'digitalOcean':region(required),forcePathStyle(defaulttrue).provider: 'cloudflare':accountId(required),jurisdiction,forcePathStyle(defaulttrue).provider: 'custom':hostname(required),region(required),secure(defaulttrue),forcePathStyle(defaulttrue).
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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
keepDays | number | yes | How long to keep old commits. | |
keepMinCommits | number | yes | Minimum commits to keep per branch regardless of age. | |
archiveKeepDays | number | no | keepDays | Grace 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.
| Field | Type | Default | Description |
|---|---|---|---|
protectPublishedBranches | boolean | false | Lock 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. |
requireApprovalToMerge | boolean | false | Require approvals before executeMerge. Note: previously merges always required approval — this now defaults to off; set true to keep the gate. |
requireApprovalBeforePublish | boolean | false | Require approvals before publishBranch, even when none were explicitly requested. false keeps the existing conditional behavior (gate only when approvals exist). |
requiredReviewers | number | 1 | Minimum distinct approved reviewers for the merge / publish gates, on top of "all requested reviewers approved". |
user
| Field | Type | Description |
|---|---|---|
table | AnyPgTable | Your Drizzle user table. |
idColumn | AnyColumn | The 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:
| Property | Description |
|---|---|
router | HTTP router; destructure handler to mount it. |
api | Type-safe server-side callers, namespaced by collection (and admin). |
collections | Your collections, each augmented with its name. |
notify | Send a notification through the CMS. |
notificationService | The underlying notification service. |
revalidate | Manual revalidation trigger. Always present; its value is undefined unless onRevalidate is set. |
$ERROR_CODES | Merged error-code map (core plus plugins). |
Define collections with defineCollection. Call the API at Server API.