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
| Property | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Machine name (snake_case). Immutable after creation. |
label | string | optional | Human-readable singular label (e.g. 'Account') |
pluralLabel | string | optional | Plural label (e.g. 'Accounts') |
description | string | optional | Developer documentation |
icon | string | optional | Icon name (Lucide/Material) |
tags | string[] | optional | Categorization tags (e.g. ['sales', 'system']) |
Data
| Property | Type | Required | Description |
|---|---|---|---|
fields | Record<string, Field> | ✅ | Field definitions. Keys must be snake_case. |
indexes | Index[] | optional | Database performance indexes |
datasource | string | optional | Target datasource ID. Default: 'default' |
tableName | string | optional | Physical table/collection name |
Display
| Property | Type | Required | Description |
|---|---|---|---|
displayNameField | string | optional | Field used as record display name (defaults to 'name') |
titleFormat | string | optional | Title expression (e.g. '{name} - {code}') |
compactLayout | string[] | optional | Primary fields for hover cards and lookups |
recordName | object | optional | Record 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
}| Flag | Default | Description |
|---|---|---|
trackHistory | false | Field history tracking for audit compliance |
searchable | true | Index records for global search |
apiEnabled | true | Expose object via automatic APIs |
apiMethods | all | Whitelist of allowed API operations |
files | false | Enable file attachments |
feeds | false | Enable social feed and comments |
activities | false | Enable tasks and events tracking |
trash | true | Enable soft delete with restore |
mru | true | Track Most Recently Used list |
clone | true | Allow 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,
}| Property | Type | Description |
|---|---|---|
type | 'text' | 'autonumber' | Generation mode |
displayFormat | string | Pattern (e.g. 'INV-{YYYY}-{0000}') |
startNumber | number | Starting 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 },
]| Property | Type | Required | Description |
|---|---|---|---|
fields | string[] | ✅ | Fields in the index |
type | enum | ✅ | 'btree', 'hash', 'gin', 'gist', 'fulltext' |
unique | boolean | ✅ | Enforce uniqueness |
partial | string | optional | Conditional index (SQL WHERE clause) |
Additional Properties
| Property | Type | Description |
|---|---|---|
active | boolean | Is object active (default: true) |
isSystem | boolean | System object, protected from deletion (default: false) |
abstract | boolean | Abstract base, cannot be instantiated (default: false) |
sharingModel | enum | 'private', 'read', 'read_write', 'full' |
keyPrefix | string | Short prefix for record IDs (e.g. '001') |
recordTypes | string[] | Record type names for this object |
validations | ValidationRule[] | Object-level validation rules (see Validation) |
stateMachines | Record<string, StateMachine> | Named state machines for parallel lifecycles |
Naming Conventions
| Element | Convention | Example |
|---|---|---|
Object name | snake_case | project_task, user_profile |
| Export constant | PascalCase | ProjectTask, UserProfile |
| Config keys | camelCase | trackHistory, 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'],
},
],
});Related
- Field Metadata — All field types and configuration options
- Validation Metadata — Data integrity rules
- View Metadata — Customize how objects are displayed
- Permission Metadata — Control access to objects