Schema

Estokad's schema is code. Types live in TypeScript files in your repo, get compiled to a JSON intermediate representation (IR) by @estokad/schema, and ship to the API via estokad push. The Studio renders the same IR — visual schema editing in M1.5b will read and write the same files.

The mental model

Three layers, top to bottom:

  • Authoring — TypeScript files calling defineType() or defineEmbedded(). Type-safe, tooling-friendly, lives in Git.
  • IR — a JSON schema-of-schemas. Stable across CLI versions; the API persists this and exposes it through the management endpoints.
  • Runtime — Drizzle row types, GraphQL types, REST OpenAPI definitions, Studio field renderers. All generated from the IR.

Schemas live in Git so engineering owns them; the Studio writes back via PR (configured in /settings/github) so content designers can propose changes without leaving the UI.

Field types

Estokad ships with sixteen field types. Each maps to a Postgres column kind, a Zod validator, a GraphQL field, and a Studio input component. The reference list:

| Type | Use for | |---|---| | text | single-line strings — titles, names, slugs | | richText | structured rich text with marks and blocks | | markdown | freeform Markdown | | number | integers and floats | | boolean | true/false flags | | dateTime | full timestamp | | date | day-precision (no timezone) | | slug | URL-safe slugs auto-derived from another field | | asset | single asset reference (image, video, file) | | assetList | ordered list of assets | | reference | reference to another entry | | referenceList | ordered list of references | | enum | one of a fixed list of strings | | geoPoint | latitude + longitude | | embedded | inline copy of an defineEmbedded() type | | json | escape hatch — opaque JSON blob |

Validators

Every field type accepts the validators that make sense for it. text takes min, max, regex. number takes min, max, integer. enum takes options. The full matrix is in defineType().

Validators run twice: in the Studio (real-time as the editor types) and on the API (always, regardless of who pushed the entry). The Studio validation is best-effort; the API is the gate.

Migrations

When you change a schema, estokad push validates every existing entry against the new shape. Narrowing changes — making a field required, swapping a type, removing an enum option — surface as a 422 with a list of conflicts. Pass --migrate to push anyway; existing rows keep their old data until next edited.

A long-form migration story (renames, splits, merges) lands with the Studio's diff view in M1.2b.

Locales

Locale-aware fields are an opt-in via the multi-locale module. With it enabled, any field can be marked localizable: true, which produces a per-locale value. Publish state is per-locale too — you can publish the French version while the Dutch is still in draft. See Schema · defineType() for the syntax.