Data model and the typed client
Declaring the tables
Schema reference
Field-by-field reference for schema.json — entities, columns, indexes, checks, staging.
Column types
The raw PostgreSQL types you may use, default and check as SQL, generated and generator.
Relationships
Foreign keys inside your app, why cross-app keys are rejected, and nested-resource CRUD.
Tenant isolation
What the platform injects, and why you never declare tenant_id.
Migrations
Additive by default, dropped_columns as the only removal, and the three-deploy rename.
Scoping
scopeable entities, the scoping engine, and providing a scope dimension.
Reading and writing
The typed client
list, page, get, create, update, delete — the query shape and the ten operators.
Adapters
mock, remote and runtime — the same code in every environment, and what mocks do not enforce.
Cross-app lookups
The lookup block — the one declaration that lets another app resolve your ids.
Projections
search.json and analytics.json — optional views over your entities.
Your app owns its tables. Nothing else reads them directly, and it reads nothing else's directly — the boundary between two apps is their capabilities, not their schemas.
You declare the tables in one file, schema.json, and the platform does the rest: it creates them, namespaces them to {vendor}__{app}__{entity}, injects and enforces the tenant column, and generates a typed data client at src/db.generated.js from the same declaration. You write no migrations and no tenant filters.
The shape of it
schema.json ──► namespaced tables + injected tenant_id + enforced scoping
└─► src/db.generated.js — one typed repository per entity
Two consequences run through every page in this group:
Migrations are additive. The difference between your schema.json and what is deployed is the change set. Additions apply; a removal needs an explicit tombstone, because inferring destruction from an absence would turn a typo into permanent data loss. Read Migrations before you delete a line from schema.json.
Scoping is not yours to forget. Your reads and writes only ever see the calling tenant's rows, applied by the platform rather than by your code. There is no query you can write that skips it. See Tenant isolation.
In this group
- Schema reference — the field-by-field reference for
schema.json. - Column types — the allowed PostgreSQL types, and why
defaultandcheckare SQL rather than values. - Relationships —
references, the cross-app boundary, and nested resources. - Tenant isolation — the injected columns.
- Migrations — what happens when a schema change is applied.
- Scoping — markets, channels and any other dimension your rows can be sliced by.
- The typed client — the query surface, in full, including what it deliberately cannot express.
- Adapters —
mock,remote,runtime. - Cross-app lookups — the privacy boundary another app sees.
- Projections —
search.jsonandanalytics.json.
Related
- Your app's API — publishing operations over these tables on the public gateway.
- Entity access — the grant that decides which operations the generated client even has.
- App SDK — the package the client and router come from.