Schemas & records

A schema is a content type — Articles, Products, Team members. It declares fields; the server enforces them on every write. A record is one document of that type, stored in your own database, editable in the dashboard and readable over a generated REST API.

Defining a schema

Create one under Schemas with a name (becomes part of the API and the collection name) and fields. Each field has a name, a type, and optional rules.

Field types

TypeHoldsExtra rules
text, textareaShort / long textminLength, maxLength
richtextFormatted HTML content
emailAn email address (validated, lowercased)
urlAbsolute URLs or site-relative paths (/shop)
numberA numbermin, max
booleantrue / false
date, datetime, timePoints in time
enumerationOne of a fixed listoptions
jsonArbitrary structured data
mediaA reference to a media file (or a list)
relationA reference to a record in another schematarget schema, single or many
componentA nested group of fields, defined once as a model
dynamiczoneAn ordered list of component instancesallowed models, min/max items

Per-field flags: required and unique.

Validation is a guarantee, not a suggestion

Every write — from the dashboard, the records API, or a flow's record step — passes the same checks:

  • Required, types, ranges are enforced server-side; failures come back as per-field errors (422, with an issues array).
  • **unique fields are backed by real database indexes** (nulls exempt), so even two simultaneous writes cannot both slip through. On data migrated with pre-existing duplicates the index cannot build; the pre-write check still runs.
  • Relations must point at records that exist. A typo'd or stale ID is rejected with a per-field error instead of storing a dangling reference. (media references are exempt — files are deletable by design.)
  • Unknown fields are dropped, not rejected — removing a field from a schema doesn't break clients still sending it.

Models and dynamic zones

Models are reusable field groups (a "CTA" with title, text, link). A component field embeds one; a dynamiczone field holds an ordered list of instances of allowed models — the classic page-builder pattern, validated instance by instance.

Working with records

The Records view lists, filters, creates and edits records; you can customise which columns show and bulk-delete selections. Every create, edit and delete writes a history entry (before and after, who, when) — the History tab is your undo trail, and it retains deleted content on purpose.

The records API

Every schema gets a REST API, documented per-schema under its Docs tab with examples written against your own fields. Authenticate with a session or an API key (Authorization: Bearer esc_…).

GET    /api/projects/{project}/schemas/{schema}/records
POST   /api/projects/{project}/schemas/{schema}/records
GET    /api/projects/{project}/schemas/{schema}/records/{id}
PATCH  /api/projects/{project}/schemas/{schema}/records/{id}
DELETE /api/projects/{project}/schemas/{schema}/records/{id}

The query language

All on the list endpoint (and where sensible, the single-record one):

ParameterExampleMeaning
page, limit?page=2&limit=25Pagination (limit ≤ 100, default 10)
search?search=launchText search across text fields
sort?sort=-createdAt- prefix for descending
filter[field]?filter[status]=livePartial match on text, exact elsewhere
exactFilter[field]?exactFilter[title]=HelloExact match always
filter[field][op]?filter[views][$gte]=100Comparison operators
expand?expand=author or expand=*Embed related records
fields / omit?fields=title,statusShape the response

Allowed operators: $eq $ne $gt $gte $lt $lte $in $nin $exists $size $all (list operators take comma-separated values). $regex is deliberately not available, and field keys may never contain $ — the query language cannot be used to smuggle raw database operators.

Relation expansion

?expand=author populates the related record under each record's expand key in one extra query per target schema — not one per record. expand=* expands every relation field.

Records + flows

Endpoints and events read and write records through the same validation with dedicated steps — query records (with expand), create record and update record — so "combine two schemas and return a shaped response" needs no server code.

Standalone schemas

Projects migrated from v1 may carry standalone schemas — an older flavour with its own history and settings. They resolve through the same APIs and live under their own dashboard section; new projects only need regular schemas.