ObjectStackObjectStack

Object Metadata

Define business entities with ObjectSchema — the core building block of every ObjectStack application

Object Metadata

An Object is the foundational metadata type in ObjectStack. It defines a business entity — its fields, capabilities, indexes, and behaviors. Each Object maps to a database table/collection and automatically gets CRUD APIs, UI forms, and query support.

Basic Structure

import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Account = ObjectSchema.create({
  name: 'account',
  label: 'Account',
  pluralLabel: 'Accounts',
  icon: 'building',
  description: 'Companies and organizations',

  fields: {
    name: Field.text({ label: 'Account Name', required: true }),
    industry: Field.select({
      label: 'Industry',
      options: [
        { label: 'Technology', value: 'technology' },
        { label: 'Finance', value: 'finance' },
        { label: 'Healthcare', value: 'healthcare' },
      ],
    }),
    annual_revenue: Field.currency({ label: 'Annual Revenue', scale: 2 }),
    owner: Field.lookup('user', { label: 'Owner', required: true }),
  },

  enable: {
    apiEnabled: true,
    searchable: true,
    trackHistory: true,
  },
});

Properties

Identity

PropertyTypeRequiredDescription
namestringMachine name (snake_case). Immutable after creation.
labelstringoptionalHuman-readable singular label (e.g. 'Account')
pluralLabelstringoptionalPlural label (e.g. 'Accounts')
descriptionstringoptionalDeveloper documentation
iconstringoptionalIcon name (Lucide/Material)
tagsstring[]optionalCategorization tags (e.g. ['sales', 'system'])

Data

PropertyTypeRequiredDescription
fieldsRecord<string, Field>Field definitions. Keys must be snake_case.
indexesIndex[]optionalDatabase performance indexes
datasourcestringoptionalTarget datasource ID. Default: 'default'
tableNamestringoptionalPhysical table/collection name

Display

PropertyTypeRequiredDescription
displayNameFieldstringoptionalField used as record display name (defaults to 'name')
titleFormatstringoptionalTitle expression (e.g. '{name} - {code}')
compactLayoutstring[]optionalPrimary fields for hover cards and lookups
recordNameobjectoptionalRecord name auto-generation config

Capabilities (enable)

Control which platform features are active for this object:

enable: {
  trackHistory: true,      // Field history tracking for audit
  searchable: true,        // Include in global search index
  apiEnabled: true,        // Expose via REST/GraphQL APIs
  apiMethods: ['get', 'list', 'create', 'update', 'delete'],
  files: true,             // File attachments
  feeds: true,             // Activity feed and comments
  activities: true,        // Tasks and events tracking
  trash: true,             // Soft delete with restore
  mru: true,               // Most Recently Used tracking
  clone: true,             // Deep record cloning
}
FlagDefaultDescription
trackHistoryfalseField history tracking for audit compliance
searchabletrueIndex records for global search
apiEnabledtrueExpose object via automatic APIs
apiMethodsallWhitelist of allowed API operations
filesfalseEnable file attachments
feedsfalseEnable social feed and comments
activitiesfalseEnable tasks and events tracking
trashtrueEnable soft delete with restore
mrutrueTrack Most Recently Used list
clonetrueAllow record deep cloning

Enterprise Features

Multi-Tenancy

tenancy: {
  enabled: true,
  strategy: 'shared',         // 'shared' | 'isolated' | 'hybrid'
  tenantField: 'tenant_id',
  crossTenantAccess: false,
}

Soft Delete

softDelete: {
  enabled: true,
  field: 'deleted_at',
  cascadeDelete: false,
}

Versioning

versioning: {
  enabled: true,
  strategy: 'snapshot',       // 'snapshot' | 'delta' | 'event-sourcing'
  retentionDays: 365,
  versionField: 'version',
}

Change Data Capture

cdc: {
  enabled: true,
  events: ['insert', 'update', 'delete'],
  destination: 'kafka://my-topic',
}

Table Partitioning

partitioning: {
  enabled: true,
  strategy: 'range',          // 'range' | 'hash' | 'list'
  key: 'created_at',
  interval: '1 month',        // Required for range strategy
}

Record Name

Auto-generate unique record identifiers:

recordName: {
  type: 'autonumber',
  displayFormat: 'ACC-{0000}',   // ACC-0001, ACC-0002, ...
  startNumber: 1,
}
PropertyTypeDescription
type'text' | 'autonumber'Generation mode
displayFormatstringPattern (e.g. 'INV-{YYYY}-{0000}')
startNumbernumberStarting number (default: 1)

Indexes

Optimize query performance:

indexes: [
  { fields: ['name'], type: 'btree', unique: false },
  { fields: ['email'], type: 'btree', unique: true },
  { fields: ['type', 'status'], type: 'btree', unique: false },
]
PropertyTypeRequiredDescription
fieldsstring[]Fields in the index
typeenum'btree', 'hash', 'gin', 'gist', 'fulltext'
uniquebooleanEnforce uniqueness
partialstringoptionalConditional index (SQL WHERE clause)

Additional Properties

PropertyTypeDescription
activebooleanIs object active (default: true)
isSystembooleanSystem object, protected from deletion (default: false)
abstractbooleanAbstract base, cannot be instantiated (default: false)
sharingModelenum'private', 'read', 'read_write', 'full'
keyPrefixstringShort prefix for record IDs (e.g. '001')
recordTypesstring[]Record type names for this object
validationsValidationRule[]Object-level validation rules (see Validation)
stateMachinesRecord<string, StateMachine>Named state machines for parallel lifecycles

Naming Conventions

ElementConventionExample
Object namesnake_caseproject_task, user_profile
Export constantPascalCaseProjectTask, UserProfile
Config keyscamelCasetrackHistory, apiEnabled

Complete Example

import { ObjectSchema, Field } from '@objectstack/spec/data';

export const ProjectTask = ObjectSchema.create({
  name: 'project_task',
  label: 'Project Task',
  pluralLabel: 'Project Tasks',
  icon: 'check-square',
  description: 'Tasks within a project',

  fields: {
    title: Field.text({ label: 'Title', required: true, maxLength: 255 }),
    description: Field.textarea({ 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' },
      ],
    }),
    priority: Field.select({
      label: 'Priority',
      options: [
        { label: 'Low', value: 'low' },
        { label: 'Medium', value: 'medium', default: true },
        { label: 'High', value: 'high' },
      ],
    }),
    due_date: Field.date({ label: 'Due Date' }),
    assignee: Field.lookup('user', { label: 'Assignee' }),
    project: Field.lookup('project', { label: 'Project', required: true }),
    estimated_hours: Field.number({ label: 'Estimated Hours', min: 0 }),
  },

  indexes: [
    { fields: ['status'], type: 'btree', unique: false },
    { fields: ['project', 'status'], type: 'btree', unique: false },
  ],

  enable: {
    apiEnabled: true,
    searchable: true,
    trackHistory: true,
    feeds: true,
    trash: true,
  },

  validations: [
    {
      name: 'due_date_future',
      type: 'script',
      severity: 'warning',
      message: 'Due date should be in the future',
      condition: 'due_date < TODAY()',
      events: ['insert'],
    },
  ],
});

On this page