Schema types

Field, document type, and block type shapes exported by @cairn-cms/schema-types, generated from its own TSDoc comments.

Functions

defineBlockType

function defineBlockType(blockType: BlockType): BlockType
blockType.fieldsField[]required
blockType.iconstringoptionalA Phosphor component export name (e.g. "SquaresFourIcon") - purely cosmetic, shown wherever the block type is listed or picked.
blockType.namestringrequired
blockType.previewDocumentTypePreviewoptionalHow to label this block type's instances wherever they're listed collapsed (an array item, a picker) - see DocumentTypePreview. Same shape and fallback rules as a document type's own preview.
blockType.titlestringoptional

Returns: BlockType

defineDocumentType

function defineDocumentType(docType: DocumentType): DocumentType
docType.descriptionstringoptionalShort explanation of what this type is for, shown in the schema builder only - purely cosmetic, no effect on validation, storage, or the published content API.
docType.fieldsField[]required
docType.groups{ name: string; title?: string }[]optionalNamed categories a top-level field can be filed under (see FieldBase.group) - rendered as collapsible sections both in the schema builder (organizing fields while defining them) and in the actual content-editing form (grouping fields while filling one out). A field with no group renders flat, outside any section.
docType.iconstringoptionalA Phosphor component export name (e.g. "FileTextIcon"), shown next to this type wherever it's listed (Schema mode's type list, Content mode's sidebar) - purely cosmetic, no effect on validation or storage.
docType.internalbooleanoptionalDocuments of this type are never reachable through the public delivery API - every /published/...//preview/... route for this type 404s the same way an unknown type name would, so its existence isn't even leaked. The Dashboard's own CRUD API is unaffected: an authenticated editor or project token can still list/create/edit these normally. For a type that only ever exists to be written into from outside (e.g. a form-submission inbox) and should never be exposed on the live site - see FieldBase.internal for the same idea on individual fields of an otherwise-public type.
docType.namestringrequired
docType.orderablebooleanoptionalWhether documents of this type get drag-and-drop ordering in list views, on top of the built-in draft/publish/reference machinery. This is a plain boolean because the rank column and reorder UI already exist on every document type's storage - this just turns the UI affordance on, no extra package or hand-added rank field required.
docType.previewDocumentTypePreviewoptionalHow to label documents of this type wherever they're listed - see DocumentTypePreview.
docType.singletonbooleanoptionalExactly one document of this type ever exists per project - no "create new", no list to browse, the nav links straight to the one instance (auto-created the first time anyone opens it). This is the built-in answer to a "site settings" page: no need to fake it with a filtered list view and a hidden create button, because storage already supports a document type with zero, one, or many rows - a singleton is just a type the UI (and, if you want, validation) treats as capped at one.
docType.sortDocumentTypeSortoptionalA custom default sort for this type's list view, used only when orderable is off (manual drag order always wins when it's on - the two are mutually exclusive, same as singleton/orderable). Without this, a non-orderable type's list falls back to creation order (createdAt, then id) - the rank column orderable types use for drag position is never written for these types at all, so it plays no part. field may itself be a reference, in which case refField names a field on *that* referenced document to sort by instead - same one-level-deep idea as PreviewChainPart's refField.
docType.titlestringoptional

Returns: DocumentType

defineField

function defineField<F = Field>(field: F): F
fieldFrequired

Returns: F

evaluateCondition

Evaluates a condition against the sibling data at whatever nesting level it was declared - never throws (a malformed comparison, e.g. gt against a non-numeric sibling, just evaluates to false rather than crashing validation or the editor).

function evaluateCondition(condition: Condition, siblingData: Record<string, unknown>): boolean
conditionConditionrequired
siblingDataRecord<string, unknown>required

Returns: boolean

generateTypeScript

Turns a project schema into real TypeScript source - one `export interface per document type, so client.fetch<Post>("post")` (see @cairn-cms/client's fetch<T>) can use a generated Post instead of a hand-written one. See apps/cli's cairn typegen command, the only real caller - this module is pure (no I/O, no fetching) so the mapping itself is unit-testable without a live schema endpoint.

Deliberately takes plain { documentTypes, blockTypes } rather than a constructed Schema instance - Schema's own constructor validates eagerly and throws on any dangling reference/blockType target, which is the right call for the Dashboard's publish path (a schema that fails that check should never go live) but wrong here: cairn typegen fetches document types and block types as two separate HTTP calls (`GET .../schema and GET .../block-types`, see typegen.ts in packages/cli), not one atomic snapshot, so a field referencing a block type that genuinely doesn't resolve (deleted between the two fetches, or just temporarily inconsistent) should degrade to unknown for that one field, not crash generation for the whole project.

Every shape here is checked against what a *published read* actually returns, not the field's raw editing-time definition - two things this deliberately gets right that a naive 1:1 field-type mapping would miss:

- localized is invisible here. @cairn-cms/db's resolveLocalizedData always resolves a localized field down to its plain value before a published read ever sees it (the { [locale]: value } wrapper never reaches a real API response) - so a localized field generates the exact same type as a non-localized one of the same underlying type. - reference becomes `{ _type: "reference"; _ref: string; _refType: "..." }`, never the referenced document's own fields inlined - that shape only exists when a caller passes include, a per-call runtime choice this static type can't know about ahead of time (see FetchOptions.include) - an included field instead comes back shaped like the referenced type's own generated interface. - an object/blockRef used as an array member is delivered flat, tagged with _id (its API identifier), _type (the schema's structural kind - "object" or "blockRef", not the same thing as _id), _fieldId (stable, survives a rename), and _key (this item's own instance id) alongside its own fields (no separate data sub-object) - this is the public page-builder shape, not the Dashboard's raw stored object.

function generateTypeScript(input: { blockTypes?: BlockType[]; documentTypes: DocumentType[] }): string
input{ blockTypes?: BlockType[]; documentTypes: DocumentType[] }required

Returns: string

isAllCondition

function isAllCondition(condition: Condition): condition is { all: Condition[] }
conditionConditionrequired

Returns: condition is { all: Condition[] }

isAnyCondition

function isAnyCondition(condition: Condition): condition is { any: Condition[] }
conditionConditionrequired

Returns: condition is { any: Condition[] }

normalizePreviewChain

Normalizes preview.title/preview.subtitle to the current chain shape - older schemas stored a single field name string instead. Every reader should call this rather than branching on the union itself, so the upgrade only needs to be taught once. New schemas always get *written* back out as a chain (see the schema builder's PreviewChainInput) - this only exists to keep reading old stored data working.

function normalizePreviewChain(value: string | PreviewChainPart[] | undefined): PreviewChainPart[]
valuestring | PreviewChainPart[] | undefinedrequired

Returns: PreviewChainPart[]

normalizePreviewMedia

Normalizes preview.media to the current {field, refField?} shape - older schemas stored a bare field name string instead. Every reader should call this rather than branching on the union itself, same idea as normalizePreviewChain just above. null (explicit "no thumbnail") passes through unchanged - callers must distinguish it from undefined ("no opinion, guess the first image field") rather than treating both as "no config."

function normalizePreviewMedia(value: string | DocumentTypePreviewMedia | null | undefined): DocumentTypePreviewMedia | null | undefined
valuestring | DocumentTypePreviewMedia | null | undefinedrequired

Returns: DocumentTypePreviewMedia | null | undefined

Types

ArrayField

of lists the field shapes an item is allowed to take - one array can mix, say, an object shape and a reference shape. Ordering for array items is always available in the Dashboard UI; there's no separate flag for it because reordering an array is just re-saving it with the items in a different order, unlike top-level document lists which need the rank column (see DocumentType.orderable).

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
ofField[]required
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"array"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

BlockReferenceField

Stands in for a named, reusable object shape defined once in the project's block type palette (see BlockType in blockType.ts), instead of an object/array field re-declaring the same shape inline. blockType is singular - unlike ReferenceField.to, a single field position has exactly one shape; an array that should accept several block types lists one blockRef entry per type in its of[] (the same mechanism that already lets of[] mix multiple inline shapes).

blockTypestringrequired
defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"blockRef"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

BlockType

A named, reusable object shape - defined once at the project level and referenced from any number of object/array fields via a blockRef field (see BlockReferenceField in fields.ts), instead of every field re-declaring the same shape inline. Deliberately minimal compared to DocumentType: no groups/singleton/orderable - those are document/content-entry-surface concerns that don't apply to a shape that only ever exists nested inside another document. preview is the one DocumentType concern that *does* carry over - a blockRef item collapsed inside an array needs the same title/subtitle/thumbnail as any other collapsed item, and unlike an object field's own inline preview (configured per usage site, since an inline shape has no identity of its own), a block type is named and reused, so its preview belongs on the type itself, authored once wherever the block type is defined.

fieldsField[]required
iconstringoptionalA Phosphor component export name (e.g. "SquaresFourIcon") - purely cosmetic, shown wherever the block type is listed or picked.
namestringrequired
previewDocumentTypePreviewoptionalHow to label this block type's instances wherever they're listed collapsed (an array item, a picker) - see DocumentTypePreview. Same shape and fallback rules as a document type's own preview.
titlestringoptional

BlockTypeFile

blocks/<name>.json - one reusable block type, referenced by blockRef fields.

type BlockTypeFile = BlockType & { $schema?: string }

BooleanField

true/false, shown as a toggle.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"boolean"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

ButtonField

Not a value but an action: renders in the Dashboard's editor, at this field's position in the form, as a button that runs action against the document being edited. It stores nothing - never validated, never in the document's data, the delivery API or generated types, and needs no migration when added or removed. name is the button's id, the button.id a receiver matches on; title is what it says (falling back to name), description the note beneath it. Honors group and showWhen like any field, but only exists at the top level of a document type - not inside an object, array or block type, where there'd be no single document position to press it from.

actionButtonFieldActionrequired
defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"button"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

ButtonFieldAction

What pressing a button field does. webhook is the only action today: Cairn POSTs a signed JSON body describing the document being edited to url (see @cairn-cms/webhook's ButtonPayload). url must be https:// (or http://localhost for development).

The URL is often a secret in its own right (an automation platform's hook URL), so the API only returns it to a caller with settings:write; every other reader of the schema (other members, cairn typegen) gets url: "".

type"webhook"required
urlstringrequired

ColorField

Stored as a CSS color string - #rrggbb(aa) or rgb()/rgba() - so it can be dropped straight into a style prop or stylesheet on the consuming site.

allowCustombooleanoptionalWhether editors may enter any colour. Defaults to true; false constrains both the Dashboard picker and document validation to swatches.
defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
swatchesstring[]optionalOptional project-defined palette shown beneath the colour entry. Values use six-digit hex so native colour inputs and frontend CSS can consume them without conversion.
titlestringoptional
type"color"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

Condition

A leaf condition, or an all/any combinator nesting more conditions - arbitrarily deep.

type Condition = FieldCondition | { all: Condition[] } | { any: Condition[] }

ConditionOp

A declarative rule for showing a field conditionally on a sibling field's value (see FieldBase.showWhen in fields.ts) - deliberately data, never a stored function. cairn schemas are JSON in a database, validated inside a Cloudflare Worker, so an executable condition (arbitrary JS evaluated against the sibling fields) would be a remote-code-execution vector. This covers everything a condition like that actually needs (equality, membership, comparison, presence) without ever running arbitrary code.

type ConditionOp = "eq" | "neq" | "in" | "notIn" | "gt" | "gte" | "lt" | "lte" | "isEmpty" | "isSet"

DateField

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
precision"year" | "month" | "day"optionalHow much of the date the Dashboard's editor captures/shows - "day" (the default) is a full calendar date; "month" only lets year+month be set; "year" only lets the year be set. Storage is unaffected: the value is always a full ISO 8601 timestamp regardless of precision (a coarser precision just defaults the parts it doesn't collect to the 1st of the month/January), so changing a field's precision later never strands an existing value - reading one at a lower precision than it was written at is just a matter of taking a shorter prefix of the same string. Doesn't apply to datetime/time, which always need to know exactly which moment.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"date"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

DatePrecision

type DatePrecision = typeof DATE_PRECISIONS[number]

DateTimeField

Like date, but keeps a time-of-day component too - stored as a full ISO 8601 timestamp rather than just a date.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"datetime"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

DocumentType

descriptionstringoptionalShort explanation of what this type is for, shown in the schema builder only - purely cosmetic, no effect on validation, storage, or the published content API.
fieldsField[]required
groups{ name: string; title?: string }[]optionalNamed categories a top-level field can be filed under (see FieldBase.group) - rendered as collapsible sections both in the schema builder (organizing fields while defining them) and in the actual content-editing form (grouping fields while filling one out). A field with no group renders flat, outside any section.
iconstringoptionalA Phosphor component export name (e.g. "FileTextIcon"), shown next to this type wherever it's listed (Schema mode's type list, Content mode's sidebar) - purely cosmetic, no effect on validation or storage.
internalbooleanoptionalDocuments of this type are never reachable through the public delivery API - every /published/...//preview/... route for this type 404s the same way an unknown type name would, so its existence isn't even leaked. The Dashboard's own CRUD API is unaffected: an authenticated editor or project token can still list/create/edit these normally. For a type that only ever exists to be written into from outside (e.g. a form-submission inbox) and should never be exposed on the live site - see FieldBase.internal for the same idea on individual fields of an otherwise-public type.
namestringrequired
orderablebooleanoptionalWhether documents of this type get drag-and-drop ordering in list views, on top of the built-in draft/publish/reference machinery. This is a plain boolean because the rank column and reorder UI already exist on every document type's storage - this just turns the UI affordance on, no extra package or hand-added rank field required.
previewDocumentTypePreviewoptionalHow to label documents of this type wherever they're listed - see DocumentTypePreview.
singletonbooleanoptionalExactly one document of this type ever exists per project - no "create new", no list to browse, the nav links straight to the one instance (auto-created the first time anyone opens it). This is the built-in answer to a "site settings" page: no need to fake it with a filtered list view and a hidden create button, because storage already supports a document type with zero, one, or many rows - a singleton is just a type the UI (and, if you want, validation) treats as capped at one.
sortDocumentTypeSortoptionalA custom default sort for this type's list view, used only when orderable is off (manual drag order always wins when it's on - the two are mutually exclusive, same as singleton/orderable). Without this, a non-orderable type's list falls back to creation order (createdAt, then id) - the rank column orderable types use for drag position is never written for these types at all, so it plays no part. field may itself be a reference, in which case refField names a field on *that* referenced document to sort by instead - same one-level-deep idea as PreviewChainPart's refField.
titlestringoptional

DocumentTypeFile

documents/<name>.json - one document type.

type DocumentTypeFile = DocumentType & { $schema?: string }

DocumentTypePreview

mediastring | DocumentTypePreviewMedia | nulloptionalWhich field to show as a thumbnail - see DocumentTypePreviewMedia. Not chainable, unlike title/subtitle - a thumbnail is a single image. Three distinct states: unset (undefined) falls back to this type's first image field; null means "explicitly no thumbnail" (the schema author opted out of the guess, not just never configured it); a field reference shows that field's image.
subtitlestring | PreviewChainPart[]optionalSame chain shape as title, for a secondary line under it. No fallback - omitted entirely if unset.
titlestring | PreviewChainPart[]optionalWhat to show as the title - a chain of field references and literal text, composed together. Falls back to this type's first string/ text field when unset. Older schemas stored this as a single field name string instead of a chain - see normalizePreviewChain, which every reader should go through rather than branching on this union itself.

DocumentTypePreviewMedia

Which field to show as a document's thumbnail - either a top-level image field's name directly, or (drilling one level through a reference field, same idea as PreviewChainPart's refField) an image field on whatever document that reference points at. Older schemas stored this as a bare field name string - see normalizePreviewMedia, which every reader should go through rather than branching on this union itself.

fieldstringrequired
refFieldstringoptional

DocumentTypeSort

A non-orderable document type's custom default list sort - see DocumentType.sort.

direction"asc" | "desc"required
fieldstringrequired
refFieldstringoptional

Field

type Field = StringField | UrlField | TextField | SlugField | NumberField | BooleanField | DateField | DateTimeField | TimeField | ColorField | SelectField | RadioField | ImageField | ImagesField | VideoField | ReferenceField | BlockReferenceField | RichTextField | ObjectField | ArrayField | ButtonField

FieldCondition

field names a sibling at the same nesting level - never a cross-level or absolute path.

fieldstringrequired
opConditionOprequired
valuestring | number | boolean | (string | number)[]optional

ImageField

Points at an asset stored via the (forthcoming) R2/Cloudflare Images asset pipeline.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"image"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

ImagesField

Several images at once, e.g. a gallery - the Dashboard renders this as a drag-and-drop bulk uploader rather than array + of: [image]'s one-at-a-time picker.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"images"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

NumberField

A JSON number. validation.integer restricts it to whole numbers; validation.min/max bound it.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"number"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

ObjectField

A nested group of fields, stored as one JSON object under this field's name. For a shape reused across types, define a block type and use blockRef instead.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
fieldsField[]required
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
previewDocumentTypePreviewoptionalHow to label this object's entries wherever they're shown collapsed - an array-of-objects item row, or (should a standalone object ever need it) elsewhere. Same shape and same fallback behavior as DocumentType.preview - see DocumentTypePreview.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"object"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

PortableTextBlock

One paragraph/heading/list-item of rich text, in Portable Text shape - a richText field's value is an array of these. level is the list nesting depth (1-based), only meaningful when listItem is set.

_keystringrequired
_type"block"required
childrenPortableTextSpan[]required
levelnumberoptional
listItem"number" | "bullet"optional
markDefsPortableTextMarkDef[]required
style"normal" | "h1" | "h2" | "h3" | "h4" | "blockquote"required

PortableTextBlockStyle

type PortableTextBlockStyle = typeof PORTABLE_TEXT_STYLES[number]

PortableTextDecorator

type PortableTextDecorator = typeof PORTABLE_TEXT_DECORATORS[number]

PortableTextListItem

type PortableTextListItem = typeof PORTABLE_TEXT_LIST_ITEMS[number]

PortableTextMarkDef

An annotation a span's marks can point at by _key - currently just link.

_keystringrequired
_type"link"required
hrefstringrequired

PortableTextSpan

One run of text within a PortableTextBlock's children - marks are mark-def keys (annotations, e.g. a link's _key) and/or the fixed decorator names in PORTABLE_TEXT_DECORATORS.

_keystringrequired
_type"span"required
marksstring[]optional
textstringrequired

PreviewChainPart

One piece of a title/subtitle chain (see DocumentTypePreview) - either a reference to a top-level field's value, or literal text to splice in between fields (a separator like " - ", punctuation, whatever). Chains are composed left to right: [{kind:"field",name:"title"}, {kind:"text",value:" · "}, {kind:"field",name:"category"}] renders as "My Post · News".

A field part whose named field is a reference may also set refField, naming a field on *that* referenced document to pull the value from instead of the reference field itself - {kind:"field",name:"company",refField:"name"} shows the linked company's name field, the same idea as "company.name". Only one level deep: refField names a field on the directly-referenced document, not a further chain through another reference.

type PreviewChainPart = { kind: "field"; name: string; refField?: string } | { kind: "text"; value: string }

RadioField

Same stored shape and validation as select (a string matching one of options) - the only difference is the Dashboard renders this as a visible list of radio buttons instead of a dropdown, which reads better for a short list the user should see all of at once.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
optionsSelectOption[]required
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"radio"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

ReferenceField

Points at another document. to lists which document types are valid targets.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
tostring[]required
type"reference"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

RichTextField

Rich, formatted text - stored as an array of Portable Text blocks (see PortableTextBlock), so @cairn-cms/client's toHTML/toPlainText/PortableText can parse it on the frontend.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"richText"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

SelectField

A fixed set of choices, rendered as a native dropdown.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
optionsSelectOption[]required
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"select"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

SelectOption

One choice in a select field's dropdown - value is what's actually stored in the document's data (and what a frontend integration matches against), title is what the Dashboard shows in the list. Distinct so a human-readable label can change without touching every document that already stored the old one, and so value can be a stable identifier (e.g. camelCase for JS-friendly consumption) independent of how it reads.

titlestringrequired
valuestringrequired

StringField

A single line of plain text - a title, a name, a label. Use text for anything multi-line.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"string"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

Structure

type Structure = StructureItem[]

StructureFile

structure.json - the Dashboard's content sidebar. Optional: without it, every document type gets one top-level link, alphabetically.

$schemastringoptional
itemsStructurerequired

StructureItem

type StructureItem = NavLink | NavGroup

TextField

Multi-line plain text (no formatting) - stored as one string with newlines. Use richText for formatted content.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"text"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

TimeField

A time of day with no date attached - stored as "HH:MM" (or "HH:MM:SS"), not tied to any particular day.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"time"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

UrlField

A single-line string that must be a well-formed http/https URL (see @cairn-cms/schema's validateField) - other schemes (mailto:, tel:, javascript:, ...) are rejected, since this is specifically for a website address, not an arbitrary URI.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"url"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.

VideoField

Points at an asset stored via the same R2 asset pipeline as image, just not gated to image mime types - the Dashboard renders an upload/preview surface with a <video> player instead of an <img>.

defaultValueunknownoptionalValue a brand-new document's field starts out with, in the same shape the field itself stores (a string for string, a boolean for boolean, ...) - the Dashboard only checks it's present, not that its shape matches this field's type, so a mismatched value here just behaves the same as if a document already had one written some other way.
descriptionstringoptional
fieldIdstringoptionalStable identity for this field, independent of name - minted once (see @cairn-cms/db's schema-publish path, which backfills any field missing one every time a schema publishes) and never reassigned again, even across a rename/retype. Optional here rather than required so existing schemas don't need a one-time migration script; the lazy backfill on publish covers both the historical catch-up and the ongoing new-field case with the same code path. Automatic schema migration keys off this, not name, to tell a rename apart from a delete+add - see ROADMAP.md §1.
groupstringoptionalWhich of the document type's groups (see DocumentType.groups) this field belongs to, by name - undefined means ungrouped. Only meaningful on a document type's own top-level fields; a field nested inside an object's fields or an array's of must never set this (there's no group list at that level to reference).
internalbooleanoptionalInverse of readOnly: this field is a normal part of the Dashboard's editing UI and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set or read it, but it never reaches the public delivery API - stripped entirely from /published/... and /preview/... responses (see @cairn-cms/db's shapeDeliveryData). For data an integration should be able to write (e.g. a form submission proxied through the customer's own server) or an editor should see, but that must never become visible on a public GET - an email address, an internal note, anything not meant for the live site. Applies uniformly regardless of the field's own type, including a whole object/array of nested fields. See also DocumentType.internal for hiding an entire document type the same way.
localizedbooleanoptionalStores this field's value as a map of the project's locale codes to the field's normal value ({ en: "...", fr: "..." }) instead of the value directly - see the project's locales/defaultLocale settings. Applies uniformly regardless of the field's own type, including object/array, since the wrapping is generic. A field already nested inside a localized ancestor must never also set this itself - see Schema's constructor for the integrity check (there's no meaningful shape for a locale map nested inside another locale map).
namestringrequired
placeholderstringoptionalGhost text shown in the Dashboard's empty input for this field - purely cosmetic, no effect on validation, storage, or the published content API. Only rendered for field types with an actual text-entry control (string/text/number/slug); ignored otherwise.
readOnlybooleanoptionalHides the Dashboard's editable control for this field, showing its current value instead - the field is still a normal part of the document's data and any authenticated write (Dashboard API, a project token via @cairn-cms/client) can still set it. For values a human shouldn't hand-edit but an integration needs to write - a computed score, an external system's id, etc.
renamedFromstringoptionalA rename hint for schema files: "this field used to be called renamedFrom". Schema files carry no fieldIds - a deploy matches each field to its published counterpart by name, so without this hint a rename reads as a delete plus an add and the old values are dropped. With it, the deploy carries the field's identity (and its stored values) across to the new name. Only ever read during a deploy and never stored; cairn schema deploy removes it from your files once the rename is live. Matched among siblings at the same nesting level only.
requiredbooleanoptional
showWhenConditionoptionalHides this field in the Dashboard's editing/schema-builder UI, and skips it entirely during validation (no required/type/rule checks at all), unless this evaluates true against its sibling fields' values at the same nesting level. Combine with required for "required exactly when shown" - a field that's hidden is never required-checked regardless of required, so no separate "required when" concept is needed on top of this.
titlestringoptional
type"video"required
validation{ integer?: boolean; max?: number; min?: number; pattern?: string }optionalExtra rules beyond required/type-correctness - which of these apply depends on the field's own type: min/max/integer for number; min/max (string length) and pattern (regex source) for string/text; min/max (item count) for array/images. Ignored for every other field type.