ObjectOS
Build

Data Model

Objects, fields, relationships, validation, indexes — described to AI or written in TypeScript.

Data Model

The data model is the single source of truth for your app. Once an object exists, ObjectOS gives you REST APIs, a Console view, RBAC checkpoints, audit log entries, and AI tool exposure — for free.

Most customers never write the schema by hand. They describe what they need in the AI Builder and the platform creates the objects, fields, indexes, and translations. This page describes the underlying shape — so you understand what the AI is generating and can edit it directly when you want to.

Authoring paths

PathLooks like
AI Builder (primary)"Create a support_ticket object with subject, description, priority, status, assignee."
Console click-buildConsole → Objects → New Object → forms
TypeScript (*.object.ts)The TS shown below — typically inside a forked template

All three produce the same schema. The schema is canonical; everything else is derived.

Anatomy of an object

// src/objects/task.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Task = ObjectSchema.create({
  name: 'todo_task',
  label: 'Task',
  pluralLabel: 'Tasks',
  icon: 'check-square',
  description: 'A single unit of work.',

  fields: {
    subject:     Field.text({ label: 'Subject', required: true, maxLength: 200 }),
    description: Field.markdown({ label: 'Description' }),
    status:      Field.select({
      label: 'Status',
      options: [
        { label: 'To Do',       value: 'todo', default: true },
        { label: 'In Progress', value: 'in_progress' },
        { label: 'Done',        value: 'done' },
      ],
    }),
    due:         Field.date({ label: 'Due' }),
    assignee:    Field.lookup({ label: 'Assignee', reference: 'sys_user' }),
  },

  enable: {
    trackHistory: true,    // record field changes in audit log
    apiEnabled: true,      // expose REST endpoints (default true)
    feeds: true,           // chatter / comments / @mentions
  },
});

Register it in your stack:

// objectstack.config.ts
import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects';

export default defineStack({
  manifest: { id: 'my.app', namespace: 'myapp', version: '0.1.0', type: 'app', name: 'My App' },
  objects: Object.values(objects),
});

That's all you need. os dev recompiles, and /api/v1/data/todo_task, the Console Task view, and the Console permission row all appear.

Field types

ObjectStack ships ~25 field types. The most-used ones:

Scalars

TypeWhat it storesHelper
textShort stringField.text({ maxLength, required })
textareaLong stringField.textarea(...)
markdownRich text with markdownField.markdown(...)
numberIntegerField.number({ min, max })
decimalExact decimal (money, etc.)Field.decimal({ precision, scale })
booleanTrue/falseField.boolean({ defaultValue })
dateCalendar dateField.date(...)
datetimeTimestampField.datetime(...)
emailValidated emailField.email(...)
urlValidated URLField.url(...)
phoneValidated phoneField.phone(...)
jsonArbitrary JSONField.json(...)

Choices

TypeUse for
selectSingle choice (enum)
multiselectMultiple choices

Relationships

TypeCardinalityHelper
lookupOne-to-many (FK)Field.lookup({ reference: 'sys_user' })
masterDetailOne-to-many with cascade deleteField.masterDetail({ reference: 'order' })

Files & media

TypeWhat it stores
attachmentOne file via the storage service
imageImage attachment with preview

Computed / derived

TypeBehavior
formulaComputed at read time from a CEL expression
rollupAggregate of related records (sum/count/avg)
autoNumberSequence (INV-{000001})
created, lastModifiedSystem-maintained timestamps
createdBy, lastModifiedBySystem-maintained user refs

Required / unique / default

Common modifiers on every scalar field:

Field.text({
  label: 'Code',
  required: true,
  unique: true,          // unique constraint enforced at DB level
  defaultValue: '',
  helpText: 'Internal short code',
})

Validation

Inline:

Field.number({ label: 'Quantity', min: 1, max: 9999 })
Field.text({ label: 'SKU', pattern: '^[A-Z]{3}-[0-9]{4}$' })

Object-level rules (cross-field):

ObjectSchema.create({
  name: 'order',
  fields: { /* ... */ },
  validations: [
    {
      name: 'discount_lt_total',
      message: 'Discount cannot exceed total',
      condition: 'discount < total',
    },
  ],
});

Validation runs on every write — REST, Console, ObjectQL — so there's no "back door."

Indexes & performance

ObjectSchema.create({
  name: 'order',
  fields: { /* ... */ },
  indexes: [
    { fields: ['status', 'created_at'] },
    { fields: ['account', 'created_at'], unique: false },
  ],
});

The driver creates real DB indexes on schema sync.

Field groups

For long forms, group fields in Console:

ObjectSchema.create({
  name: 'task',
  fieldGroups: [
    { key: 'core',     label: 'Task',     icon: 'check-square' },
    { key: 'planning', label: 'Planning', icon: 'calendar' },
    { key: 'meta',     label: 'Metadata', icon: 'info', defaultExpanded: false },
  ],
  fields: {
    subject: Field.text({ label: 'Subject', group: 'core' }),
    due:     Field.date({ label: 'Due',     group: 'planning' }),
  },
});

Lifecycle & ownership

ObjectSchema.create({
  name: 'task',
  ownership: 'own',          // 'own' | 'shared' | 'system'
  enable: {
    apiEnabled: true,         // generated REST endpoints
    trackHistory: true,       // audit log of field changes
    feeds: true,              // sys_comment / sys_activity / @mentions
    softDelete: true,         // tombstone instead of hard delete
  },
});

System objects (free with every project)

You don't have to declare these — they're always there:

ObjectWhat
sys_userUser accounts
sys_orgOrganizations / tenants
sys_memberOrg membership
sys_role, sys_permission_setRBAC primitives
sys_audit_logAudit trail (when audit capability loaded)
sys_file, sys_attachmentFile metadata (when storage loaded)
sys_comment, sys_activityFeed / chatter (when feed loaded)
sys_session, sys_api_keyAuth artifacts
sys_webhook, sys_webhook_deliveryWebhook subs (when enabled)

Reference them in lookup fields by name — e.g. Field.lookup({ reference: 'sys_user' }).

Polymorphic platform features

When you enable feeds: true and trackHistory: true, your object automatically participates in:

  • sys_comment (thread_id = <object>:<id>)
  • sys_attachment (parent_object = <object>, parent_id = <id>)
  • sys_activity (timeline)
  • sys_audit_log (field-level diffs)

You don't wire these per object — they're polymorphic on the platform.

Where to go next

On this page