Field Metadata Configure field types and properties — text, numbers, dates, relationships, files, and more
A Field defines an individual property within an Object. ObjectStack provides a comprehensive set of field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes.
Fields are defined inside an Object's fields map using Field.* factory methods:
import { ObjectSchema, Field } from '@objectstack/spec/data' ;
export const Contact = ObjectSchema. create ({
name: 'contact' ,
label: 'Contact' ,
fields: {
first_name: Field. text ({ label: 'First Name' , required: true }),
last_name: Field. text ({ label: 'Last Name' , required: true }),
email: Field. email ({ label: 'Email' , unique: true }),
phone: Field. phone ({ label: 'Phone' }),
birth_date: Field. date ({ label: 'Date of Birth' }),
is_active: Field. boolean ({ label: 'Active' , defaultValue: true }),
account: Field. lookup ( 'account' , { label: 'Account' , required: true }),
},
});
Type Factory Description textField.text()Single-line text textareaField.textarea()Multi-line text emailField.email()Email address with validation urlField.url()URL with validation phoneField.phone()Phone number passwordField.password()Masked password input markdownField.markdown()Markdown editor htmlField.html()HTML content richtextField.richtext()Rich text (WYSIWYG) editor
name : Field. text ({ label: 'Name' , required: true , maxLength: 255 }),
bio : Field. textarea ({ label: 'Bio' , maxLength: 5000 }),
website : Field. url ({ label: 'Website' }),
notes : Field. markdown ({ label: 'Notes' }),
Type Factory Description numberField.number()Integer or decimal currencyField.currency()Monetary value with currency support percentField.percent()Percentage value
quantity : Field. number ({ label: 'Quantity' , min: 0 , max: 10000 , step: 1 }),
price : Field. currency ({ label: 'Price' , scale: 2 , min: 0 }),
// percent stores a fraction: 0.15 = 15% (the UI renders it as a percentage)
discount : Field. percent ({ label: 'Discount' , scale: 2 , min: 0 , max: 1 }),
Currency Configuration:
price : Field. currency ({
label: 'Price' ,
currencyConfig: {
precision: 2 ,
currencyMode: 'dynamic' , // 'dynamic' | 'fixed'
defaultCurrency: 'USD' ,
},
}),
Type Factory Description dateField.date()Date only datetimeField.datetime()Date and time time— Time only
start_date : Field. date ({ label: 'Start Date' , required: true }),
created_at : Field. datetime ({ label: 'Created At' , readonly: true }),
is_active : Field. boolean ({ label: 'Active' , defaultValue: true }),
Type Factory Description selectField.select()Single or multi-select dropdown radio— Radio button group checkboxes— Checkbox group
// Single select
status : Field. select ({
label: 'Status' ,
options: [
{ label: 'New' , value: 'new' , color: '#999' , default: true },
{ label: 'Active' , value: 'active' , color: '#0A0' },
{ label: 'Closed' , value: 'closed' , color: '#A00' },
],
}),
// Multi-select
skills : Field. select ({
label: 'Skills' ,
multiple: true ,
options: [
{ label: 'JavaScript' , value: 'js' },
{ label: 'Python' , value: 'python' },
{ label: 'Go' , value: 'go' },
],
}),
Options must use { label, value } objects. Values are stored in the database and must be lowercase.
Type Factory Description lookupField.lookup()Many-to-one reference master_detailField.masterDetail()Parent-child with cascade delete
// Simple lookup
account : Field. lookup ( 'account' , {
label: 'Account' ,
required: true ,
}),
// Filtered lookup — structured filters, cascading from another field
primary_contact : Field. lookup ( 'contact' , {
label: 'Primary Contact' ,
dependsOn: [ 'account' ],
lookupFilters: [
{ field: 'is_active' , operator: 'eq' , value: true },
],
}),
// Master-detail (cascade delete)
project : Field. masterDetail ( 'project' , {
label: 'Project' ,
required: true ,
}),
// Inline line items on the parent form
order : Field. masterDetail ( 'order' , {
label: 'Order' ,
required: true ,
inlineEdit: true ,
inlineTitle: 'Lines' ,
inlineAmountField: 'line_total' ,
}),
Property Type Description reference (1st arg)stringTarget object name referenceFiltersstring[]Filter conditions for the lookup deleteBehaviorenum'set_null', 'cascade', 'restrict'inlineEditboolean | 'grid' | 'form'Render child records inline on the parent create/edit form (true = auto-pick, 'grid', or 'form') inlineColumnsarrayOptional explicit columns for the inline grid inlineAmountFieldstringOptional numeric child field for the inline running total
Type Factory Description imageField.image()Image file upload fileField.file()Generic file upload avatarField.avatar()User avatar/profile image video— Video file audio— Audio file
photo : Field. image ({ label: 'Photo' }),
resume : Field. file ({ label: 'Resume' }),
profile_image : Field. avatar ({ label: 'Profile Image' }),
Type Factory Description formulaField.formula()Calculated from other fields summaryField.summary()Roll-up from child records autonumberField.autonumber()Sequential auto-generated number
// Formula
total : Field. formula ({
label: 'Total' ,
expression: 'quantity * price * (1 - discount / 100)' ,
}),
// Summary (roll-up)
total_amount : Field. summary ({
label: 'Total Amount' ,
summaryOperations: {
object: 'order_item' ,
field: 'amount' ,
function: 'sum' ,
relationshipField: 'order' ,
},
}),
// AutoNumber
case_number : Field. autonumber ({
label: 'Case Number' ,
autonumberFormat: 'CASE-{0000}' ,
}),
Summary fields are server-owned. The engine recomputes them when child records
are inserted, updated, or deleted. relationshipField is optional when the child
has only one lookup/master_detail field pointing back to the parent; set it
when multiple relationships target the same parent object.
Type Factory Description addressField.address()Structured address (street, city, state, country) locationField.location()Geographic coordinates (lat/lng) colorField.color()Color picker (hex/rgb/hsl) ratingField.rating()Star rating (1-5) sliderField.slider()Range slider signatureField.signature()Digital signature capture qrcodeField.qrcode()QR/barcode generator codeField.code()Code editor with syntax highlighting jsonField.json()JSON data vectorField.vector()AI embedding vectors tags— Tag list progress— Progress bar
office : Field. address ({ label: 'Office Address' }),
coordinates : Field. location ({ label: 'Location' }),
brand_color : Field. color ({ label: 'Color' }),
satisfaction : Field. rating ( 5 , { label: 'Rating' }),
approval_signature : Field. signature ({ label: 'Signature' }),
embedding : Field. vector ( 1536 , { label: 'Embedding' }),
These properties are available on all field types:
Property Type Default Description requiredbooleanfalseField must have a value uniquebooleanfalseValues must be unique across records defaultValueany— Default value for new records maxLengthnumber— Maximum character length minLengthnumber— Minimum character length minnumber— Minimum numeric value maxnumber— Maximum numeric value
Property Type Default Description labelstring— Human-readable display name descriptionstring— Developer documentation inlineHelpTextstring— Help text shown in UI hiddenbooleanfalseHide from default views readonlybooleanfalsePrevent editing — hidden from create/edit forms AND server-enforced: non-system UPDATE writes to the field are silently dropped (insert may still seed it) sortablebooleantrueAllow sorting by this field groupstring— Group name for organizing in forms (e.g. 'billing')
Property Type Default Description searchablebooleanfalseInclude in full-text search indexbooleanfalseCreate a database index externalIdbooleanfalseMark as external system identifier
Property Type Default Description requiredPermissionsstring[]— Capabilities required to read/edit this field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3) trackHistoryboolean— Render this field's value changes as entries on the record activity timeline
For values that must be encrypted at rest (API keys, tokens, DB passwords), use the
secret field type — see the Field Type Gallery .
Property Type Description visibleWhenstring | ExpressionCEL predicate; field is shown only when TRUE readonlyWhenstring | ExpressionCEL predicate; field is read-only when TRUE requiredWhenstring | ExpressionCEL predicate; field is required when TRUE conditionalRequiredstring | ExpressionDeprecated alias of requiredWhen multiplebooleanAllow multiple values (for select, lookup, file)
import { P } from '@objectstack/spec' ;
import { Field } from '@objectstack/spec/data' ;
close_reason : Field. select ({
label: 'Close Reason' ,
visibleWhen: P `record.status == 'closed'` ,
requiredWhen: P `record.status == 'closed'` ,
options: [
{ label: 'Won' , value: 'won' },
{ label: 'Lost' , value: 'lost' },
{ label: 'Cancelled' , value: 'cancelled' },
],
}),
invoice_total : Field. currency ({
label: 'Invoice Total' ,
readonlyWhen: P `record.status == 'paid'` ,
}),
Conditional field rules are evaluated in both tiers. ObjectUI forms update
visibility, read-only state, and required markers live as the draft record
changes, including row-scoped rules inside inline master-detail grids. The
server enforces requiredWhen on submit and ignores writes to fields whose
readonlyWhen predicate is TRUE, so client-side affordances are not the
only guard. Use requiredWhen for new metadata; keep conditionalRequired
only for older packages that already emitted it.
Field names use snake_case: first_name, annual_revenue, is_active
Boolean fields use is_ or has_ prefix: is_active, has_children
Lookup fields use the object name directly: account (not account_id)
Configuration keys use camelCase: maxLength, defaultValue