Field Types
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:
| Format | Description | Toolbar Button |
|---|---|---|
| Bold | Bold text | B button |
| Italic | Italic text | I button |
| Underline | Underlined text | U button |
| Strikethrough | Strikethrough text | S button |
| Inline Code | Inline code formatting | </> button |
| Headings | H1 through H6 | Dropdown menu |
| Bullet List | Unordered lists with nesting | Bullet icon |
| Numbered List | Ordered lists with nesting | Number icon |
| Blockquote | Quote blocks | Quote icon |
| Code Block | Multi-line code blocks | Code icon |
| Horizontal Rule | Dividing line | Divider icon |
| Link | Hyperlinks with URL validation | Link icon |
| Image | Images from media library | Image icon |
| Text Align | Left, center, right alignment | Align icons |
| Undo/Redo | History navigation | Arrow 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:
| Tag | Attributes |
|---|---|
a | href, target, rel, title, class |
img | src, alt, title, width, height, class |
p, h1–h6, blockquote | class, style |
span, div, pre, code, li | class |
ul | class, type (disc, circle or square) |
ol | class, start, type |
table | class, style, border, cellpadding, cellspacing, width |
th, td | class, style, colspan, rowspan, width, height, colwidth (th also scope) |
col | class, style, span, width |
colgroup | class, span |
thead, tbody, tfoot, tr, caption | class |
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,formcontrols,svg,math,templateand 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//hostlinks), keeping anyrelvalues already there. - Reports what it removed. A write that removed anything returns a
warningsarray besidedata:{ "data": { "…": "…" }, "warnings": [ { "field": "sections[2].body", "code": "RICH_TEXT_SANITIZED", "message": "Removed disallowed markup from rich text: img[onerror]", "removed": ["img[onerror]"] } ] }fieldis the path to the value inside the content. A?dryRun=truewrite 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 & 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
textfield 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 isnull; 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
choicesare defined, the value must match one of the definedvalueentries.
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
textfield 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
}
helpTextis 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 forcomponentPicker(andnestedComponent) 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)
}
helpTextis not exposed in the admin UI for this field type, same ascomponentPickerabove — 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:
componentPickernestedComponentrepeater(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 countmaxLength?: number- Maximum character countpattern?: string- Regex pattern the value must matchpatternMessage?: string- Custom validation message shown whenpatternfails
Numeric Fields
- number:
min?: number- Minimum valuemax?: number- Maximum valuestep?: number- Increment step (default: 1)
Media Fields
- image, media:
allowedMimeTypes?: string[]- Allowed file typesmaxFileSize?: number- Maximum file size in bytes
Reference Fields
- reference:
collectionType?: string- Target collection; required only in the default/collectionItemreference mode, forbidden foruser/blogPost, unused forpagemultiple?: boolean- Allow multiple selectionsreferenceType?: 'page' | 'collectionItem' | 'user' | 'blogPost'- Reference mode (default:collectionItem)pageType?: string- Target page type; only applicable whenreferenceTypeispage
Component Fields
- componentPicker:
allowedComponents?: string[]- Allowed component slugs (optional — omit to allow any component)maxItems?: number- Maximum itemshelpText?: 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
minLengthandmaxLength
Numbers:
- Type check: must be number
- Range validation: enforces
minandmax
Dates:
- Type check: must be a date string
new Date()can parse datefields expectYYYY-MM-DDin the admin UI, but validation just checks the string parses as a valid datedatetimefields are stored as ISO 8601 by the admin UI, but validation (isValidDateTime()) accepts any stringnew 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 likeTitlepass 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
titlefirstNameproductIdmeta_dataTitle(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:
- Get breaking change warning - API shows warnings
- Acknowledge impact - Understand data loss
- Plan migration - Map old values to new format
- 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
- Use specific types - Choose the most specific type for your data
- Add constraints - Use min/max/length to prevent invalid data
- Provide help text - Guide editors with clear explanations
- Plan ahead - Think about field constraints before creating
- Test schemas - Create test content to validate fields work as expected
- Document references - Note which collections are referenced
- Use meaningful names - Field names should describe the data
- Group related fields - Order fields logically in schema
Related Documentation
- API Reference
- Validation Rules
- Schema Builder Guide
- Media Admin API — full
Mediaentity shape, upload/list/folder endpoints (relevant toimage/mediafields) - Media API (public) — serving URLs and image-transform parameters for turning an
image/mediafield's stored media ID into a usable URL

