Fields
Field types
Section titled “Field types”| Field | Storage | Admin Component |
|---|---|---|
text |
text |
Input or Textarea (with rows) |
slug |
text (unique) |
Auto-generated from source field |
email |
text |
Email input |
number |
integer |
Number input |
boolean |
integer (0/1) |
Checkbox |
date |
text (ISO 8601) |
Date picker |
select |
text |
Select dropdown |
color |
text (hex) |
Palette swatch dropdown |
link |
text (JSON) |
Internal/external link control |
richText |
text (JSON AST) |
Tiptap editor |
content |
text (JSON AST) |
Tiptap editor with inline blocks |
image |
text |
Image picker with upload/browse |
relation |
text (reference ID) |
Combobox with search |
array |
text (JSON) |
Comma-separated input (of: fields.text()), else JSON textarea |
json |
text (JSON) |
Textarea or custom component |
blocks |
text (JSON) |
Drag-and-drop block editor |
Common options
Section titled “Common options”All fields accept these options:
| Option | Type | Description |
|---|---|---|
required |
boolean |
Validate as non-empty on save |
label |
string |
Custom label (defaults to humanized field name) |
description |
string |
Text shown below the label |
defaultValue |
varies | Initial value for new documents |
translatable |
boolean |
Store per-locale in translations table |
indexed |
boolean |
Add database index |
unique |
boolean |
Enforce unique values (defaults to true for slug, false otherwise) |
condition |
{ field, value } |
Show/hide based on another field |
admin.placeholder |
string |
Input placeholder text |
admin.rows |
number |
Textarea height (text fields) |
admin.help |
string |
Help text below the input |
admin.position |
"content" | "sidebar" |
Where the field renders in the edit form. Defaults to "content" |
admin.group |
string | object |
Titled, optionally collapsible panel in the edit form — consecutive fields sharing a group render together (see Admin UI) |
admin.hidden |
boolean |
Hide from admin UI |
admin.component |
string |
Custom admin component (see Admin UI) |
Type-specific options
Section titled “Type-specific options”| Field | Option | Type | Description |
|---|---|---|---|
text |
maxLength |
number |
Maximum character length, validated on save |
slug |
from |
string |
Field name the slug is auto-generated from |
select |
options |
string[] (required) |
Allowed values |
relation |
collection |
string (required) |
Target collection slug |
relation |
hasMany |
boolean |
Store an array of IDs instead of one |
relation |
maxItems |
number |
Max selected documents (hasMany only) |
array |
of |
field (required) | Item field type, e.g. fields.text() |
array |
maxItems |
number |
Max items, enforced on save |
json |
itemFields |
Record<string, field> |
Typed repeater rows (with admin.component: "repeater") |
content |
blocks |
object |
Inline component block types (same shape as blocks types) |
content |
fullscreen |
boolean |
Distraction-free overlay button. Defaults to true |
blocks |
types |
object (required) |
Block types, each a map of sub-fields |
blocks |
shared |
boolean |
Allow shared section references. Defaults to true |
Content
Section titled “Content”Use fields.content(...) for long-form editing where prose and inline component blocks should live in one ordered stream, such as a blog post body.
body: fields.content({ translatable: true, admin: { rows: 14 }, fullscreen: true, blocks: { faq: { heading: fields.text(), items: fields.json({ admin: { component: "repeater" }, }), }, image: { images: fields.array({ of: fields.image(), defaultValue: [] }), }, },});The field stores a rich-text document whose children can also include inline component blocks. Each inline block uses a blockType and fields payload, so the schema is similar to fields.blocks(...), but the block appears inside the writing flow instead of as a separate page-section list.
Render content fields with ContentRenderer, which interleaves prose and the configured block components. Use fields.richText(...) when the editor only needs formatted text, and use fields.blocks(...) when the editor should manage standalone reusable page sections.
Content options
Section titled “Content options”| Option | Type | Description |
|---|---|---|
blocks |
object |
Inline component block types, keyed by blockType (same shape as fields.blocks(...) types) |
fullscreen |
boolean |
Add a button that expands the editor into a distraction-free overlay. Defaults to true; set false to hide the button |
When fullscreen is enabled, a maximize button appears in the top-right of the editor. Activating it opens a fullscreen overlay that hides the sidemenu and every other field, drops the editor’s border, and centers the writing column under a slim header showing the field label and an Exit button. Press Escape or Exit to return; edits are preserved, so you save from the normal form afterwards.
Blocks
Section titled “Blocks”fields.blocks({ types: { hero: { heading: fields.text({ required: true }), body: fields.text(), ctaLabel: fields.text(), ctaHref: fields.text(), }, text: { heading: fields.text(), content: fields.richText(), }, faq: { heading: fields.text(), items: fields.json({ defaultValue: [], admin: { component: "repeater" }, }), }, },});Block sub-fields accept any field type. Common choices: text, number, boolean, select, richText, image, relation, array, json, color.
The repeater component renders JSON arrays as sortable add/remove item cards. Declare
itemFields to give rows typed controls instead of free-form JSON — including link
sub-fields, which get the internal-link picker:
services: fields.json({ admin: { component: "repeater" }, itemFields: { title: fields.text({ required: true }), body: fields.text({ admin: { rows: 3 } }), link: fields.link(), },}),Repeaters work both as block sub-fields and as top-level collection fields — useful for fixed-slot templates where a section holds a list of cards.
Item limits and ordering
Section titled “Item limits and ordering”List-shaped fields (relation with hasMany, array, and json repeaters) accept
maxItems — a declarative cap enforced centrally on save (Field "x" allows at most N items.)
and reflected in the admin picker, which shows a 2/4 selected counter and stops accepting
picks at the limit:
newsItems: fields.relation({ collection: "articles", hasMany: true, maxItems: 4 }),The order of a hasMany selection is data: it is stored and rendered as-is. The admin
renders selections as drag-sortable rows (grip handle, #index, remove) by default.
No option needed.
Colors
Section titled “Colors”fields.color(...) stores a hex string and renders a dropdown of predefined swatches. Editors pick a named color instead of typing a hex value.
Define the palette once in your CMS config and every color field offers it:
export default defineConfig({ admin: { colors: [ { label: "Blue", value: "#4000FF" }, { label: "Pink", value: "#FFDBEB" }, { label: "Black", value: "#000000" }, ], }, collections: [...],});// Uses the global admin.colors palettebackgroundColor: fields.color(),Override the palette for a single field with colors — it wins over the global list:
accentColor: fields.color({ colors: [ { label: "Brand", value: "#4000FF" }, { label: "Ink", value: "#000000" }, ],}),The stored value is the selected hex string ("" when cleared). Color fields work both as top-level fields and as block sub-fields. See Admin UI → Colors for the global palette.
Shared sections
Section titled “Shared sections”Editors can save a block as a shared section, insert it into other block fields, and detach it later to make a local copy. Shared sections live in the admin sidebar under Shared Sections and are stored as normal draft/versioned content.
fields.blocks({ types: { hero: { heading: fields.text({ required: true }), body: fields.text(), }, },});Shared sections are currently supported by the fields.blocks(...) editor. This is the right field type for page section builders where editors should reuse whole components, such as heroes, CTAs, FAQ sections, or feature grids.
Set shared: false for block fields that should never use shared sections, such as form builders:
fields.blocks({ shared: false, types: { text: { name: fields.text({ required: true }), label: fields.text({ required: true }), }, },});fields.content(...) is for mixed prose and inline component blocks, such as a post body. Shared sections can be inserted from the editor’s / slash menu (listed after the component blocks). The block renders as a linked “Shared” card that resolves to the source on the public page. Each inline block also has header actions: a regular block can be saved as a shared section (turning it into a reference), and a shared block can be detached back into a local, editable copy, using the same controls as the fields.blocks(...) editor.
Conditional fields
Section titled “Conditional fields”Show/hide fields based on a select or boolean field’s value:
postType: fields.select({ options: ["article", "video", "podcast"],}),videoUrl: fields.text({ condition: { field: "postType", value: "video" },}),The value can be a string, boolean, or array of strings (matches any).
Field-level access
Section titled “Field-level access”Fields support read and update access rules:
import { hasRole } from "@kidecms/core";
summary: fields.text({ access: { read: hasRole("admin"), // hidden from non-admins },}),seoDescription: fields.text({ access: { update: hasRole("admin"), // read-only for non-admins },}),| Rule | Effect in admin UI | Effect on save |
|---|---|---|
read |
Field is completely hidden | Field excluded from response |
update |
Field is rendered as read-only (disabled) | Field value silently preserved (changes stripped) |
Both rules receive the same context as collection-level access rules: { user, doc, operation, collection }.
AI assistant
Section titled “AI assistant”The admin includes optional AI features powered by the Vercel AI SDK. Add AI_PROVIDER=openai and AI_API_KEY to your .env to enable them. AI_MODEL is optional and defaults to gpt-4o-mini. Currently openai is the only supported provider; other values throw an error. When configured, AI buttons appear automatically for:
- Alt text generation on asset detail pages
- SEO descriptions on post/page edit forms
- Translation with per-field “Translate from EN” buttons that handle both plain text and rich text (preserving JSON AST structure)
Without the AI env vars, all AI buttons are hidden and no AI dependencies are loaded.