Schemas

Defining Tables

Define tables and relationships in schema.ts using the TypeScript DSL.

Project layout

A Jazz project has a small set of files at the app root:

app-root/
├── schema.ts            # Structural schema — tables and columns
├── permissions.ts       # Optional row-level policies
└── migrations/          # Reviewed migration edges
    └── 20260331-add-description-aaa-bbb.ts

schema.ts is the source of truth for your data model. permissions.ts is optional and must be a separate file. The migrations/ directory holds reviewed migration stubs — see Migrations for the full workflow.

Table definitions

Tables are defined in schema.ts using the Jazz DSL. Each s.table(...) call registers a table and s.ref(...) defines typed relations between them.

schema.ts
projects: s.table({
  name: s.string(),
}),
todos: s.table({
  title: s.string(),
  done: s.boolean(),
  description: s.string().optional(),
  owner_id: s.string().optional(),
  parentId: s.ref("todos").optional(),
  projectId: s.ref("projects").optional(),
}),

s.ref() columns must be named with an Id or _id suffix (for example projectId or owner_id). For s.array(s.ref()), use an Ids or _ids suffix instead. The runtime enforces this convention and will throw if a ref column name does not match.

Validate locally

Validate your schema and permissions locally:

pnpm dlx jazz-tools@alpha validate

This validates schema.ts and the optional permissions.ts, then compiles them into Jazz's internal schema representation.

When Jazz reports a difference between the old and new schema hashes after changing schema.ts, and your app already has data, you should create and push a migration edge as described in Migrations:

pnpm dlx jazz-tools@alpha migrations create <appId> --fromHash <fromHash> --toHash <toHash>
pnpm dlx jazz-tools@alpha migrations push <appId> <fromHash> <toHash>

When you change your schema on a shared app, create and push a migration. See Migrations for details.

If you need to clear local browser data after a schema change, see How do I reset browser storage?.

Exporting the app

s.defineApp(schema) converts your schema definition into a typed app object. This is what you pass to queries, mutations, and subscriptions throughout your application code.

schema.ts
type AppSchema = s.Schema<typeof schema>;
export const app: s.App<AppSchema> = s.defineApp(schema);

export type Todo = s.RowOf<typeof app.todos>;

The app object has one typed table handle per table (e.g. app.todos, app.projects). Table handles are query builders — you chain .where(), .include(), .orderBy() and other methods directly on them.

Type helpers

Extract precise TypeScript types from any table handle:

HelperReturns
s.RowOf<typeof app.todos>The row type (all columns, id included)
s.InsertOf<typeof app.todos>The insert shape (no id, respects optionals and defaults)
s.WhereOf<typeof app.todos>The where(...) input shape for that table

On this page