Skip to content

Fields

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

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)
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

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.

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.

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.

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.

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 palette
backgroundColor: 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.

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.

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).

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 }.

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.