Field Type Gallery
Complete reference for every ObjectStack field type with per-type configuration properties
Field Type Gallery
ObjectStack provides a comprehensive set of field types covering every data modeling need — from basic text and numbers to AI vectors and rich media. This guide organizes them by category with per-type configuration details.
Source: packages/spec/src/data/field.zod.ts
Import: import { FieldType, FieldSchema } from '@objectstack/spec/data'
Text Types
text
Single-line plain text input.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
minLength | number | — | Minimum character length |
format | string | — | Validation format pattern |
{ name: 'first_name', label: 'First Name', type: 'text', maxLength: 100 }textarea
Multi-line text input for longer content.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
minLength | number | — | Minimum character length |
{ name: 'description', label: 'Description', type: 'textarea', maxLength: 5000 }email
Email address with built-in format validation.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
{ name: 'email', label: 'Email', type: 'email', required: true, unique: 'organization' }url
URL with format validation.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
{ name: 'website', label: 'Website', type: 'url' }phone
Phone number field.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
format | string | — | Phone format pattern |
{ name: 'phone', label: 'Phone', type: 'phone' }password
Masked password input. On a generic object the value is plaintext at rest but masked to •••••••• on read (ADR-0100) — it is not one-way hashed. One-way hashing is owned by the auth subsystem and applies only to its identity tables, never to an authored password field. For reversible encrypted-at-rest secrets (API keys, tokens, DB passwords), use the secret type instead; for login credentials, model them on the auth user object.
{ name: 'password', label: 'Password', type: 'password', required: true }secret
Reversible, encrypted-at-rest value (DB password, API key, token). Unlike password (masked on read but plaintext at rest, or one-way hashed inside the auth subsystem), a secret is encrypted on write via the registered crypto provider, stored as an opaque ref on the row, and masked on read. Fail-closed: with no provider configured, writes throw rather than persist cleartext.
{ name: 'api_key', label: 'API Key', type: 'secret' }Rich Content Types
markdown
Markdown-formatted text with preview support.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
{ name: 'readme', label: 'README', type: 'markdown' }html
Rich HTML content.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
{ name: 'body', label: 'Body', type: 'html' }richtext
WYSIWYG rich text editor content.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength | number | — | Maximum character length |
{ name: 'content', label: 'Content', type: 'richtext' }Number Types
number
Numeric value with optional precision.
| Property | Type | Default | Description |
|---|---|---|---|
precision | number | — | Total digits |
scale | number | — | Decimal places |
min | number | — | Minimum value |
max | number | — | Maximum value |
{ name: 'quantity', label: 'Quantity', type: 'number', min: 0, max: 99999 }currency
Monetary value with currency configuration.
| Property | Type | Default | Description |
|---|---|---|---|
precision | number | — | Total digits |
scale | number | — | Decimal places |
min | number | — | Minimum value |
max | number | — | Maximum value |
currencyConfig.precision | number | 2 | Decimal precision for currency |
currencyConfig.currencyMode | 'dynamic' | 'fixed' | 'dynamic' | Whether currency is per-record or fixed |
currencyConfig.defaultCurrency | string | 'CNY' | Default currency code |
{ name: 'price', label: 'Price', type: 'currency', currencyConfig: { precision: 2, currencyMode: 'fixed', defaultCurrency: 'USD' } }A declared precision must agree with the currency's ISO 4217 fraction digits
when the currency is statically known — that is, when currencyConfig is in
fixed mode. Publish-time validation rejects the contradiction, naming both
numbers ("currency JPY has 0 fraction digits; precision: 2 contradicts it"):
the yen has no minor unit to show two digits of, and precision: 2 on a KWD
field would silently drop the third fils digit that exists. Omit precision
to let renderers derive the width from the currency itself. The rule is
deliberately partial: a field in dynamic currencyMode has no single currency
to check against, and currency codes outside CLDR currencyData
(cryptocurrency or custom business codes) are not checked. The rule judges
only authored precision — an untouched currencyConfig (whose precision
defaults to 2) is never rejected, whatever its currency.
percent
Percentage value (0–100 or decimal).
| Property | Type | Default | Description |
|---|---|---|---|
precision | number | — | Total digits |
scale | number | — | Decimal places |
min | number | — | Minimum value |
max | number | — | Maximum value |
{ name: 'discount', label: 'Discount', type: 'percent', min: 0, max: 100 }Date & Time Types
date
Date value (no time component).
{ name: 'birth_date', label: 'Birth Date', type: 'date' }datetime
Date and time value with timezone support.
{ name: 'created_at', label: 'Created At', type: 'datetime', readonly: true }time
Time-only value (no date component).
{ name: 'start_time', label: 'Start Time', type: 'time' }Boolean Types
boolean
Standard true/false field.
{ name: 'is_active', label: 'Active', type: 'boolean', defaultValue: true }toggle
Toggle switch (functionally identical to boolean, different UI).
{ name: 'notifications_enabled', label: 'Notifications', type: 'toggle' }Selection Types
select
Single-choice dropdown.
| Property | Type | Default | Description |
|---|---|---|---|
options | SelectOption[] | required | List of available options |
SelectOption properties:
| Property | Type | Required | Description |
|---|---|---|---|
label | string | ✅ | Display label |
value | string | ✅ | Machine value (snake_case) |
color | string | — | Badge color |
default | boolean | — | Pre-selected option |
{
name: 'status', label: 'Status', type: 'select',
options: [
{ label: 'Open', value: 'open', color: 'blue', default: true },
{ label: 'In Progress', value: 'in_progress', color: 'yellow' },
{ label: 'Done', value: 'done', color: 'green' }
]
}multiselect
Multi-choice dropdown. Same options property as select.
{
name: 'tags', label: 'Tags', type: 'multiselect',
options: [
{ label: 'Bug', value: 'bug' },
{ label: 'Feature', value: 'feature' },
{ label: 'Enhancement', value: 'enhancement' }
]
}radio
Radio button group (single choice). Same options property as select.
{
name: 'priority', label: 'Priority', type: 'radio',
options: [
{ label: 'Low', value: 'low' },
{ label: 'Medium', value: 'medium', default: true },
{ label: 'High', value: 'high' }
]
}checkboxes
Checkbox group (multi-choice). Same options property as select.
{
name: 'features', label: 'Features', type: 'checkboxes',
options: [
{ label: 'Email', value: 'email' },
{ label: 'SMS', value: 'sms' },
{ label: 'Push', value: 'push' }
]
}Relational Types
lookup
Reference to a record in another object (foreign key).
| Property | Type | Default | Description |
|---|---|---|---|
reference | string | required | Target object name (snake_case) |
referenceFilters | string[] | — | Removed (#2377, ADR-0049) — no longer a recognized field property (unknown keys are stripped by the schema). Use structured lookupFilters + dependsOn instead; see Relationships |
deleteBehavior | 'restrict' | 'cascade' | 'set_null' | 'set_null' | Behavior when referenced record is deleted (a required lookup left at the default set_null is escalated to restrict, since a NOT NULL foreign key cannot be cleared) |
{ name: 'company', label: 'Company', type: 'lookup', reference: 'account' }user
Person picker — a lookup specialized to the built-in sys_user object. Stored identically to lookup (foreign key to sys_user.id; multiple: true stores an array) and resolved through the same $expand machinery. Supports defaultValue: 'current_user' to stamp the acting user's id on insert.
Field.user({ label: 'Assignee' })
Field.user({ label: 'Watchers', multiple: true })
Field.user({ label: 'Owner', defaultValue: 'current_user' })master_detail
Parent-child relationship (cascading delete by default).
| Property | Type | Default | Description |
|---|---|---|---|
reference | string | required | Target (master) object name |
referenceFilters | string[] | — | Removed (#2377, ADR-0049) — no longer a recognized field property (unknown keys are stripped by the schema). Use structured lookupFilters + dependsOn instead; see Relationships |
deleteBehavior | 'restrict' | 'cascade' | 'set_null' | 'cascade' | Behavior when parent is deleted (master-detail cascades unless set to restrict) |
inlineEdit | boolean | 'grid' | 'form' | — | Edit child records inline on the parent create/edit form (true = auto-pick, 'grid', or 'form') |
inlineColumns | array | — | Optional explicit inline grid columns |
inlineAmountField | string | — | Numeric child field used for the inline running total |
{
name: 'order',
label: 'Order',
type: 'master_detail',
reference: 'order',
inlineEdit: true,
inlineAmountField: 'line_total',
}tree
Self-referential hierarchy (e.g., categories, org chart).
| Property | Type | Default | Description |
|---|---|---|---|
reference | string | required | Same object (self-reference) |
{ name: 'parent_category', label: 'Parent Category', type: 'tree', reference: 'category' }Media Types
Media fields store a reference to an uploaded file. They take the universal field properties (multiple is honored for file and image); there is no per-field file-attachment config object in the field schema — file constraints and storage are configured at the storage-provider layer, not on the field.
image
Image file with optional validation.
{ name: 'photo', label: 'Photo', type: 'image' }file
Generic file attachment.
{ name: 'attachment', label: 'Attachment', type: 'file', multiple: true }avatar
Profile image (typically square, small).
{ name: 'avatar', label: 'Avatar', type: 'avatar' }video
Video file attachment.
{ name: 'demo_video', label: 'Demo Video', type: 'video' }audio
Audio file attachment.
{ name: 'recording', label: 'Recording', type: 'audio' }Computed Types
formula
Calculated field using a CEL expression (see Expressions).
| Property | Type | Default | Description |
|---|---|---|---|
expression | string | Expression | required | CEL calculation expression |
returnType | 'number' | 'text' | 'boolean' | 'date' | — | Declared value type of the computed result |
{ name: 'total', label: 'Total', type: 'formula', expression: 'record.quantity * record.unit_price', returnType: 'number' }summary
Roll-up summary from child records.
| Property | Type | Default | Description |
|---|---|---|---|
summaryOperations.object | string | required | Child object to aggregate |
summaryOperations.field | string | required | Child field to aggregate; ignored for count |
summaryOperations.function | enum | required | count, sum, avg, min, or max |
summaryOperations.relationshipField | string | auto | Child FK back to the parent when it cannot be inferred |
summaryOperations.filter | FilterCondition | — | Only child rows matching this where predicate are aggregated |
{
name: 'task_count',
label: 'Task Count',
type: 'summary',
summaryOperations: {
object: 'task',
field: 'id',
function: 'count',
relationshipField: 'project',
},
}Add a filter to aggregate only a subset of children — this is how one child
object feeds several different totals. The engine ANDs the predicate with the
parent-FK match and recomputes on the child's next write, so a row moving in or
out of the filter (a status change) keeps the parent current:
// Sum only received receipt lines (3-way match), not every line.
{
name: 'received_amount',
type: 'summary',
summaryOperations: {
object: 'procurement_receipt',
field: 'amount',
function: 'sum',
filter: { status: 'received' },
},
}
// One `engagement` child → distinct totals, differentiated only by the filter.
{
name: 'total_signups',
type: 'summary',
summaryOperations: { object: 'engagement', field: 'id', function: 'count', filter: { type: 'signup' } },
}autonumber
Auto-incrementing number with format template.
| Property | Type | Default | Description |
|---|---|---|---|
autonumberFormat | string | — | Format template (e.g., 'INV-{0000}') |
{ name: 'ticket_number', label: 'Ticket #', type: 'autonumber', autonumberFormat: 'TKT-{0000}' }Embedded Structured Types
These types store structured values as JSON on the parent row — no separate table or foreign key.
composite
Single embedded sub-object with declared sub-fields (similar to a Strapi component / ACF group).
{ name: 'dimensions', label: 'Dimensions', type: 'composite' }repeater
Repeating embedded sub-object array with declared sub-fields (similar to a Strapi repeatable component / ACF repeater).
{ name: 'line_items', label: 'Line Items', type: 'repeater' }record
Name-keyed map of embedded sub-objects (Record<string, SubObject>). Insertion order is display order. Used for collections where each item has a stable machine name. See ADR-0007.
{ name: 'settings', label: 'Settings', type: 'record' }Enhanced Types
location
Geographic coordinates. Stored as { latitude, longitude, altitude?, accuracy? } (latitude −90..90, longitude −180..180). No per-type config properties.
{ name: 'headquarters', label: 'Location', type: 'location' }address
Structured postal address. Stored as { street, city, state, postalCode, country, countryCode, formatted } (all parts optional). No per-type config properties.
{ name: 'billing_address', label: 'Billing Address', type: 'address' }code
Source code with syntax highlighting.
| Property | Type | Default | Description |
|---|---|---|---|
language | string | — | Programming language for highlighting |
{ name: 'snippet', label: 'Code Snippet', type: 'code', language: 'typescript' }json
Raw JSON data.
{ name: 'metadata', label: 'Metadata', type: 'json' }color
Color picker. Stores the color value as a string. No per-type config properties.
{ name: 'brand_color', label: 'Brand Color', type: 'color' }rating
Star rating input.
| Property | Type | Default | Description |
|---|---|---|---|
max | number | 5 | Maximum rating value (set by the Field.rating(max) factory) |
{ name: 'satisfaction', label: 'Rating', type: 'rating', max: 5 }
// or with the factory:
// satisfaction: Field.rating(5, { label: 'Rating' })slider
Range slider input.
| Property | Type | Default | Description |
|---|---|---|---|
min | number | — | Minimum value |
max | number | — | Maximum value |
step | number | 1 | Step increment |
{ name: 'confidence', label: 'Confidence', type: 'slider', min: 0, max: 100, step: 5 }signature
Digital signature capture.
{ name: 'approval_signature', label: 'Signature', type: 'signature' }qrcode
QR/barcode value rendered as a scannable code. No per-type config properties.
{ name: 'asset_tag', label: 'Asset Tag', type: 'qrcode' }progress
Progress bar (0–100).
{ name: 'completion', label: 'Completion', type: 'progress' }tags
Tag input with autocomplete.
{ name: 'labels', label: 'Labels', type: 'tags' }AI / ML Types
vector
Vector embeddings for semantic search and similarity.
| Property | Type | Default | Description |
|---|---|---|---|
dimensions | number | required | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
An earlier nested vectorConfig object was removed — the field builder no longer emits it and it is ignored. Set the flat dimensions property instead.
{ name: 'embedding', label: 'Embedding', type: 'vector', dimensions: 1536 }
// or with the factory:
// embedding: Field.vector(1536, { label: 'Embedding' })Universal Field Properties
These properties are available on all field types:
| Property | Type | Default | Description |
|---|---|---|---|
name | string | required | Machine name (snake_case) |
label | string | — | Human-readable display label |
description | string | — | Field description / help text |
type | FieldType | required | One of the supported field types |
required | boolean | false | Whether the field is required |
unique | boolean | false | Enforce uniqueness |
multiple | boolean | false | Allow array of values |
searchable | boolean | false | Include in search index |
sortable | boolean | true | Allow sorting by this field |
hidden | boolean | false | Hide from default views |
readonly | boolean | false | Server contract — non-system writes to this field are stripped on both INSERT and UPDATE (the value falls back to defaultValue), not just hidden in the UI (#2948 / #3043) |
externalId | boolean | false | External system identifier |
defaultValue | any | — | Default value for new records |
group | string | — | Field grouping / section |
inlineHelpText | string | — | Inline help tooltip |
trackHistory | boolean | — | Render this field's value changes as entries on the record activity timeline |
requiredPermissions | string[] | — | Capabilities required to read/edit this field (masked on read, denied on write) |
dependsOn | string[] | — | Fields whose values scope this field's options (lookup candidate query; select/radio option gating) |
visibleWhen | Expression | — | Show field only when predicate is true |
readonlyWhen | Expression | — | Make field read-only when predicate is true |
requiredWhen | Expression | — | Require field when predicate is true |
Choosing a Field Type
Not sure which type fits your data? Follow the Field Type Decision Tree — a flowchart, quick-reference tables, and a use-case mapping for every type in this gallery.