ObjectStackObjectStack

Object

Object protocol schemas

API Operations Enum

Source: packages/spec/src/data/object.zod.ts

TypeScript Usage

import { ApiMethod, ApiOperationSchema, IndexSchema, LifecycleSchema, LifecycleClassSchema, ObjectSchema, ObjectAccessConfigSchema, ObjectCapabilities, ObjectExtensionSchema, ObjectExternalBindingSchema, ObjectFieldGroupSchema, ObjectOwnershipEnum, ObjectRequiredPermissionsSchema, PerOperationRequiredPermissionsSchema, RowCrudActionOverrideSchema, TenancyConfigSchema } from '@objectstack/spec/data';
import type { ApiMethod, ApiOperation, Lifecycle, LifecycleClass, ObjectAccessConfig, ObjectCapabilities, ObjectExtension, ObjectExternalBinding, ObjectFieldGroup, ObjectRequiredPermissions, PerOperationRequiredPermissions, RowCrudActionOverride, TenancyConfig } from '@objectstack/spec/data';

// Validate data
const result = ApiMethod.parse(data);

ApiMethod

Allowed Values

  • get
  • list
  • create
  • update
  • delete
  • bulk

ApiOperation

Allowed Values

  • get
  • list
  • create
  • update
  • delete
  • upsert
  • bulk
  • aggregate
  • history
  • search
  • restore
  • purge
  • import
  • export

Index

Properties

PropertyTypeRequiredDescription
namestringoptionalIndex name (auto-generated if not provided)
fieldsstring[]Fields included in the index
uniqueboolean | 'global' | 'organization'Whether the index enforces uniqueness, and at which scope (ADR-0120). 'global' = materialized over exactly fields, no organization column injected — one holder across the whole installation; 'organization' = the driver prepends the NULL-safe organization key part (COALESCE(organization_id, 'global')) at registration — one holder per organization; bare true = deprecated positional spelling of 'global' (warned in 17.x by lint unique/unscoped-declared-index, rejected at protocol 18, #5082) — state the scope. 'tenant'/'org' are rejected — the word is 'organization'
typeneveroptional[REMOVED] indexes[].type was removed in @objectstack/spec 17.0.0 (#5248, ADR-0049) — no driver ever read it. SqlDriver.syncDeclaredIndexes creates every declared index through knex's table.index() / table.unique(), which cannot express an access method, so the value changed no DDL; its .default('btree') merely made an inert knob show up in every parse output. Delete the key. The index method is the driver/dialect's decision (Postgres defaults to B-tree; gin/gist/fulltext are dialect-specific and are chosen by a database-layer migration when a workload actually needs one). Run os migrate meta --from 16 to rewrite existing sources automatically.
partialneveroptional[REMOVED] indexes[].partial was removed in @objectstack/spec 17.0.0 (#5248, #4943, ADR-0049) — no driver ever emitted the WHERE clause, so a declared partial index was materialized as a FULL index and the predicate silently did nothing. Delete the key. Partial indexes are built at the database layer, not the declaration surface: issue CREATE [UNIQUE] INDEX … WHERE <predicate> from a runtime migration (this is what metadata-protocol's ensureOverlayIndex already does for sys_metadata). Drift detection is unaffected — it reads partiality back from the database's own DDL, never from this key. Run os migrate meta --from 16 to rewrite existing sources automatically.

Lifecycle

Properties

PropertyTypeRequiredDescription
classEnum<'record' | 'audit' | 'telemetry' | 'transient' | 'event'>Persistence contract: record (business truth, permanent) | audit (compliance ledger) | telemetry (high-freq log) | transient (ephemeral state) | event (bus messages).
retention{ maxAge: string; onlyWhen?: Record<string, string | number | boolean | object> }optionalAge-based retention window enforced by the LifecycleService Reaper.
ttl{ field: string; expireAfter: string }optionalPer-row TTL auto-expiry (transient/event classes).
storage{ strategy: 'rotation'; shards: integer; unit: Enum<'day' | 'week' | 'month'> }optionalPhysical storage strategy for high-frequency telemetry (LifecycleService Rotator).
archive{ after: string; to: string; keep?: string }optionalCold-store archival (LifecycleService Archiver) — audit-class hot→cold hand-off.
reclaimbooleanoptionalRun driver space reclamation (SQLite incremental_vacuum) after sweeping this object. Default true for non-record classes.

LifecycleClass

Allowed Values

  • record
  • audit
  • telemetry
  • transient
  • event

Object

Properties

PropertyTypeRequiredDescription
namestringMachine unique key (snake_case). Immutable.
labelstringoptionalHuman readable singular label (e.g. "Account")
pluralLabelstringoptionalHuman readable plural label (e.g. "Accounts")
descriptionstringoptionalDeveloper documentation / description
iconstringoptionalIcon name (Lucide/Material) for UI representation
isSystembooleanoptionalIs system object (protected from deletion; defaults its org-wide sharing to public when no sharingModel is set — plugin-sharing)
managedByEnum<'platform' | 'config' | 'system-data' | 'engine-owned' | 'append-only' | 'better-auth'>optionalLifecycle bucket — platform (user CRUD) | config (admin authored) | system-data (platform-defined schema, admin/user-writable data) | engine-owned (engine owns the lifecycle, no user writes) | append-only (audit) | better-auth (identity). UI clients honour the resolved affordance matrix.
ownershipEnum<'user' | 'business_unit' | 'org' | 'none'>optionalRecord-ownership model: user (default — injects reassignable owner_id plus owning_business_unit_id) | business_unit (unit-owned: owning_business_unit_id only, no owner_id) | org | none (no per-record owner, neither anchor). Distinct from the package own/extend contribution kind.
userActions{ create?: boolean | object; import?: boolean | object; edit?: boolean | object; delete?: boolean | object; … }optionalPer-object override of the resolved CRUD affordance matrix.
systemFieldsfalse | { tenant?: boolean; audit?: boolean }optionalOpt out of, or selectively disable, registry-level system-field auto-injection.
datasourcestringoptionalTarget Datasource ID. "default" is the primary DB.
external{ remoteName?: string; remoteSchema?: string; writable?: boolean; columnMap?: Record<string, string>; … }optionalRemote table binding for federated (external) objects.
fieldsRecord<string, { name?: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; description?: string; … }>Field definitions map. Keys must be snake_case identifiers.
indexes{ name?: string; fields: string[]; unique?: boolean | 'global' | 'organization' }[]optionalDatabase performance indexes
fieldGroups{ key: string; label: string; icon?: string; description?: string; … }[]optionalOrdered list of field groups (array order = display order). See ObjectFieldGroupSchema.
tenancy{ enabled: boolean; tenantField?: string }optionalMulti-tenancy configuration for SaaS applications
access{ default?: Enum<'public' | 'private'> }optional[ADR-0066 D2] Object exposure posture (public-by-default vs private secure-by-default).
requiredPermissionsstring[] | { read?: string[]; create?: string[]; update?: string[]; delete?: string[] }optional[ADR-0066 D3/⑤] Capabilities required to access this object (AND-gate) — string[] gates all CRUD, or a {read,create,update,delete} map gates per operation.
lifecycle{ class: Enum<'record' | 'audit' | 'telemetry' | 'transient' | 'event'>; retention?: object; ttl?: object; storage?: object; … }optionalData lifecycle contract (ADR-0057): class + retention/ttl/rotation/archive policies enforced by the platform LifecycleService.
fileAccessDelegatestringoptionalKernel service that authorizes downloads of files owned by this object's media fields, instead of testing whether the caller can read the owning row. For objects whose access is mediated by a service (e.g. sys_approval_action → approvals). Fails closed.
validationsany[]optionalObject-level validation rules
activityMilestones{ field: string; value: string; summary: string; type?: string }[]optionalDeclarative semantic activity milestones — emit a templated timeline row when a field transitions into a value, no hook code (ADR-0052 §5b.2).
nameFieldstringoptional[ADR-0079] Canonical primary title field — the stored field used as the record display name (e.g. "name", "title").
displayNameFieldstringoptional[DEPRECATED → nameField] Field to use as the record display name (e.g., "name", "title"). Accepted as an alias for nameField.
titleFormatstring | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optional[DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField.
highlightFieldsstring[]optional[ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout.
stageFieldstring | falseoptional[ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed.
listViewsRecord<string, { name?: string; label?: string | Record<string, string>; type?: Enum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>; data?: object | … +3 more; … }>optionalBuilt-in named list views (segmented tabs) shipped with the object schema — "views" mode, dropdown userFilters allowed, no page-only tabs (ADR-0047)
searchableFieldsstring[]optionalFields the $search query matches against (ADR-0061). Canonical default for the record picker, list quick-search and global search; views may narrow it. When unset, search auto-defaults to the name/title field plus short-text fields. Entries must name a STORED column: a virtual formula field is computed on read and materializes no column, so searching it can never match and it is refused (#6674) — mirror the value onto a stored text field and declare that.
enable{ trackHistory?: boolean; searchable?: boolean; apiEnabled?: boolean; apiMethods?: Enum<'get' | 'list' | 'create' | 'update' | 'delete' | 'bulk'>[]; … }optionalEnabled system features modules
sharingModelEnum<'private' | 'public_read' | 'public_read_write' | 'controlled_by_parent'>optionalOrg-Wide Default record visibility (OWD) for INTERNAL users. Canonical four only (legacy aliases removed, ADR-0090 D4): private (owner-only) | public_read (everyone reads, owner writes) | public_read_write (everyone reads+writes) | controlled_by_parent (derived from the master record). A CUSTOM object that omits this resolves to private at runtime (ADR-0090 D1).
externalSharingModelEnum<'private' | 'public_read' | 'public_read_write' | 'controlled_by_parent'>optional[ADR-0090 D11] OWD for external (portal/partner) principals. Defaults to private; must be <= sharingModel in openness.
publicSharing{ enabled?: boolean; allowedAudiences?: Enum<'public' | 'link_only' | 'signed_in' | 'email'>[]; allowedPermissions?: Enum<'view' | 'comment' | 'edit'>[]; maxExpiryDays?: integer; … }optionalPublic share-link policy (Notion/Figma-style link sharing)
actions{ name: string; label: string | Record<string, string>; description?: string | Record<string, string>; objectName?: string; … }[]optionalActions associated with this object (auto-populated from top-level actions via objectName)
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this object.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

ObjectAccessConfig

Properties

PropertyTypeRequiredDescription
defaultEnum<'public' | 'private'>Default exposure posture: public (covered by wildcard grants) | private (needs explicit grant; exempt from wildcard RLS).

ObjectCapabilities

Properties

PropertyTypeRequiredDescription
trackHistorybooleanShow the record History tab (audit-trail UI). Pair with per-field trackHistory to pick which field diffs are summarized; audit capture itself is always on for compliance
searchablebooleanIndex records for global search
apiEnabledbooleanExpose object via automatic APIs
apiMethodsEnum<'get' | 'list' | 'create' | 'update' | 'delete' | 'bulk'>[]optionalWhitelist of allowed API operations (six primitives; undefined = all, [] = none)
filesbooleanGeneric record Attachments panel (sys_attachment). Opt-in: true surfaces the panel and permits attachments targeting this object; otherwise creation is rejected. Field.file/Field.image are independent
feedsbooleanRecord comments/collaboration feed. Default on; explicit false hides the feed UI and rejects new comments for this object
activitiesbooleanRecord activity timeline (sys_activity mirror of CRUD). Default on; explicit false stops mirroring and hides the timeline
clonebooleanAllow record deep cloning

ObjectExtension

Properties

PropertyTypeRequiredDescription
extendstringTarget object name (FQN) to extend
fieldsRecord<string, { name?: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; description?: string; … }>optionalFields to add/override
labelstringoptionalOverride label for the extended object
pluralLabelstringoptionalOverride plural label for the extended object
descriptionstringoptionalOverride description for the extended object
validationsany[]optionalAdditional validation rules to merge into the target object
indexes{ name?: string; fields: string[]; unique?: boolean | 'global' | 'organization' }[]optionalAdditional indexes to merge into the target object
priorityintegeroptionalMerge priority (higher = applied later)

ObjectExternalBinding

External datasource binding (ADR-0015)

Properties

PropertyTypeRequiredDescription
remoteNamestringoptionalRemote table/view name. Defaults to object.name.
remoteSchemastringoptionalRemote schema/database qualifier.
writablebooleanPer-object write opt-in (also requires datasource.external.allowWrites).
columnMapRecord<string, string>optionalRemote column name → local field name.
introspectedAtstringoptionalSet by os datasource introspect; informational.
ignoreColumnsstring[]optionalRemote columns to skip during validation (dev convenience).

ObjectFieldGroup

Properties

PropertyTypeRequiredDescription
keystringGroup machine key (snake_case). Referenced by Field.group.
labelstringGroup display label
iconstringoptionalIcon name (Lucide/Material) for the group header
descriptionstringoptionalOptional description shown under the group header
collapseEnum<'none' | 'expanded' | 'collapsed'>[ADR-0085] Section collapse behaviour: 'none' (always open, no toggle), 'expanded' (collapsible, starts open), 'collapsed' (collapsible, starts closed).
defaultExpandedbooleanoptional[DEPRECATED → collapse] true → 'expanded', false → 'collapsed'.
collapsiblebooleanoptional[DEPRECATED → collapse] Boolean pair with collapsed; use the collapse enum.
collapsedbooleanoptional[DEPRECATED → collapse] Boolean pair with collapsible; use the collapse enum.

ObjectOwnershipEnum

Allowed Values

  • own
  • extend
  • overlay

ObjectRequiredPermissions

Union Options

This schema accepts one of the following structures:

Option 1

Type: string[]


Option 2

Properties

PropertyTypeRequiredDescription
readstring[]optionalCapabilities required to read (find/findOne/count/aggregate).
createstring[]optionalCapabilities required to create (insert).
updatestring[]optionalCapabilities required to update (update/transfer/restore).
deletestring[]optionalCapabilities required to delete (delete/purge).


PerOperationRequiredPermissions

Properties

PropertyTypeRequiredDescription
readstring[]optionalCapabilities required to read (find/findOne/count/aggregate).
createstring[]optionalCapabilities required to create (insert).
updatestring[]optionalCapabilities required to update (update/transfer/restore).
deletestring[]optionalCapabilities required to delete (delete/purge).

RowCrudActionOverride

Boolean-or-predicates override for a built-in CRUD affordance.

Properties

PropertyTypeRequiredDescription
enabledbooleanoptionalObject-level on/off for the generic affordance; same meaning as the bare boolean form. Omitted → managedBy bucket default.
visibleWhenstring | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optionalCEL predicate over the record in scope (row record for edit/delete, host record for a related-list create/import toolbar); false → hide the button. Fail-closed.
disabledWhenstring | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optionalCEL predicate over the record in scope (row record for edit/delete, host record for a related-list create/import toolbar); true → render the button disabled. Fail-soft.

TenancyConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanEnable multi-tenancy for this object
tenantFieldstringoptionalColumn this object is tenant-scoped by. Omit it unless the tenant column genuinely is not the platform's: when undeclared the driver falls back to organization_id, the kernel-injected column the RLS predicates and tenantPolicy() also assume. A declared name is honoured only when the object really has that field — otherwise the same organization_id fallback applies. No default is materialized here on purpose (#5315).

On this page