Skip to content

Latest commit

 

History

History
359 lines (294 loc) · 12 KB

File metadata and controls

359 lines (294 loc) · 12 KB
title Field Metadata
description Configure field types and properties — text, numbers, dates, relationships, files, and more

Field Metadata

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.

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

Type Factory Description
text Field.text() Single-line text
textarea Field.textarea() Multi-line text
email Field.email() Email address with validation
url Field.url() URL with validation
phone Field.phone() Phone number
password Field.password() Masked password input
markdown Field.markdown() Markdown editor
html Field.html() HTML content
richtext Field.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

Type Factory Description
number Field.number() Integer or decimal
currency Field.currency() Monetary value with currency support
percent Field.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',
  },
}),

Date & Time Types

Type Factory Description
date Field.date() Date only
datetime Field.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 }),

Boolean Type

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

Selection Types

Type Factory Description
select Field.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.

Relationship Types

Type Factory Description
lookup Field.lookup() Many-to-one reference
master_detail Field.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) string Target object name
referenceFilters string[] Filter conditions for the lookup
deleteBehavior enum 'set_null', 'cascade', 'restrict'
inlineEdit boolean | 'grid' | 'form' Render child records inline on the parent create/edit form (true = auto-pick, 'grid', or 'form')
inlineColumns array Optional explicit columns for the inline grid
inlineAmountField string Optional numeric child field for the inline running total

File & Media Types

Type Factory Description
image Field.image() Image file upload
file Field.file() Generic file upload
avatar Field.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' }),

Calculation Types

Type Factory Description
formula Field.formula() Calculated from other fields
summary Field.summary() Roll-up from child records
autonumber Field.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.

Specialized Types

Type Factory Description
address Field.address() Structured address (street, city, state, country)
location Field.location() Geographic coordinates (lat/lng)
color Field.color() Color picker (hex/rgb/hsl)
rating Field.rating() Star rating (1-5)
slider Field.slider() Range slider
signature Field.signature() Digital signature capture
qrcode Field.qrcode() QR/barcode generator
code Field.code() Code editor with syntax highlighting
json Field.json() JSON data
vector Field.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' }),

Common Properties

These properties are available on all field types:

Constraints

Property Type Default Description
required boolean false Field must have a value
unique boolean false Values must be unique across records
defaultValue any Default value for new records
maxLength number Maximum character length
minLength number Minimum character length
min number Minimum numeric value
max number Maximum numeric value

Display

Property Type Default Description
label string Human-readable display name
description string Developer documentation
inlineHelpText string Help text shown in UI
hidden boolean false Hide from default views
readonly boolean false Prevent editing — hidden from create/edit forms AND server-enforced: non-system UPDATE writes to the field are silently dropped (insert may still seed it)
sortable boolean true Allow sorting by this field
group string Group name for organizing in forms (e.g. 'billing')

Search & Index

Property Type Default Description
searchable boolean false Include in full-text search
index boolean false Create a database index
externalId boolean false Mark as external system identifier

Security

Property Type Default Description
requiredPermissions string[] Capabilities required to read/edit this field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3)
trackHistory boolean 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.

Conditional Logic

Property Type Description
visibleWhen string | Expression CEL predicate; field is shown only when TRUE
readonlyWhen string | Expression CEL predicate; field is read-only when TRUE
requiredWhen string | Expression CEL predicate; field is required when TRUE
conditionalRequired string | Expression Deprecated alias of requiredWhen
multiple boolean Allow 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.

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

Related