Skip to content

Configuration

This page is a reference, not a tutorial. For the path through, start with Quickstart.

astro.config.mjs
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'] },
})
OptionDefaultType
delivery'server' (implicit)'static' | { mode, bake?, publish? }
mode'embedded''embedded' | 'cloud'
mountPath'/admin'string
apiBasePath'/api/cms'string
enableAdmintrue (embedded), false (cloud)boolean
enableInlineEditortrue (embedded), false (cloud)boolean
storagemarkdownStorage() if src/content/ collections exist, else filesystemStorage()CaretStorageProvider
uploadslocalUploads()CaretUploadProvider
cloudCaretCloudOptions (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.

Controls how CMS content reaches visitors. See Static delivery and Deployment.

ValueBehavior
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.bakeDefault true when mode is static — set false to skip HTML rewrite at build
delivery.publish.webhookUrlOptional URL called after Publish to trigger CI rebuild
// Shorthand
caret({ delivery: 'static' })
// With rebuild webhook
caret({
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.

Where the admin UI mounts. Login is ${mountPath}, Studio is ${mountPath}/cms.

caret({ mountPath: '/staff' })
// → /staff, /staff/cms

Use this if /admin collides with your app’s existing routes.

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.

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).

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.

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'
})

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'

Required when mode: 'cloud'. Ignored otherwise.

FieldDefaultNotes
endpointHosted control plane URL. Required.
projectIdProject ID in the hosted control plane. Required.
contentPath'/content'API base path on the hosted endpoint.
publicTokenundefinedPublic bootstrap token (per-project).
environmentundefinedEnvironment name ('preview', 'production').

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 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.

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.

VariableRequiredPurpose
CARET_EDIT_PASSWORDYes (for editing)Editor password — anyone with it can log in
EDIT_PASSWORDFallbackSame as above; kept for backward compat
CARET_SESSION_SECRETStrongly recommended in prodHMAC secret for session cookies
CARET_TRUST_PROXYOptionalSet true behind a reverse proxy so Secure cookies derive from X-Forwarded-Proto
CARET_GIT_ON_PUBLISHOptionalSet true to git-commit storage after a successful publish
CARET_DEMO_MODEOptionalSet 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:

BindingPurpose
CMS_KV (or your custom name)KV namespace for storage
CMS_R2 (or your custom name)R2 bucket for uploads
{
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: {},
}
caret({
delivery: {
mode: 'static',
publish: { webhookUrl: process.env.CARET_REBUILD_WEBHOOK_URL },
},
})