| title | Field Type Gallery |
|---|---|
| description | Complete reference for every ObjectStack field type with per-type configuration properties |
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'`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 }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 address with built-in format validation.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
— | Maximum character length |
{ name: 'email', label: 'Email', type: 'email', required: true, unique: true }URL with format validation.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
— | Maximum character length |
{ name: 'website', label: 'Website', type: 'url' }Phone number field.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
— | Maximum character length |
format |
string |
— | Phone format pattern |
{ name: 'phone', label: 'Phone', type: 'phone' }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 }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' }Markdown-formatted text with preview support.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
— | Maximum character length |
{ name: 'readme', label: 'README', type: 'markdown' }Rich HTML content.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
— | Maximum character length |
{ name: 'body', label: 'Body', type: 'html' }WYSIWYG rich text editor content.
| Property | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
— | Maximum character length |
{ name: 'content', label: 'Content', type: 'richtext' }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 }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' } }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 value (no time component).
{ name: 'birth_date', label: 'Birth Date', type: 'date' }Date and time value with timezone support.
{ name: 'created_at', label: 'Created At', type: 'datetime', readonly: true }Time-only value (no date component).
{ name: 'start_time', label: 'Start Time', type: 'time' }Standard true/false field.
{ name: 'is_active', label: 'Active', type: 'boolean', defaultValue: true }Toggle switch (functionally identical to boolean, different UI).
{ name: 'notifications_enabled', label: 'Notifications', type: 'toggle' }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' }
]
}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 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' }
]
}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' }
]
}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' }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' })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',
}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 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 file with optional validation.
{ name: 'photo', label: 'Photo', type: 'image' }Generic file attachment.
{ name: 'attachment', label: 'Attachment', type: 'file', multiple: true }Profile image (typically square, small).
{ name: 'avatar', label: 'Avatar', type: 'avatar' }Video file attachment.
{ name: 'demo_video', label: 'Demo Video', type: 'video' }Audio file attachment.
{ name: 'recording', label: 'Recording', type: 'audio' }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' }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' } },
}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}' }These types store structured values as JSON on the parent row — no separate table or foreign key.
Single embedded sub-object with declared sub-fields (similar to a Strapi component / ACF group).
{ name: 'dimensions', label: 'Dimensions', type: 'composite' }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' }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' }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' }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' }Source code with syntax highlighting.
| Property | Type | Default | Description |
|---|---|---|---|
language |
string |
— | Programming language for highlighting |
{ name: 'snippet', label: 'Code Snippet', type: 'code', language: 'typescript' }Raw JSON data.
{ name: 'metadata', label: 'Metadata', type: 'json' }Color picker. Stores the color value as a string. No per-type config properties.
{ name: 'brand_color', label: 'Brand Color', type: 'color' }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' })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 }Digital signature capture.
{ name: 'approval_signature', label: 'Signature', type: 'signature' }QR/barcode value rendered as a scannable code. No per-type config properties.
{ name: 'asset_tag', label: 'Asset Tag', type: 'qrcode' }Progress bar (0–100).
{ name: 'completion', label: 'Completion', type: 'progress' }Tag input with autocomplete.
{ name: 'labels', label: 'Labels', type: 'tags' }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' })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 |
conditionalRequired |
Expression |
— | Deprecated alias of requiredWhen |
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.