Field types
Every field type the schema can declare, with its knobs, defaults, and the YAML it produces on disk.
The CMS supports 15 field types. Every field has the same set of common
keys (FieldBase) plus type-specific extras. This page is exhaustive —
use the table of contents on the right to jump to the type you need.
Common to every field
- key: heading # frontmatter key — where the value lands
type: text # discriminator — selects the field implementation
label: Heading # author-facing label, shown above the input
description: … # optional helper text, shown under the field
required: true # validator enforces non-empty
hidden: true # never rendered (value seeded from `default`)
default: 'Welcome' # initial value for new entries
visibleIf: # show only when the condition is true
field: tier
op: eq
value: pro
whenHidden: keep # keep | drop — what to do with stored value
customValidators: [] # plugin validators, see Validation
computed: # auto-derived value, see Computed fields
from: title
transform: slug
mode: auto| Key | Effect |
|---|---|
key | The frontmatter key the value lands at. Must be unique inside its group. |
type | Type discriminator. The remaining knobs are type-specific. |
label | Author-facing label. Required on every field (no fallback to key). |
description | Helper text rendered under the input. |
required | Reject save when empty. See Validation → required. |
hidden | Never rendered. Value still seeded from default so saves don't strip it. |
default | Initial value (literals only; no expressions). |
visibleIf | Hide unless the condition evaluates true. See Conditional rendering. |
whenHidden | keep (default) preserves the stored value when hidden; drop purges on save. |
customValidators | Plugin validators applied in addition to the built-ins. |
computed | Auto-fill the value from another field. See Computed fields. |
text
Single-line text input.
- key: title
type: text
label: Title
default: ''
minLength: 5
maxLength: 80
recommendedLength: { min: 30, max: 60 }
regex: '^[A-Z]'
regexMessage: 'Start with a capital.'
minLengthMessage: 'Make it at least 5 characters.'| Knob | Effect |
|---|---|
minLength | Hard lower bound (chars). Violations surface in the Errors panel. |
maxLength | Hard upper bound. |
recommendedLength | Soft sweet-spot range. The counter turns green inside, amber outside. |
regex | JavaScript-flavoured regex source (no surrounding slashes). |
regexMessage | Custom error text for regex. |
*Message variants | Override the default error text for required / minLength / maxLength. |
Use text for short single-line values (titles, slugs, IDs). For multi-line
plain text use paragraph. For rich formatting use richtext.
paragraph
Multi-line plain-text input. Same length / regex knobs as text.
- key: description
type: paragraph
label: Description
rows: 3
recommendedLength: { min: 50, max: 160 }| Knob | Effect |
|---|---|
rows | Initial visible row count (auto-grows). |
Use paragraph for SEO descriptions, summaries, social-card copy — anywhere
the content is plain text but multi-line.
richtext
Tiptap-based rich-text editor. Toolbar buttons are opt-in.
- key: body
type: richtext
label: Body
format: markdown
toolbar:
- bold
- italic
- heading2
- heading3
- link
- bulletList
- orderedList
- code
- codeBlock
- blockquote| Knob | Values | Effect |
|---|---|---|
format | markdown (default), html | Storage format the value round-trips through. |
toolbar | array of button names | Which buttons render. Omit for the default set. |
format: markdown writes the value as Markdown — typically the .md file's
body. format: html writes HTML. Pick markdown when your build pipeline
already runs the file through a Markdown processor; pick html when you
want the CMS to do the rendering and you're storing pre-rendered output.
number
Numeric input. Integer when step: 1, decimals otherwise.
- key: seats
type: number
label: Seats
min: 1
max: 100
step: 1
default: 1| Knob | Effect |
|---|---|
min | Reject save when below. |
max | Reject save when above. |
step | Increment (also drives integer vs decimal). |
For a sliding-control UI prefer range. For a set of allowed values use select.
boolean
Toggle. The simplest field type.
- key: featured
type: boolean
label: Featured
default: falseNo type-specific knobs beyond default.
date
Date picker. Stores as ISO-8601 yyyy-mm-dd.
- key: publishedAt
type: date
label: Published at
default: ''No time component. Use a text field with a regex if you need ISO datetimes
or other formats.
color
Colour picker.
- key: accent
type: color
label: Accent colour
format: hex
default: '#3b33c7'| Knob | Values | Effect |
|---|---|---|
format | hex (default), oklch | hex writes #rrggbb; oklch writes oklch(...) — useful when your theme runs through Tailwind v4 / OKLch. |
range
Slider with min / max / step.
- key: spacing
type: range
label: Spacing
min: 0
max: 100
step: 5
default: 50Same min / max / step semantics as number but renders as a slider control.
Good fit for opacity, spacing scale, or any value the author tweaks visually.
multichoice
Multi-select with min / max constraints. Value is string[].
- key: tags
type: multichoice
label: Tags
options:
- travel
- food
- sport
min: 1
max: 3| Knob | Effect |
|---|---|
options | The allowed values. Strings or {value, label, description} objects. |
min | Reject save when fewer than this many are selected. |
max | Reject save when more than this many are selected. |
Multichoice arrays diff as + added, − removed pills in the
Version history — the schema-aware renderer
recognises the set semantics.
link
URL field. Same regex semantics as text.
- key: ctaUrl
type: link
label: CTA URL
regex: '^https?://'Use this over text when the intent is "this is a hyperlink" — UX hints +
validators tune to URLs. The author can still paste anything; the regex
enforces the actual shape.
image
Picker + drag-drop upload. With storage: set, the picker scopes to that
storage and the upload routes through it.
- key: hero
type: image
label: Hero image
storage: uploads
accept: image/png, image/jpeg
maxSize: 5242880| Knob | Effect |
|---|---|
storage | Named storage from config.yml#storages. See Media library. |
accept | Comma-separated MIME glob list. Overrides the storage's accept. |
maxSize | Bytes. Overrides the storage's max_size. |
The editor renders the value as an inline thumbnail. For non-images use file.
file
Same as image but for non-image attachments (PDFs, videos, audio).
- key: attachment
type: file
label: Attachment
storage: uploads
accept: application/pdf
maxSize: 10485760The editor renders the value as a file row (name + icon + type + size) rather than a thumbnail.
select
Single-select with an explicit option list.
- key: tier
type: select
label: Tier
options:
- free
- pro
- enterprise
default: free
widget: dropdown| Knob | Values | Effect |
|---|---|---|
options | array of strings or {value, label, description} | The allowed values. |
default | one of the option values | Initial value. |
widget | dropdown (default), radio, buttons, combobox | Controls the input UI. |
dropdown is the universal default. radio for short lists where every option
should be visible. buttons for visual toggles. combobox for long lists
where the author needs to type-to-search.
group
Object field — a nested set of fields under a single key.
- key: hero
type: group
label: Hero
fields:
- key: heading
type: text
label: Heading
- key: subheading
type: paragraph
label: Subheading
preview:
title: heading| Knob | Effect |
|---|---|
fields | Ordered list of child fields. Recursive — groups can contain groups. |
preview | Drives the "drill-in card" rendering when the parent surfaces this group. |
The value is {heading, subheading} in YAML; the editor renders the inner
fields inline at the parent level (no separate drill-in card unless you mount
the group as an array item).
array
The richest field type. Each items[] entry is one allowed item shape — a
group, a $ref to a block, or a primitive ({type: text} for arrays of
strings).
- key: blocks
type: array
label: Content blocks
items:
- $ref: hero-compact
- $ref: media-text
idKey: _type
minItems: 1
maxItems: 10
uniqueOn: slug
uniqueOnMessage: 'Each block needs a unique slug.'| Knob | Effect |
|---|---|
items | One entry per allowed item shape. Single-entry = uniform array; multi-entry = multi-shape. |
idKey | Multi-shape discriminator key on items. Defaults to _type. Override for content that uses kind:, block:, etc. |
minItems | Reject save when below this count. |
maxItems | Reject save when above this count; "Add item" disables at the cap. |
uniqueOn | Relative path within an item; rejects duplicates. |
*Message | Custom messages for any of the above. |
Single-shape
items:
- type: text
key: tag
label: TagEvery item is the same shape. No discriminator key on items.
Multi-shape
items:
- $ref: hero-compact
- $ref: media-text
- $ref: stats-rowEach item carries a discriminator key (_type: by default) matching one
entry's $ref target. The CMS reads + writes through this key. Changing
idKey: after content exists requires a one-shot migration to rename the
key on each item.
Primitive
items:
- type: text
key: tagArrays of strings, numbers, etc. No discriminator needed (the JS type is the discriminator). The editor renders a compact list with inline controls.