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): BlockTypeblockType.fields | Field[] | required | |
blockType.icon | string | optional | A Phosphor component export name (e.g. "SquaresFourIcon") - purely cosmetic, shown wherever the block type is listed or picked. |
blockType.name | string | required | |
blockType.preview | DocumentTypePreview | optional | How 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.title | string | optional |
Returns: BlockType
defineDocumentType
function defineDocumentType(docType: DocumentType): DocumentTypedocType.description | string | optional | Short 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.fields | Field[] | required | |
docType.groups | { name: string; title?: string }[] | optional | Named 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.icon | string | optional | A 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.internal | boolean | optional | Documents 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.name | string | required | |
docType.orderable | boolean | optional | Whether 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.preview | DocumentTypePreview | optional | How to label documents of this type wherever they're listed - see DocumentTypePreview. |
docType.singleton | boolean | optional | Exactly 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.sort | DocumentTypeSort | optional | A 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.title | string | optional |
Returns: DocumentType
defineField
function defineField<F = Field>(field: F): Ffield | F | required |
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>): booleancondition | Condition | required | |
siblingData | Record<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[] }): stringinput | { blockTypes?: BlockType[]; documentTypes: DocumentType[] } | required |
Returns: string
isAllCondition
function isAllCondition(condition: Condition): condition is { all: Condition[] }condition | Condition | required |
Returns: condition is { all: Condition[] }
isAnyCondition
function isAnyCondition(condition: Condition): condition is { any: Condition[] }condition | Condition | required |
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[]value | string | PreviewChainPart[] | undefined | required |
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 | undefinedvalue | string | DocumentTypePreviewMedia | null | undefined | required |
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).
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
of | Field[] | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "array" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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).
blockType | string | required | |
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "blockRef" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
fields | Field[] | required | |
icon | string | optional | A Phosphor component export name (e.g. "SquaresFourIcon") - purely cosmetic, shown wherever the block type is listed or picked. |
name | string | required | |
preview | DocumentTypePreview | optional | How 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. |
title | string | optional |
BlockTypeFile
blocks/<name>.json - one reusable block type, referenced by blockRef fields.
type BlockTypeFile = BlockType & { $schema?: string }BooleanField
true/false, shown as a toggle.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "boolean" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
action | ButtonFieldAction | required | |
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "button" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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 | |
url | string | required |
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.
allowCustom | boolean | optional | Whether editors may enter any colour. Defaults to true; false constrains both the Dashboard picker and document validation to swatches. |
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
swatches | string[] | optional | Optional 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. |
title | string | optional | |
type | "color" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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" | optional | How 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "date" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "datetime" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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
description | string | optional | Short 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. |
fields | Field[] | required | |
groups | { name: string; title?: string }[] | optional | Named 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. |
icon | string | optional | A 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. |
internal | boolean | optional | Documents 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. |
name | string | required | |
orderable | boolean | optional | Whether 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. |
preview | DocumentTypePreview | optional | How to label documents of this type wherever they're listed - see DocumentTypePreview. |
singleton | boolean | optional | Exactly 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. |
sort | DocumentTypeSort | optional | A 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. |
title | string | optional |
DocumentTypeFile
documents/<name>.json - one document type.
type DocumentTypeFile = DocumentType & { $schema?: string }DocumentTypePreview
media | string | DocumentTypePreviewMedia | null | optional | Which 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. |
subtitle | string | PreviewChainPart[] | optional | Same chain shape as title, for a secondary line under it. No fallback - omitted entirely if unset. |
title | string | PreviewChainPart[] | optional | What 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.
field | string | required | |
refField | string | optional |
DocumentTypeSort
A non-orderable document type's custom default list sort - see DocumentType.sort.
direction | "asc" | "desc" | required | |
field | string | required | |
refField | string | optional |
Field
type Field = StringField | UrlField | TextField | SlugField | NumberField | BooleanField | DateField | DateTimeField | TimeField | ColorField | SelectField | RadioField | ImageField | ImagesField | VideoField | ReferenceField | BlockReferenceField | RichTextField | ObjectField | ArrayField | ButtonFieldFieldCondition
field names a sibling at the same nesting level - never a cross-level or absolute path.
field | string | required | |
op | ConditionOp | required | |
value | string | number | boolean | (string | number)[] | optional |
ImageField
Points at an asset stored via the (forthcoming) R2/Cloudflare Images asset pipeline.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "image" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "images" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "number" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
fields | Field[] | required | |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
preview | DocumentTypePreview | optional | How 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "object" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
_key | string | required | |
_type | "block" | required | |
children | PortableTextSpan[] | required | |
level | number | optional | |
listItem | "number" | "bullet" | optional | |
markDefs | PortableTextMarkDef[] | 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.
_key | string | required | |
_type | "link" | required | |
href | string | required |
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.
_key | string | required | |
_type | "span" | required | |
marks | string[] | optional | |
text | string | required |
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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
options | SelectOption[] | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "radio" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
to | string[] | required | |
type | "reference" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "richText" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
options | SelectOption[] | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "select" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
title | string | required | |
value | string | required |
StringField
A single line of plain text - a title, a name, a label. Use text for anything multi-line.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "string" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
$schema | string | optional | |
items | Structure | required |
StructureItem
type StructureItem = NavLink | NavGroupTextField
Multi-line plain text (no formatting) - stored as one string with newlines. Use richText for formatted content.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "text" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "time" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "url" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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>.
defaultValue | unknown | optional | Value 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. |
description | string | optional | |
fieldId | string | optional | Stable 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. |
group | string | optional | Which 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). |
internal | boolean | optional | Inverse 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. |
localized | boolean | optional | Stores 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). |
name | string | required | |
placeholder | string | optional | Ghost 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. |
readOnly | boolean | optional | Hides 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. |
renamedFrom | string | optional | A 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. |
required | boolean | optional | |
showWhen | Condition | optional | Hides 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. |
title | string | optional | |
type | "video" | required | |
validation | { integer?: boolean; max?: number; min?: number; pattern?: string } | optional | Extra 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. |