Overview
Schema as data: declare entities, get a generic API, keep everything encrypted.
Updated
On this page
The idea
The records engine is what lets one codebase serve a smoke shop, a car-rental fleet and a charter company. A platform declares its entities — a vehicle, a repair_ticket, an seo_engagement — as record types: named schemas with typed fields. The API over them is generic: create, read, update, list with filters, sorting and pagination, all through one surface.
const { records, total } = await dash.records.list("repair_ticket", {
status: "open",
sort: "-created_at",
});
await dash.records.create("repair_ticket", {
data: { serial_number: "A1B2", priority: "high" },
customer: "pcust__…",
});
What a record is
One row of a record type. It carries:
data— the values, encrypted at rest. Postgres sees ciphertext.index_data— the only plaintext copy of any values: exactly the fields the schema markedfilterable, projected out so the database can query them (GIN-indexed).search_text— a flattened copy of the fields markedsearchable.status— a plain workflow column (open,closed,won…), deliberately outside the encrypted blob so it can be a WHERE clause.customer— an optional link to a platform customer, which is how portal scoping works.
Why modules ride it
Most higher-level modules are record types plus a purpose-built surface: a lead is a contact record, a product is a product record, a task is a task record, a client website is a website record. That is why a new module usually costs a schema and a front end, not a backend.
update() merges data — sending one field will not wipe the rest. That makes partial edits safe from concurrent surfaces (a portal and a dashboard editing different fields of the same record don't clobber each other).