ObjectStackObjectStack

Field Metadata

Configure field types and properties — 40+ field types for text, numbers, dates, relationships, and more

Field Metadata

A Field defines an individual property within an Object. ObjectStack provides 40+ field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes.

Basic Usage

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 }),
  },
});

Field Types

Text Types

TypeFactoryDescription
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' }),

Number Types

TypeFactoryDescription
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 }),
discount: Field.percent({ label: 'Discount', scale: 2, min: 0, max: 100 }),

Currency Configuration:

price: Field.currency({
  label: 'Price',
  currencyConfig: {
    precision: 2,
    currencyMode: 'dynamic',    // 'dynamic' | 'fixed'
    defaultCurrency: 'USD',
  },
}),

Date & Time Types

TypeFactoryDescription
dateField.date()Date only
datetimeField.datetime()Date and time
timeTime only
start_date: Field.date({ label: 'Start Date', required: true }),
created_at: Field.datetime({ label: 'Created At', readonly: true }),

Boolean Type

is_active: Field.boolean({ label: 'Active', defaultValue: true }),

Selection Types

TypeFactoryDescription
selectField.select()Single or multi-select dropdown
radioRadio button group
checkboxesCheckbox 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.

Relationship Types

TypeFactoryDescription
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
primary_contact: Field.lookup('contact', {
  label: 'Primary Contact',
  referenceFilters: ['account = {account}', 'is_active = true'],
}),

// Master-detail (cascade delete)
project: Field.masterDetail('project', {
  label: 'Project',
  required: true,
}),
PropertyTypeDescription
reference (1st arg)stringTarget object name
referenceFiltersstring[]Filter conditions for the lookup
deleteBehaviorenum'set_null', 'cascade', 'restrict'

File & Media Types

TypeFactoryDescription
imageField.image()Image file upload
fileField.file()Generic file upload
avatarField.avatar()User avatar/profile image
videoVideo file
audioAudio file
photo: Field.image({ label: 'Photo' }),
resume: Field.file({ label: 'Resume' }),
profile_image: Field.avatar({ label: 'Profile Image' }),

Calculation Types

TypeFactoryDescription
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',
  },
}),

// AutoNumber
case_number: Field.autonumber({
  label: 'Case Number',
  autonumberFormat: 'CASE-{0000}',
}),

Specialized Types

TypeFactoryDescription
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
jsonJSON data
vectorField.vector()AI embedding vectors
tagsTag list
progressProgress bar
office: Field.address({ label: 'Office Address', addressFormat: 'international' }),
coordinates: Field.location({ label: 'Location', displayMap: true }),
brand_color: Field.color({ label: 'Color', colorFormat: 'hex' }),
satisfaction: Field.rating({ label: 'Rating', maxRating: 5 }),
approval_signature: Field.signature({ label: 'Signature' }),
embedding: Field.vector({ label: 'Embedding', vectorConfig: { dimensions: 1536 } }),

Common Properties

These properties are available on all field types:

Constraints

PropertyTypeDefaultDescription
requiredbooleanfalseField must have a value
uniquebooleanfalseValues must be unique across records
defaultValueanyDefault value for new records
maxLengthnumberMaximum character length
minLengthnumberMinimum character length
minnumberMinimum numeric value
maxnumberMaximum numeric value

Display

PropertyTypeDefaultDescription
labelstringHuman-readable display name
descriptionstringDeveloper documentation
inlineHelpTextstringHelp text shown in UI
hiddenbooleanfalseHide from default views
readonlybooleanfalsePrevent editing
sortablebooleantrueAllow sorting by this field
groupstringGroup name for organizing in forms (e.g. 'billing')

Search & Index

PropertyTypeDefaultDescription
searchablebooleanfalseInclude in full-text search
indexbooleanfalseCreate a database index
externalIdbooleanfalseMark as external system identifier

Security

PropertyTypeDefaultDescription
encryptionConfigobjectField-level encryption settings
maskingRuleobjectData masking rules for display
auditTrailbooleanfalseTrack all changes to this field

Conditional Logic

PropertyTypeDescription
conditionalRequiredstringFormula expression that makes field required when TRUE
multiplebooleanAllow multiple values (for select, lookup, file)
// Conditional required example
close_reason: Field.select({
  label: 'Close Reason',
  conditionalRequired: "status = 'closed'",
  options: [
    { label: 'Won', value: 'won' },
    { label: 'Lost', value: 'lost' },
    { label: 'Cancelled', value: 'cancelled' },
  ],
}),

Naming Conventions

  • 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

On this page