TentoCMS
Api

Field Types

All 16 field types across 4 categories, with their options and use cases.

TentoCMS supports 16 field types organized into 4 categories. Each field type has specific options and use cases.

Field Type Categories

Text Fields

Suitable for textual content with various formatting options.

text

Single-line text input field.

Use cases:

  • Page titles
  • Article headlines
  • Names
  • Short descriptions
  • URLs

Options:

{
  placeholder?: string     // Placeholder text
  helpText?: string        // Helper message
  minLength?: number       // Minimum length (default: 0)
  maxLength?: number       // Maximum length (default: unlimited)
  pattern?: string         // Regex pattern the value must match
  patternMessage?: string  // Custom message shown when pattern validation fails
}

Example schema:

{
  "name": "title",
  "label": "Page Title",
  "type": "text",
  "required": true,
  "options": {
    "placeholder": "Enter page title",
    "maxLength": 200
  }
}

Editor experience:

  • Single line input
  • Character counter
  • Validation errors on save
  • Real-time length validation

textarea

Multi-line text input field.

Use cases:

  • Excerpts
  • Descriptions
  • Short biographies
  • Product summaries
  • Meta descriptions

Options:

{
  placeholder?: string     // Placeholder text
  helpText?: string        // Helper message
  minLength?: number       // Minimum length
  maxLength?: number       // Maximum length
  pattern?: string         // Regex pattern the value must match
  patternMessage?: string  // Custom message shown when pattern validation fails
}

Example schema:

{
  "name": "excerpt",
  "label": "Article Excerpt",
  "type": "textarea",
  "required": false,
  "options": {
    "placeholder": "Brief summary of the article",
    "maxLength": 500
  }
}

Editor experience:

  • Multi-line text box
  • Character counter
  • Resizable interface
  • Paste support

wysiwyg

Rich text editor with formatting capabilities (WYSIWYG - What You See Is What You Get). Powered by Tiptap editor.

Use cases:

  • Blog post content
  • Long-form articles
  • Page body content
  • Rich descriptions

Options:

{
  placeholder?: string // Placeholder text
  helpText?: string    // Helper message
}

Supported formats:

FormatDescriptionToolbar Button
BoldBold textB button
ItalicItalic textI button
UnderlineUnderlined textU button
StrikethroughStrikethrough textS button
Inline CodeInline code formatting</> button
HeadingsH1 through H6Dropdown menu
Bullet ListUnordered lists with nestingBullet icon
Numbered ListOrdered lists with nestingNumber icon
BlockquoteQuote blocksQuote icon
Code BlockMulti-line code blocksCode icon
Horizontal RuleDividing lineDivider icon
LinkHyperlinks with URL validationLink icon
ImageImages from media libraryImage icon
Text AlignLeft, center, right alignmentAlign icons
Undo/RedoHistory navigationArrow icons

Storage format: An HTML string. Every write is sanitized on the server against the allow-list below, whatever it came from — the admin editor, the write API, MCP, a migration or a backup restore.

Embedded images are stored as a reference, not a URL. An image inserted through the editor is saved as:

<img src="/api/v1/media/<mediaId>/file" alt="…">

That src carries the media id and no host, so the content survives being restored into a different environment. It is rewritten on read to the servable URL, exactly as an image field is:

<img src="https://tento-api.example.com/api/v1/media/<projectId>/<r2Key>" alt="…">

Treat the delivered src as the URL and use it directly. The stored form is an internal reference, not an address — it is relative and does not resolve on its own, exactly as the { "_media": "<id>" } shape used by media fields is not a URL either.

One case delivers the stored form: if the media item has been deleted, its <img> is left exactly as stored rather than emptied or dropped, so the reference is still visible for diagnosis — an empty src makes the browser re-request the page, and a removed tag would lose authored content silently. A src that is relative and matches /api/v1/media/<id>/file is therefore the signal that the underlying media no longer exists; every resolvable image is delivered as an absolute URL.

Allowed HTML tags:

p, h1, h2, h3, h4, h5, h6, blockquote, pre, code,
ul, ol, li, br, hr, strong, b, em, i, u, s, del,
a, span, div, img,
table, thead, tbody, tfoot, tr, th, td, colgroup, col, caption

Allowed attributes, per tag:

TagAttributes
ahref, target, rel, title, class
imgsrc, alt, title, width, height, class
p, h1–h6, blockquoteclass, style
span, div, pre, code, liclass
ulclass, type (disc, circle or square)
olclass, start, type
tableclass, style, border, cellpadding, cellspacing, width
th, tdclass, style, colspan, rowspan, width, height, colwidth (th also scope)
colclass, style, span, width
colgroupclass, span
thead, tbody, tfoot, tr, captionclass

Allowed inline styles: text-align on p, h1–h6, blockquote, th and td; width and min-width in pixels on table and col. Any other declaration is removed, and a style containing url(, expression(, quotes, escapes or comments is removed whole.

Allowed URLs: href may be http, https, mailto, tel or relative (including # and ?); src may be http, https or relative. javascript:, vbscript: and data: are removed, however they are encoded.

What the server does with anything else:

  • Strips it and saves the rest. A disallowed element is unwrapped and its text kept; script, style, iframe, object, embed, form controls, svg, math, template and similar are removed together with their contents. Comments are dropped. A write is not rejected for its markup — with one exception: content is validated after it is sanitised, so a required field that held nothing but disallowed markup is empty once sanitised, and is refused as missing.
  • Sanitises markup the schema doesn't describe. A key the type does not declare, or a value without the structure its field promises (a rich-text value that is not a string, a component picked without its fields), may still be rendered as HTML by a frontend, so every string in it that contains < is sanitised as rich text too.
  • Adds rel="noopener noreferrer" to links that open a new tab or point at another site (including protocol-relative //host links), keeping any rel values already there.
  • Reports what it removed. A write that removed anything returns a warnings array beside data:
    {
      "data": { "…": "…" },
      "warnings": [
        {
          "field": "sections[2].body",
          "code": "RICH_TEXT_SANITIZED",
          "message": "Removed disallowed markup from rich text: img[onerror]",
          "removed": ["img[onerror]"]
        }
      ]
    }
    

    field is the path to the value inside the content. A ?dryRun=true write returns the same warnings for what it would remove, whether or not the content is valid.

HTML the admin editor produces is already within the allow-list and is stored byte-for-byte.

Changing a schema re-sanitises existing content. Text fields are not HTML, so markup typed into one is stored as written. Changing a page type's, collection type's or component's schema — for example making a field wysiwyg — or creating a component under a slug content still uses starts a background pass that removes disallowed markup from the project's stored pages and collection items (drafts, published content and the trash) against the new schemas. It usually finishes within seconds of the save; a second pass follows about fifteen minutes later. Markup that is already allowed is left byte-for-byte, and cached public responses are purged as content changes.

Variables in rich text. #{KEY} tokens are replaced when content is read. In a rich-text field a value is inserted as text: <, >, & and quotes are escaped, so markup in a value shows literally rather than becoming part of the page, and the result is checked against the allow-list again — a value that gives an href or src a disallowed scheme loses that attribute, and a link it points at another site gets rel="noopener noreferrer". In fields the schema declares as any other type, the value is inserted exactly as written. So Smith & Co arrives as Smith &amp; Co in rich text and Smith & Co in a text field, which render the same. Where the schema cannot say whether a value is rich text — its type or component has been deleted, or the key is not in the schema — the value is treated as rich text if it, or a variable substituted into it, contains <. Variables are not substituted into reference, image or media fields.

Example schema:

{
  "name": "content",
  "label": "Article Content",
  "type": "wysiwyg",
  "required": true,
  "options": {
    "placeholder": "Write your article..."
  }
}

Editor experience:

  • Full-featured toolbar with formatting controls
  • Link modal for URL insertion with validation
  • Media library integration for images
  • Keyboard shortcuts (Cmd/Ctrl+B for bold, etc.)
  • Real-time HTML sanitization
  • Prose-styled content preview
  • Dark mode support

Media Fields

For managing images and file uploads.

image

Image selection from media library.

Use cases:

  • Featured images
  • Hero section images
  • Product photos
  • Background images
  • Thumbnails

Options:

{
  helpText?: string          // Helper message
  allowedMimeTypes?: string[]// e.g., ["image/jpeg", "image/png"]
  maxFileSize?: number       // File size limit in bytes
}

Example schema:

{
  "name": "featuredImage",
  "label": "Featured Image",
  "type": "image",
  "required": false,
  "options": {
    "allowedMimeTypes": ["image/jpeg", "image/png", "image/webp"],
    "maxFileSize": 5242880
  }
}

Editor experience:

  • Media library browser
  • Upload new images
  • Crop/resize tools
  • Alt text editor
  • Image preview

Stored value shape:

The value saved in draftContent/publishedContent is normally a plain string — the media item's UUID:

{ "featuredImage": "550e8400-e29b-41d4-a716-446655440000" }

The admin UI's image field (apps/admin/app/components/fields/ImageField.vue) writes this bare-string form when an editor picks an image. Content produced by a CMS migration is instead written as a single-key object, { "_media": "<uuid>" } (see Migrations), and that shape is left alone unless the editor re-picks or clears the field. The field component reads both transparently.

The delivered shape does not depend on how the value was stored. Whichever shape a media field holds, the public API and the admin preview endpoints resolve it the same way: normalizeContentFields() canonicalises it against the page-type (or collection-type) schema, then resolveReferences() expands it. In the default ?media=object mode you get a media object (_url, _mimeType, and _width/_height/_altText when the item has them); in ?media=url you get the URL string alone. See Media Serialization for both shapes.

The shapes recognised are a bare id string, { "_media": "<uuid>" }, { "id": "<uuid>", … }, and the conventional mediaId / imageId / fileId / media keys — the same set the content validator accepts, so anything storable is resolvable.

Two consequences worth knowing before you build against it:

  • Resolution is driven by the declared field type, not by the value. A UUID stored in a text field stays text. If a media id is sitting in a text-typed field (which a migration can produce when it infers the type from sparse sample data), it will not resolve — fix the schema, not the content.
  • A dangling id resolves to null, not to the id. If the media item has been deleted, the field is null; it does not fall back to returning the raw UUID.

This is all separate from blog posts' dedicated featuredImage/featuredImageId pair, which is a hardcoded model field rather than this schema-driven image type. It is resolved by enrichBlogPostMedia — see Media Admin API for that shape.


media

Any file type from media library.

Use cases:

  • PDFs
  • Documents
  • Videos
  • Audio files
  • Downloadable assets
  • Mixed media collections

Options:

{
  helpText?: string          // Helper message
  allowedMimeTypes?: string[]// Allowed file types
  maxFileSize?: number       // File size limit in bytes
}

Example schema:

{
  "name": "downloadFile",
  "label": "Download",
  "type": "media",
  "required": false,
  "options": {
    "allowedMimeTypes": ["application/pdf", "application/msword"],
    "maxFileSize": 10485760
  }
}

Editor experience:

  • Media library browser
  • File type filtering
  • Upload interface
  • File preview
  • Metadata display

Stored value shape:

Same as image above — a plain string (the media item's UUID) in the common case:

{ "downloadFile": "550e8400-e29b-41d4-a716-446655440000" }

with the same { "_media": "<uuid>" } migration-content exception, the same admin field normalisation behaviour (apps/admin/app/components/fields/MediaField.vue), and the same content-validator caveat — see the image field's stored-value note above for the full detail.

See Media Admin API and Media API (public) for the full resolved Media entity shape.


Data Fields

For structured data entry and selection.

number

Numeric input field with optional constraints.

Use cases:

  • Prices
  • Quantities
  • Ratings/scores
  • Page order
  • Product inventory
  • Percentages

Options:

{
  placeholder?: string // Placeholder text
  helpText?: string    // Helper message
  min?: number         // Minimum value
  max?: number         // Maximum value
  step?: number        // Increment step (default: 1)
}

Example schema:

{
  "name": "price",
  "label": "Product Price",
  "type": "number",
  "required": true,
  "options": {
    "min": 0,
    "max": 10000,
    "step": 0.01
  }
}

Editor experience:

  • Number input with spinners
  • Min/max validation
  • Step controls
  • Number formatting

boolean

Toggle/switch field for true/false values.

Use cases:

  • Feature flags
  • Publish status
  • Show/hide sections
  • Yes/no questions
  • Enable/disable options

Options:

{
  helpText?: string // Helper message
}

Example schema:

{
  "name": "isFeatured",
  "label": "Featured Product",
  "type": "boolean",
  "required": false,
  "options": {
    "helpText": "Show this product in the featured section"
  }
}

Editor experience:

  • Toggle switch
  • Yes/no labels
  • Simple on/off interaction

date

Date picker field (YYYY-MM-DD format).

Use cases:

  • Event dates
  • Publication dates
  • Birth dates
  • Deadlines
  • Product launch dates
  • Availability dates

Options:

{
  helpText?: string // Helper message
}

Example schema:

{
  "name": "eventDate",
  "label": "Event Date",
  "type": "date",
  "required": true,
  "options": {
    "helpText": "When is this event happening?"
  }
}

Editor experience:

  • Calendar picker
  • Type-in date input
  • Keyboard navigation
  • Today button
  • Format validation

datetime

Date and time picker. The admin UI stores values in ISO 8601 format, but server-side validation (isValidDateTime() in packages/shared/src/schema/validator.ts) accepts any string new Date() can parse — it does not enforce strict ISO 8601 formatting. Content written directly via the API can therefore store non-ISO date strings.

Use cases:

  • Event start times
  • Scheduled publishing
  • Application deadlines
  • Meeting times
  • Timestamp recordings
  • Scheduled actions

Options:

{
  helpText?: string // Helper message
}

Example schema:

{
  "name": "publishedAt",
  "label": "Published Date & Time",
  "type": "datetime",
  "required": false,
  "options": {
    "helpText": "When should this content be published?"
  }
}

Editor experience:

  • Calendar date picker
  • Time input
  • Timezone support
  • ISO format display
  • Relative time

color

Color picker field.

Use cases:

  • Theme colors
  • Text colors
  • Background colors
  • Brand colors
  • Visual indicators
  • Design tokens

Options:

{
  helpText?: string // Helper message
}

Supported formats:

  • Hex colors: #FF0000, #F00
  • RGB: rgb(255, 0, 0)
  • RGBA: rgba(255, 0, 0, 0.5)
  • HSL: hsl(0, 100%, 50%)
  • HSLA: hsla(0, 100%, 50%, 0.5)
  • Named colors: red, blue, etc.

Example schema:

{
  "name": "brandColor",
  "label": "Brand Color",
  "type": "color",
  "required": true,
  "options": {
    "helpText": "Primary brand color"
  }
}

Editor experience:

  • Color picker widget
  • Format conversion
  • Copy hex/rgb/hsl
  • Color history
  • Transparency slider

json

Raw JSON editor for complex data structures.

Use cases:

  • Configuration data
  • Complex data structures
  • Custom properties
  • Settings objects
  • Metadata
  • Analytics data

Options:

{
  helpText?: string // Helper message
}

Example schema:

{
  "name": "metadata",
  "label": "Custom Metadata",
  "type": "json",
  "required": false,
  "options": {
    "helpText": "Custom JSON data for this page"
  }
}

Editor experience:

  • Syntax-highlighted JSON editor
  • Format validation
  • Pretty-print
  • Collapse/expand
  • JSON linting

select

Dropdown with a predefined set of options.

Use cases:

  • Status values
  • Category selection from a fixed list
  • Layout/variant choices
  • Sizes
  • Any single-choice value from a known set

Options:

{
  helpText?: string          // Helper message
  choices?: Array<{          // Predefined options
    label: string            // Display label
    value: string            // Stored value
  }>
}

Example schema:

{
  "name": "status",
  "label": "Status",
  "type": "select",
  "required": true,
  "options": {
    "choices": [
      { "label": "Draft", "value": "draft" },
      { "label": "Published", "value": "published" },
      { "label": "Archived", "value": "archived" }
    ]
  }
}

Validation:

  • The stored value must be a string.
  • If choices are defined, the value must match one of the defined value entries.

Editor experience:

  • Dropdown selector
  • Single choice
  • Predefined options only

Advanced Fields

For managing relationships and component composition.

reference

Link to items in a collection type.

Use cases:

  • Author references
  • Category assignments
  • Related items
  • Product variants
  • Department assignments
  • Team members

Options:

{
  helpText?: string       // Helper message
  collectionType?: string // Target collection slug — required only in the default/collectionItem
                           // reference mode (referenceType unset or 'collectionItem'); forbidden
                           // when referenceType is 'user'/'blogPost'; unused when 'page'
  multiple?: boolean      // Allow multiple selections
  referenceType?: 'page' | 'collectionItem' | 'user' | 'blogPost'  // Reference mode (default: 'collectionItem')
  pageType?: string       // Target page type slug — only applicable when referenceType is 'page'
}

referenceType selects the reference mode: the default/collectionItem mode links to a collection item (and requires collectionType); page links to a page (optionally scoped by pageType, collectionType unused); user/blogPost link to a project member or blog post respectively and must not set collectionType or pageType at all.

Example schema:

{
  "name": "author",
  "label": "Author",
  "type": "reference",
  "required": true,
  "options": {
    "collectionType": "authors",
    "helpText": "Select the article author"
  }
}

Multiple selections:

{
  "name": "relatedProducts",
  "label": "Related Products",
  "type": "reference",
  "required": false,
  "options": {
    "collectionType": "products",
    "multiple": true,
    "helpText": "Select related products to display"
  }
}

Editor experience:

  • Collection item browser
  • Search/filter
  • Multi-select (if enabled)
  • Item preview
  • Batch selection

Stored value shape:

For a single reference (multiple unset or false), the stored value is an object matching ReferenceValue (packages/shared/src/types/index.ts):

{ "author": { "_ref": "550e8400-e29b-41d4-a716-446655440000", "_type": "collectionItem" } }

_type is one of 'page' | 'collectionItem' | 'user' | 'blogPost' and always matches the field's referenceType option. For multiple: true, the stored value is an array of the same shape:

{
  "relatedProducts": [
    { "_ref": "550e8400-e29b-41d4-a716-446655440000", "_type": "collectionItem" },
    { "_ref": "660e8400-e29b-41d4-a716-446655440001", "_type": "collectionItem" }
  ]
}

A bare string ID (or an array of bare string IDs, for multiple) is also accepted for backward compatibility, by both the content validator (validateReferenceField() in packages/shared/src/schema/validator.ts) and the admin reference field (apps/admin/app/components/fields/ReferenceField.vue). The admin UI's picker, however, always writes the { _ref, _type } object form — the bare-string form only appears in older or externally-written content.

Delivered shape: a populated reference is expanded into the referenced entity — full content for a collection item, minimal metadata for a page, blog post or user, depending on _type. This does not depend on which internal shape the value was stored in: a bare id string and { "_ref": …, "_type": … } deliver the same thing.

Resolution continues into the referenced entity, so an image/media field on an embedded collection item is delivered as a URL exactly as it would be on the page itself. Nesting depth does not change what you receive.

Two bounds apply:

  • Depth. Collection items nested more than five levels deep are not expanded — see REFERENCE_RESOLUTION_MAX_DEPTH. Content deeper than that is returned as its reference.
  • Declared type. As with media, resolution follows the field's declared type. A reference id stored in a text field is returned as the string it is.

componentPicker

Select and arrange multiple component instances.

Use cases:

  • Page section builders
  • Feature lists
  • Gallery layouts
  • Content blocks
  • Widget composition
  • Modular pages

Options:

{
  helpText?: string           // Helper message — accepted by the schema, but see note below
  allowedComponents?: string[]// Component slugs allowed (optional — omit to allow any component)
  maxItems?: number           // Maximum number of items
}

helpText is not exposed in the admin UI for this field type. The schema accepts it, and the API will store it if set directly, but the schema builder's field editor hides the help-text input entirely for componentPicker (and nestedComponent) fields — editors can't set it through the UI.

Example schema:

{
  "name": "sections",
  "label": "Page Sections",
  "type": "componentPicker",
  "required": false,
  "options": {
    "allowedComponents": ["hero", "features", "testimonials", "cta"],
    "maxItems": 20
  }
}

Editor experience:

  • Component selector
  • Drag-to-reorder
  • Add/remove components
  • Component preview
  • Inline editing
  • Duplicate items

nestedComponent

Embed a single component instance inline.

Use cases:

  • Required page sections
  • Header/footer
  • Sidebar widgets
  • Fixed layout sections
  • Mandatory components

Options:

{
  helpText?: string      // Helper message — accepted by the schema, but hidden in the admin UI (see note below)
  componentSlug: string  // Target component slug (REQUIRED)
}

helpText is not exposed in the admin UI for this field type, same as componentPicker above — the schema builder's field editor hides the help-text input for both component field types.

Example schema:

{
  "name": "header",
  "label": "Page Header",
  "type": "nestedComponent",
  "required": true,
  "options": {
    "componentSlug": "header"
  }
}

Editor experience:

  • Fixed component
  • Inline field editing
  • No reordering
  • Required validation
  • Compact display

repeater

A repeatable group of sub-fields. Each entry is an object matching the defined sub-field schema.

Use cases:

  • Lists of key/value pairs
  • FAQ items (question + answer)
  • Feature lists
  • Repeated structured rows
  • Timelines or step lists

Options:

{
  helpText?: string        // Helper message
  fields: SchemaField[]    // Sub-field definitions (REQUIRED, at least one)
  minItems?: number        // Minimum number of entries
  maxItems?: number        // Maximum number of entries
}

Disallowed sub-field types:

To prevent deep nesting, a repeater may not contain the following sub-field types:

  • componentPicker
  • nestedComponent
  • repeater (no nested repeaters)

For complex nested structures, use componentPicker instead.

Example schema:

{
  "name": "faqs",
  "label": "FAQs",
  "type": "repeater",
  "required": false,
  "options": {
    "minItems": 1,
    "maxItems": 20,
    "fields": [
      {
        "name": "question",
        "label": "Question",
        "type": "text",
        "required": true
      },
      {
        "name": "answer",
        "label": "Answer",
        "type": "textarea",
        "required": true
      }
    ]
  }
}

Editor experience:

  • Add/remove entries
  • Drag-to-reorder
  • Inline editing of sub-fields
  • Min/max item enforcement

Field Options Summary

Common Options

The schema accepts placeholder/helpText on any field type, and most editor UIs surface both. The exception is componentPicker/nestedComponent: the admin schema builder hides both inputs for these two field types (see the notes in their sections above), even though the underlying schema doesn't forbid setting them:

{
  placeholder?: string  // Hint text before value entered
  helpText?: string     // Explanation shown below field
}

Text Fields

  • text, textarea:
    • minLength?: number - Minimum character count
    • maxLength?: number - Maximum character count
    • pattern?: string - Regex pattern the value must match
    • patternMessage?: string - Custom validation message shown when pattern fails

Numeric Fields

  • number:
    • min?: number - Minimum value
    • max?: number - Maximum value
    • step?: number - Increment step (default: 1)

Media Fields

  • image, media:
    • allowedMimeTypes?: string[] - Allowed file types
    • maxFileSize?: number - Maximum file size in bytes

Reference Fields

  • reference:
    • collectionType?: string - Target collection; required only in the default/collectionItem reference mode, forbidden for user/blogPost, unused for page
    • multiple?: boolean - Allow multiple selections
    • referenceType?: 'page' | 'collectionItem' | 'user' | 'blogPost' - Reference mode (default: collectionItem)
    • pageType?: string - Target page type; only applicable when referenceType is page

Component Fields

  • componentPicker:
    • allowedComponents?: string[] - Allowed component slugs (optional — omit to allow any component)
    • maxItems?: number - Maximum items
    • helpText?: string - Accepted by the schema, but hidden in the admin UI for this field type
  • nestedComponent:
    • componentSlug: string - Target component (required)
    • helpText?: string - Accepted by the schema, but hidden in the admin UI for this field type

Field Validation

Type-Specific Validation

Text fields (text, textarea):

  • Type check: must be string
  • Length validation: enforces minLength and maxLength

Numbers:

  • Type check: must be number
  • Range validation: enforces min and max

Dates:

  • Type check: must be a date string new Date() can parse
  • date fields expect YYYY-MM-DD in the admin UI, but validation just checks the string parses as a valid date
  • datetime fields are stored as ISO 8601 by the admin UI, but validation (isValidDateTime()) accepts any string new Date() can parse — it does not enforce ISO 8601 formatting specifically

Colors:

  • Format validation: hex, rgb, rgba, hsl, hsla
  • Named colors supported — a fixed subset of roughly 55 CSS color names (not the full CSS3 named-color list)

References:

  • Type check: string ID for single, array for multiple
  • Existence check: referenced item must exist

Components:

  • Slug validation: component must exist
  • Nesting limit: maximum 1 level deep
  • Circular reference check: prevents self-references

Required vs Optional Fields

  • required: true - Editor must provide a value before saving
  • required: false - Editor may leave field empty

Validation errors will be shown for:

  • Missing required fields
  • Invalid data types
  • Constraint violations (length, range, etc.)

Default Values

The defaultValue property can be set on any field:

{
  "name": "status",
  "label": "Status",
  "type": "text",
  "required": true,
  "defaultValue": "draft"
}

Default values are used when:

  • Creating new content items
  • Schema has default specified
  • Editors don't explicitly provide a value

Field Naming Conventions

Valid Field Names

The validation regex is ^[a-zA-Z][a-zA-Z0-9_]*$ (packages/shared/src/schemas/index.ts):

  • Must start with a letter — either case is accepted, a-zA-Z. Uppercase-starting names like Title pass validation.
  • Can contain letters, numbers, and underscores: a-zA-Z0-9_
  • Hyphens and spaces are not allowed
  • Can use camelCase: firstName, lastName
  • Can use underscores: first_name (discouraged)

Convention, not a rule: the admin UI's schema builder auto-generates a field's name from its label and always produces a lowercase-starting camelCase name (e.g. label "First Name" → firstName). If you set name directly via the API, a leading-uppercase name is accepted by validation but goes against this convention — prefer lowercase-starting camelCase for consistency with UI-created fields.

Valid Examples

  • title
  • firstName
  • productId
  • meta_data
  • Title (unconventional but technically valid — starts with an uppercase letter)

Invalid Examples

  • first-name (hyphens not allowed)
  • 1st (starts with a number)
  • first name (spaces not allowed)

Choosing Field Types

For Text Content

  • text - Short text (titles, headlines)
  • textarea - Medium text (descriptions, excerpts)
  • wysiwyg - Long formatted text (articles, blog posts)

For Numbers

  • number - Any numeric value with optional constraints
  • boolean - Yes/no, true/false decisions

For Dates/Times

  • date - Date only (YYYY-MM-DD)
  • datetime - Date and time (ISO 8601)

For Media

  • image - Images specifically
  • media - Any file type

For Selection/Linking

  • reference - Link to collection items
  • componentPicker - Multiple components
  • nestedComponent - Single required component

For Complex Data

  • json - Unstructured or complex data
  • color - Color values with picker

Migration Guide

Changing Field Types

When changing a field's type on an in-use schema:

  1. Get breaking change warning - API shows warnings
  2. Acknowledge impact - Understand data loss
  3. Plan migration - Map old values to new format
  4. Update content - Fix invalid content items

Non-breaking Changes

These can be made without affecting content:

  • Changing label
  • Changing placeholder
  • Changing helpText
  • Adding optional field
  • Making required field optional
  • Changing option values (except type restrictions)

Best Practices

  1. Use specific types - Choose the most specific type for your data
  2. Add constraints - Use min/max/length to prevent invalid data
  3. Provide help text - Guide editors with clear explanations
  4. Plan ahead - Think about field constraints before creating
  5. Test schemas - Create test content to validate fields work as expected
  6. Document references - Note which collections are referenced
  7. Use meaningful names - Field names should describe the data
  8. Group related fields - Order fields logically in schema

Copyright © 2026