Field Types Reference
Complete reference for all built-in field types and their options.
Base options
All field types accept these common options:
type BaseFieldOptions<TValue = unknown> = {
title?: string; // human-readable label shown in the admin UI instead of the field name
description?: string; // helper text rendered below the label in the admin UI
required?: boolean; // default: false
searchable?: boolean; // include in /api/search results (default: false; applies to text, slug, select)
defaultValue?: TValue;
validate?: (value: TValue) => true | string;
admin?: {
components?: FieldSlotComponents;
// Both receive { value, data, user }. Return false to hide / disable.
visible?: (ctx: AdminFieldCtx) => boolean;
readOnly?: (ctx: AdminFieldCtx) => boolean;
};
};
type AdminFieldCtx = {
// `value` is intentionally `unknown` — TS can't narrow it across the
// discriminated `AnyField` union when the admin invokes these callbacks.
// Narrow inside the function body if you need a precise type.
value: unknown;
data: Record<string, unknown>;
user: CmsUser;
};
text and number additionally accept unique?: boolean (default: false) to add a per-entity UNIQUE constraint on the column.
See Fields — Custom field components for the slot reference and usage examples.
text
text(name: string, opts?: BaseFieldOptions<string> & {
unique?: boolean;
rows?: number; // > 1 renders a textarea in the admin
}): TextField
- Storage: Column (
text) - API value:
string - Admin: defaults to a single-line
<input>. Setrowsgreater than 1 to render a multi-line<textarea>with that many visible rows. Storage is unchanged either way.
text("summary", { rows: 4 });
slug
slug(name: string, opts?: BaseFieldOptions<string> & {
unique?: 'entity' | 'global' // default: 'entity'
}): SlugField
- Storage: Column (
text) - API value:
string - Format: Must match
/^[a-z0-9]+(?:-[a-z0-9]+)*$/— lowercase alphanumeric segments separated by hyphens - Special: Supports
$globfilter operator for path matching
Uniqueness scopes
| unique | Mechanism | Scope |
| ---------- | --------- | ----- |
| 'entity' (default) | Column UNIQUE constraint | Unique within this entity's table only |
| 'global' | _slug registry table (PK + FK) | Unique across all entities |
Use 'global' when slugs must be unique site-wide (e.g. for a catch-all route). Use 'entity' (the default) when slugs only need to be unique per entity.
number
number(name: string, opts?: BaseFieldOptions<number> & { unique?: boolean }): NumberField
- Storage: Column (
double precision) - API value:
number
boolean
boolean(name: string, opts?: BaseFieldOptions<boolean>): BooleanField
- Storage: Column (
boolean) - API value:
boolean
date
date(name: string, opts?: BaseFieldOptions<string>): DateField
- Storage: Column (
timestamptz) - API value:
string(ISO 8601)
select
type SelectOption = string | { label: string; value: string };
select<T extends readonly SelectOption[]>(
name: string,
opts: BaseFieldOptions<SelectOptionValue<T[number]>> & { options: T }
): SelectField<T>
- Storage: Column (
text) - API value: One of the specified option values
- Type-safe: Option values are inferred as a literal union type
- Two option forms: Pass plain strings (
"draft") when the label matches the stored value, or{ label, value }objects when you want a human-friendly label that differs from the DB value. The two forms can mix in a single list.
json
json(name: string, opts?: BaseFieldOptions): JsonField
- Storage: JSONB
- API value: Any JSON value
- Limitation: Cannot be used for sorting
richText
richText(name: string, opts?: BaseFieldOptions & {
styles?: RichTextFeature[];
decorators?: RichTextFeature[];
lists?: RichTextFeature[];
annotations?: RichTextAnnotation[];
blocks?: RichTextBlock[];
inlineBlocks?: RichTextBlock[];
}): RichTextField
- Storage: JSONB
- API value: Array of Portable Text blocks
- Admin: WYSIWYG editor with configurable styles, decorators, lists, annotations, blocks, and inline blocks
- Frontend: Render with
@portabletext/reactor any Portable Text library
Sub-types
// Styles, decorators, and lists — pass a plain string to use defaults
type RichTextFeature = string;
type RichTextAnnotation = {
name: string;
fields: AnyField[];
};
type RichTextBlock = {
name: string;
fields: AnyField[];
};
Factory helpers
richText.annotation(name: string, opts: {
fields: AnyField[];
}): RichTextAnnotation
richText.block(name: string, opts: {
fields: AnyField[];
}): RichTextBlock
richText.inlineBlock(name: string, opts: {
fields: AnyField[];
}): RichTextBlock
See the Fields guide for usage examples.
relation
relation(name: string, opts: {
to: EntityDef | string | Array<EntityDef | string>;
multiple?: boolean;
} & BaseFieldOptions): RelationField
- Storage: FK column (single) or junction table (multiple)
- API value: UUID string (single), UUID array (multiple), or resolved object(s) with
?resolve
Single relation
Creates a {name}_id column with a foreign key constraint.
Multiple relation
Creates a {entity}_{name} junction table with sourceId, targetId, and position columns.
Polymorphic relation
When to is an array, the target entity type is stored alongside the ID.
image
image(name: string, opts?: {
multiple?: boolean;
} & BaseFieldOptions): RelationField
- Storage: FK column (single) or junction table (multiple)
- API value: UUID string (single) or UUID array (multiple)
- Target: Built-in
_imageentity - Admin: Upload input with thumbnail preview, browse library, and remove controls
Shorthand for relation(name, { to: "_image", multiple? }). The admin UI renders a typed image input instead of the generic relation picker.
video
video(name: string, opts?: {
multiple?: boolean;
} & BaseFieldOptions): RelationField
- Storage: FK column (single) or junction table (multiple)
- API value: UUID string (single) or UUID array (multiple)
- Target: Built-in
_videoentity - Admin: Upload input with filename preview, browse library, and remove controls
Shorthand for relation(name, { to: "_video", multiple? }).
file
file(name: string, opts?: {
multiple?: boolean;
} & BaseFieldOptions): RelationField
- Storage: FK column (single) or junction table (multiple)
- API value: UUID string (single) or UUID array (multiple)
- Target: Built-in
_fileentity - Admin: Upload input with filename and size preview, browse library, and remove controls
Shorthand for relation(name, { to: "_file", multiple? }).
For fields that accept multiple asset types, use relation() directly with a polymorphic target:
relation("media", { to: ["_image", "_video"] })
object
object(name: string, opts: {
fields: AnyField[];
} & BaseFieldOptions): ObjectField
- Storage: JSONB
- API value: Object matching the field structure
blocks
blocks(name: string, opts: {
of: ObjectField[];
} & BaseFieldOptions): BlocksField
- Storage: JSONB (array)
- API value: Array of objects, each with a
_typediscriminator
[
{ "_type": "hero", "heading": "Welcome" },
{ "_type": "cta", "text": "Sign up", "url": "/signup" }
]
array
array(name: string, opts: {
of: AnyField;
} & BaseFieldOptions): ArrayField
- Storage: JSONB
of: A single field definition — use a primitive field for flat arrays, or an object field for arrays of objects- API value: Array of primitives or array of objects, depending on
of - Admin: Add/remove/reorder items with up/down buttons
Do not use
array()to hold multiple references to another entity — userelation()withmultiple: trueinstead. Wrapping a relation in an array stores IDs as JSONB, losing referential integrity, cross-entity filtering, and resolution. See Handling multiple relations.
Layout directives
Layout directives are admin-only — no storage, no migration impact.
tabs(tabDefs: Array<{ label: string; fields: AnyField[] }>): TabsDirective
collapsible(
label: string,
fields: AnyField[],
opts?: { defaultOpen?: boolean } // default: true
): CollapsibleDirective
row(fields: AnyField[]): RowDirective
Directives can be nested and combined with regular fields in any fields array.
Previous
Client
Next
Config Reference