Reference

Field Types

What a field type decides in ObjectOS — the column, the API, the form widget and what the AI Builder asks for — with a few examples and the full catalogue on objectstack.ai.

Full specification: Field types on objectstack.ai. This page is the summary: where ObjectOS uses it, and a few examples.

A field's type decides what it stores and how every surface treats it. There are 48 built-in types — text, numbers, dates, choices, relations, computed values, media, structured data and richer inputs such as addresses, ratings and signatures — and every object also carries system fields (id, created_at, updated_at, created_by, updated_by) you do not declare.

Where ObjectOS uses it

One field definition drives all of these:

*.object.ts (field spec)
   │
   ├─► Postgres / MySQL / SQLite column + index + constraint
   ├─► REST: validated on POST/PATCH, exposed on GET
   ├─► UI: form widget + list column
   ├─► AI Builder: tool argument schema (so the AI knows what to ask)
   └─► Audit: change-tracked if `trackHistory: true`
  • The database gets a column, its index and its constraints.
  • The REST API validates the field on every write and returns it on read (REST API).
  • The UI picks the form widget and the list column from the type.
  • The AI Builder knows what to ask for, because the type is the argument schema of its tools.
  • Audit records the field's changes when trackHistory is on.

Examples

A choice — a select field's options:

// Keys of a `select` field; its other keys are omitted.
options: [
  { value: 'low',    label: 'Low' },
  { value: 'high',   label: 'High',   color: '#e02' },
  { value: 'urgent', label: 'Urgent', color: '#c00' }
]

A relation — a lookup to an account, with the picker narrowed to active ones:

// One field.
{
  type: 'lookup',
  reference: 'account',                       // target object name
  lookupFilters: [                            // narrow the lookup picker
    { field: 'status', operator: 'eq', value: 'active' }
  ],
  deleteBehavior: 'set_null'                  // 'set_null' | 'cascade' | 'restrict'
}

A computed value — a formula field, written in CEL:

// One field.
{
  name: 'profit_margin',
  type: 'formula',
  expression: F`(record.revenue - record.cost) / record.revenue * 100`
}

An attachment — files, checked on the server:

// One field.
{
  type: 'file',
  multiple: true,                             // store an array of attachments
  accept: ['image/png', 'image/jpeg'],        // MIME types / extensions
  maxSize: 10_000_000                         // bytes
}

Media

A media field stores an opaque sys_file id — not a URL, and not an inline metadata blob. The { url, name, size, mimeType, … } object you see when you read a record is the expanded form, derived from the owning sys_file record; url comes from the /files/:fileId resolver.

Because the platform owns the bytes, accept and maxSize are not just hints for the browser's file picker: the server re-checks every record write against them, so a caller that talks to the API directly cannot bypass them. A download carries the real filename and content type, and read access is delegated to the owning object. Existing deployments convert with os migrate files-to-references; see Storage for where the bytes live and the 17.0 release notes for the migration.

See also