ObjectStackObjectStack

Data Modeling

Objects, fields, relationships, validation, CEL formulas, and a compiled query AST — the ObjectQL layer every other module builds on.

Every ObjectStack application starts here: you declare objects (business entities), their fields, relationships, and validation rules as typed metadata, and the platform derives the database schema, REST API, permission surface, and UI from that single definition. This module is the practical documentation for the ObjectQL layer — the Data Protocol specified normatively in the ObjectQL spec.

A real object definition looks like this (from the CRM example app):

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

export const Lead = ObjectSchema.create({
  name: 'crm_lead',
  sharingModel: 'public_read_write',   // OWD — required on every custom object
  label: 'Lead',
  pluralLabel: 'Leads',
  icon: 'funnel',
  description: 'An inbound prospect not yet qualified as an opportunity.',
  fields: {
    name: Field.text({ label: 'Lead Name', required: true, searchable: true, maxLength: 200 }),
    email: Field.email({ label: 'Email', searchable: true }),
    phone: Field.phone({ label: 'Phone' }),
    company: Field.text({ label: 'Company', searchable: true, maxLength: 200 }),
  },
});

That one definition is enough to get a persisted table, CRUD + query endpoints, permission checks, and generated list views and forms.

What the data layer actually gives you

  • A full spectrum of field types — from text, currency, and lookup/master_detail relationships to formula, summary, signature, and vector (with dimensions config for AI embeddings). See the Field Types gallery.
  • Validation as metadata — required/format rules, CEL script validation with access to previous.<field>, uniqueness via indexes, and severity levels — enforced identically in API, UI, and automation.
  • CEL expressions — formula fields and computed defaults share one expression language (Expressions).
  • A compiled query AST — queries are JSON documents validated against the protocol, then compiled by a driver into native queries with aggregations, groupBy, HAVING, and subqueries. joins and request-surface windowFunctions were retired in protocol 17 (#4286, ADR-0049) and are now rejected at parse; window functions survive only as a SQL-driver door (SqlDriver.findWithWindowFunctions()), not on the IDataDriver contract. The query cheat sheet covers the syntax; the spec is normative.
  • Five database drivers in this repodriver-sql (PostgreSQL / MySQL / SQLite via Knex), driver-mongodb, driver-memory (in-memory, for tests and demos), driver-sqlite-wasm (SQLite in the browser / WebContainers), and driver-turso (Turso / libSQL — an optional install, because it pulls @libsql/client plus native bindings; see Database Drivers). The same model runs unchanged on any of them.
  • External datasource federation — introspect an existing external database, import selected tables into the catalog, and query them alongside native objects (External Datasources).
  • Composable ownership — one package owns an object, and any other package can merge fields, validation rules, and indexes into it without forking the definition (Object Extensions).
  • Repeatable bulk import — a named mapping projects someone else's column headers and codes onto your fields, and the import endpoint applies it by name (Import Mappings).

What's in this module

On this page