Skip to content

Schemas

Schemas tell Studio which fields an entry has, their types, and their labels. They are optional: without one, CaretCMS infers a schema from stored data.

A schema lets Studio render typed controls such as enum menus, date inputs, and validated number fields. Add one when the collection shape is stable.

When Studio asks for a collection’s schema (GET /api/cms/schema?collection=pages), the server checks three sources in order. The first match wins.

Order Source When used Response source
1 Registered Passed via the schemas option in caret() config "explicit"
2 Dynamic Created via Studio’s collection builder (stored as metadata) "dynamic"
3 Inferred Guessed from the first stored entry "inferred"

Registered Recommended

Section titled “Registered ”

Pass JSON Schemas in your caret() config. You can write them directly or convert Zod schemas with @caretcms/zod. If content.config.ts already contains the collection’s Zod schema, reuse it here to keep one definition.

Terminal window
npm install @caretcms/zod
src/cms-schemas.ts
import { z } from 'zod';
export const PageSchema = z.object({
headline: z.string().min(1).meta({ title: 'Headline' }),
intro: z.string().meta({ title: 'Intro paragraph' }),
hero: z.object({
image: z.string().url().meta({ title: 'Hero image', format: 'url' }),
alt: z.string().optional(),
}),
cta: z.enum(['primary', 'secondary', 'none']).meta({ title: 'CTA style' }),
});
export const SiteSchema = z.object({
company: z.object({
name: z.string(),
tagline: z.string(),
}),
social: z.object({
twitter: z.string().url().optional(),
github: z.string().url().optional(),
}),
});

Keep these in a plain module that imports only zod — not astro:content — so both content.config.ts and astro.config.mjs can import the same object.

What this gets you:

  • Typed inputs in Studio (string, number, boolean, enum, array, object)
  • Required-field markers
  • Validation on save with field-level error messages
  • A canonical template payload returned alongside the schema

Dynamic Editor-defined

Section titled “Dynamic ”

Editors create new collections through /admin/cms/collections/new without you writing any code. The “schema builder” UI captures fields, types, and validation, then writes them into collection metadata via the create_collection mutation.

See Dynamic Collections for the full flow. The stored metadata looks like:

{
"id": "products",
"label": "Products",
"icon": "📦",
"creatable": true,
"orderable": true,
"deletable": true,
"order": 20,
"schema": {
"type": "object",
"properties": {
"name": { "type": "string", "title": "Product name" },
"price": { "type": "number", "minimum": 0, "title": "Price (USD)" },
"in_stock": { "type": "boolean", "title": "In stock" }
},
"required": ["name", "price"]
},
"created_at": 1714225200000,
"updated_at": 1714225200000
}

Use this when your editors need new content types and you don’t want to redeploy.

If no schema is registered and no metadata exists, Studio reads the first entry in the collection and guesses field types from JS values:

JS value Inferred type
"hello" string
42 number
true boolean
["a", "b"] array of string
{ x: 1 } object with inferred properties

Inference is great for prototyping. Promote to a registered schema once the shape stabilizes.

Every schema response includes a template — a default value for new entries. Studio uses it when an editor clicks “New entry”:

Field type Template value
string ""
number 0
boolean false
object {} with each property recursively templated
array []
enum First value

You can override per-field via default in JSON Schema (or .default(value) in Zod).

CaretCMS understands a focused subset of JSON Schema for Studio controls, templates, and dependency-free server validation. Extra metadata is preserved, but unsupported validation keywords are not enforced.

Feature Supported Notes
type: string Yes validates minLength, maxLength, and pattern
format: email / url Studio control chooses the input type; format itself is not server-validated
type: number / integer Yes validates minimum, maximum, exclusive bounds, and integer values
type: boolean Yes
type: array Yes validates items, minItems, and maxItems recursively
type: object Yes validates nested properties, required, and additionalProperties: false
enum, const Yes exact-value validation
required Yes array of property names
title, description Yes shown as labels and helper text
default Yes used in template generation
oneOf, anyOf Yes value must match at least one alternative
allOf Yes every child schema is applied
local $ref Yes #/... references only; remote references are not fetched

Validation failures return structured issues with path, code, and message so Studio can place an error beside the exact nested field. The server validates put_entry, field saves, and every affected entry in a reorder before any write is committed.

Situation Use
You’re prototyping, schema unknown Inferred (do nothing)
Schema is stable and code-owned Registered
Editors need to define new content types Dynamic
You want both code-owned core + editor-defined extras Mix — register the core, let editors create dynamic ones