Configuration
This page is a reference, not a tutorial. For the path through, start with Quickstart.
caret(options) shape
Section titled “caret(options) shape”import caret from '@caretcms/core';
caret({ // Delivery delivery: 'static' | { mode: 'static' | 'server', bake?: boolean, publish?: { webhookUrl?: string } },
// Mode mode: 'embedded' | 'cloud',
// Routes mountPath: '/admin', apiBasePath: '/api/cms',
// Toggles enableAdmin: true, enableInlineEditor: true,
// Storage and uploads (embedded mode only) storage: filesystemStorage(), uploads: localUploads(),
// Cloud (cloud mode only) cloud: { endpoint: 'https://cloud.caretcms.com', projectId: 'proj_marketing_site', contentPath: '/content', publicToken: '...', environment: 'production', },
// Schemas schemas: { /* collection: jsonSchema */ },
// Rich text allowedClasses: { span: ['gold'], strong: ['big'] },})Options at a glance
Section titled “Options at a glance”| Option | Default | Type |
|---|---|---|
delivery | 'server' (implicit) | 'static' | { mode, bake?, publish? } |
mode | 'embedded' | 'embedded' | 'cloud' |
mountPath | '/admin' | string |
apiBasePath | '/api/cms' | string |
enableAdmin | true (embedded), false (cloud) | boolean |
enableInlineEditor | true (embedded), false (cloud) | boolean |
storage | markdownStorage() if src/content/ collections exist, else filesystemStorage() | CaretStorageProvider |
uploads | localUploads() | CaretUploadProvider |
cloud | — | CaretCloudOptions (required when mode: 'cloud') |
schemas | {} | Record<string, JsonSchemaDefinition> |
allowedClasses | {} | Record<string, string[]> — per-tag class allowlist for data-caret-rich |
Unknown option keys log a warning with a did-you-mean hint instead of silently using defaults.
Delivery
Section titled “Delivery”Controls how CMS content reaches visitors. See Static delivery and Deployment.
| Value | Behavior |
|---|---|
Omitted or { mode: 'server' } | Per-request middleware rewrite — requires output: 'server' (+ adapter) |
'static' or { mode: 'static' } | Dev: full admin + API. Build: bake .caret/data into HTML; no CMS routes in prod output |
delivery.bake | Default true when mode is static — set false to skip HTML rewrite at build |
delivery.publish.webhookUrl | Optional URL called after Publish to trigger CI rebuild |
// Shorthandcaret({ delivery: 'static' })
// With rebuild webhookcaret({ delivery: { mode: 'static', publish: { webhookUrl: process.env.CARET_REBUILD_WEBHOOK_URL }, },})- Default:
'embedded' - Values:
'embedded'|'cloud'Alpha
embedded runs the CMS inside your Astro site — middleware, admin UI, API routes, all served from the same origin. This is what you want today.
cloud is alpha. The integration accepts the option and ships a client bootstrap, but the hosted control plane isn’t live yet.
Routes
Section titled “Routes”mountPath
Section titled “mountPath”Where the admin UI mounts. Login is ${mountPath}, Studio is ${mountPath}/cms.
caret({ mountPath: '/staff' })// → /staff, /staff/cmsUse this if /admin collides with your app’s existing routes.
apiBasePath
Section titled “apiBasePath”Base path for all API routes. Every endpoint hangs off this:
${apiBasePath}/entries${apiBasePath}/mutate${apiBasePath}/schema${apiBasePath}/auth/login- …etc
If your project’s src/pages/api/ already has CMS-adjacent routes, change this to avoid collisions.
Toggles
Section titled “Toggles”enableAdmin
Section titled “enableAdmin”Whether to inject the admin routes (mountPath, mountPath/cms, mountPath/cms/[collection], mountPath/cms/[collection]/[id]). Set to false to disable Studio entirely while keeping the API surface (e.g. for a pure-headless setup or to disable editing in prod).
enableInlineEditor
Section titled “enableInlineEditor”Whether to inject the inline-editor bootstrap script onto pages. Disable to turn off data-caret click-to-edit while keeping Studio working. Useful in production if you want editing to happen only via the admin UI, not on live pages.
Storage and uploads
Section titled “Storage and uploads”storage
Section titled “storage”Where entries, revisions, history, and dynamic-collection metadata live. See Storage Adapters.
If you don’t set this, CaretCMS picks a default: markdownStorage() when your project has Astro content collections under src/content/, otherwise filesystemStorage(). Set it explicitly to override the auto-detection.
import { filesystemStorage } from '@caretcms/core';
filesystemStorage({ dataRoot: 'src/content/cms', // optional — default: '.caret/data' metaRoot: 'src/content/cms/.meta', // optional — default: '.caretcms'})import { cloudflareStorage } from '@caretcms/cloudflare';
cloudflareStorage({ binding: 'CMS_KV' }) // default binding: 'CMS_KV'import { defineStorageProvider } from '@caretcms/core';
defineStorageProvider({ entrypoint: './src/cms/postgres-adapter.ts', exportName: 'postgresStorageProvider', options: { connectionString: process.env.DATABASE_URL },})uploads
Section titled “uploads”Where uploaded files (images via <img data-caret>) go. Same shape as storage.
import { localUploads } from '@caretcms/core';
localUploads({ uploadsDir: './public/uploads' }) // default: 'public/uploads'import { r2Uploads } from '@caretcms/cloudflare';
r2Uploads({ binding: 'CMS_R2', // default: 'CMS_R2' publicBaseUrl: 'https://cdn.example.com', // optional; falls back to the R2_PUBLIC_DOMAIN env var})import { defineUploadProvider } from '@caretcms/core';
defineUploadProvider({ entrypoint: './src/cms/s3-uploads.ts', exportName: 's3UploadHandler', options: { bucket: process.env.S3_BUCKET },})Your entrypoint exports an UploadHandler (an upload(file, ctx) method returning { url }), the same way defineStorageProvider wires a custom storage adapter.
Required when mode: 'cloud'. Ignored otherwise.
| Field | Default | Notes |
|---|---|---|
endpoint | — | Hosted control plane URL. Required. |
projectId | — | Project ID in the hosted control plane. Required. |
contentPath | '/content' | API base path on the hosted endpoint. |
publicToken | undefined | Public bootstrap token (per-project). |
environment | undefined | Environment name ('preview', 'production'). |
Schemas
Section titled “Schemas”Map of collection name → JSON Schema. Highest priority schema source. See Schemas.
import { schemaFromZod } from '@caretcms/zod';
caret({ schemas: { pages: schemaFromZod(PageSchema), site: schemaFromZod(SiteSchema), },})Rich text (allowedClasses)
Section titled “Rich text (allowedClasses)”Rich fields use data-caret-rich. The sanitizer strips unsafe markup by default; allowedClasses whitelists CSS classes per tag so styled inline spans survive round-trip editing.
caret({ allowedClasses: { span: ['gold', 'highlight'], strong: ['big'], },})Caretize prints a copy-pasteable allowedClasses snippet when --rich-class finds classes that need to be allowlisted.
Storage default (auto-detection)
Section titled “Storage default (auto-detection)”When you don’t pass storage, CaretCMS checks for Astro content collections under src/content/. If any exist, it defaults to markdownStorage() so Studio lists those collections immediately (frontmatter edits write back to .md / .mdx on publish). Otherwise it uses filesystemStorage() (.caret/data/ JSON).
Override explicitly when you want JSON storage despite having content collections:
import { filesystemStorage } from '@caretcms/core';
caret({ storage: filesystemStorage() })See Storage Adapters for markdown vs filesystem tradeoffs.
Environment variables
Section titled “Environment variables”| Variable | Required | Purpose |
|---|---|---|
CARET_EDIT_PASSWORD | Yes (for editing) | Editor password — anyone with it can log in |
EDIT_PASSWORD | Fallback | Same as above; kept for backward compat |
CARET_SESSION_SECRET | Strongly recommended in prod | HMAC secret for session cookies |
CARET_TRUST_PROXY | Optional | Set true behind a reverse proxy so Secure cookies derive from X-Forwarded-Proto |
CARET_GIT_ON_PUBLISH | Optional | Set true to git-commit storage after a successful publish |
CARET_DEMO_MODE | Optional | Set true for a public sandbox where every visitor edits in a private, isolated session. See Demo / Sandbox Mode |
CaretCMS does not read a rebuild-webhook URL from the environment directly. Pass it in config, typically from env:
caret({ delivery: { mode: 'static', publish: { webhookUrl: process.env.CARET_REBUILD_WEBHOOK_URL }, },})See Authentication & Security for how each one is used.
Cloudflare deployments also need bindings declared in wrangler.toml:
| Binding | Purpose |
|---|---|
CMS_KV (or your custom name) | KV namespace for storage |
CMS_R2 (or your custom name) | R2 bucket for uploads |
Defaults summary
Section titled “Defaults summary”{ mode: 'embedded', mountPath: '/admin', apiBasePath: '/api/cms', enableAdmin: true, enableInlineEditor: true, delivery: 'server', // implicit when omitted; use 'static' for CDN sites storage: filesystemStorage(), // → .caret/data/ (markdownStorage() if src/content collections exist) uploads: localUploads(), // → public/uploads/ schemas: {}, // → all collections inferred allowedClasses: {},}Common combinations
Section titled “Common combinations”caret({ delivery: { mode: 'static', publish: { webhookUrl: process.env.CARET_REBUILD_WEBHOOK_URL }, },})caret({ enableAdmin: import.meta.env.PROD === false, enableInlineEditor: import.meta.env.PROD === false,})caret({ mountPath: '/cms-admin', apiBasePath: '/api/cms-admin',})caret({ storage: cloudflareStorage({ binding: 'CMS_KV' }), uploads: r2Uploads({ binding: 'CMS_R2' }), schemas: { pages: schemaFromZod(PageSchema), },})caret({ enableAdmin: false, enableInlineEditor: false,})API routes still mount; everything UI-related is skipped.