CapyDB Docs
Guides

Type generation

Generate TypeScript types, Zod schemas, or a Drizzle schema from the live database schema - and gate CI on schema drift.

Quickstart

In a linked project directory (capydb link):

capydb generate types        # writes database.types.ts

Generation runs server-side against the live schema of your cell - one implementation shared by the CLI, the API, and the MCP server - so the output is identical no matter which surface asked for it. The CLI just fetches the result and writes the file.

Every generator takes the same flags:

  • --out <path> / -o - output file (defaults to the generator's suggested filename)
  • --print - print to stdout instead of writing a file
  • --preview <id> - generate from a preview database instead of the project database
  • --project <ref> - project id, slug, or name (defaults to the linked project)

TypeScript

The default style (capydb) emits per-table Row / Insert / Update interfaces plus a Database aggregate that mirrors them:

database.types.ts (excerpt)
export interface UsersRow { ... }
export interface UsersInsert { ... }   // generated/defaulted columns optional
export interface UsersUpdate { ... }   // everything optional

export type Database = {
  public: {
    Tables: {
      users: { Row: UsersRow; Insert: UsersInsert; Update: UsersUpdate };
    };
    ...
  };
};

Porting from Supabase

--style supabase emits a supabase-js-compatible Database generic instead, including the Tables<T> / TablesInsert<T> / TablesUpdate<T> / Enums<T> helper types:

capydb generate types --style supabase

If you imported a Supabase project and your code is full of Tables<"users"> references, this style is a drop-in replacement for supabase gen types typescript - regenerate against your CapyDB cell and the existing type references keep compiling.

Zod

capydb generate zod          # writes database.zod.ts

Emits a z.object(...) row schema per table plus inferred Row types - useful when you validate data at runtime (API boundaries, form input) rather than only at compile time.

Two ways to Zod

There are two complementary sources of truth for runtime validators:

  • Database-first - capydb generate zod derives validators from the live schema. Right when the database is the source of truth (imported/legacy schemas, SQL-managed migrations, or no ORM at all): the validators track what the database actually enforces.
  • Schema-first - drizzle v1 ships validator generation in core: createSelectSchema / createInsertSchema from drizzle-orm/zod derive validators from your Drizzle table objects. Right when your Drizzle schema file is the source of truth and migrations flow through drizzle-kit - validators update the moment the schema file does, no regeneration step.
// schema-first: validators straight from your Drizzle tables
import { createInsertSchema, createSelectSchema } from 'drizzle-orm/zod'
import { users } from './db/schema'

const userRow = createSelectSchema(users)
const newUser = createInsertSchema(users)

They compose: bootstrap with capydb generate drizzle, then let drizzle-orm/zod derive validators from that schema file going forward.

Drizzle

capydb generate drizzle      # writes schema.ts

Renders the live schema as a Drizzle ORM schema file - tables, enums, and foreign keys as pgTable / pgEnum declarations. This is the "database-first" entry into Drizzle: point it at an existing cell and start querying with full types. capydb init drizzle goes one step further and scaffolds the config and client around it - see the Drizzle guide.

Generating against a preview branch in CI

The GitHub Action creates a preview database per pull request; --preview generates types from that branch database instead of production, so a PR that migrates the schema also carries the matching types:

.github/workflows/preview-database.yml (excerpt)
- name: Create preview database
  id: capydb
  uses: capy-base/capydb-preview-action@v1
  with:
    api-key: ${{ secrets.CAPYDB_API_KEY }}
    project-id: ${{ vars.CAPYDB_PROJECT_ID }}

- name: Run migrations against the preview
  run: pnpm db:migrate   # DATABASE_URL / DATABASE_URL_UNPOOLED already exported

- name: Verify committed types match the migrated schema
  env:
    CAPYDB_API_KEY: ${{ secrets.CAPYDB_API_KEY }}
  run: |
    capydb generate types --preview "${{ steps.capydb.outputs.preview-id }}" --out database.types.ts
    git diff --exit-code database.types.ts

If the generated file differs from what the PR committed, the diff fails the job - the types can never silently lag the schema.

Drizzle-kit v1 users should add one more gate before migrating: drizzle-kit check detects migrations that conflict across git branches (two PRs each adding a migration that doesn't commute), and drizzle-kit push --explain previews the exact DDL a push would run without executing it:

- name: Migration hygiene (drizzle-kit v1)
  run: pnpm drizzle-kit check

Drift gating with schema dump and diff

capydb schema dump writes the canonical schema document as deterministic JSON - the same structural facts the generators consume (tables, columns, constraints, enums, extensions). Because the output is deterministic, it can be committed and diffed like source:

capydb schema dump --out schema.json

capydb schema diff compares the live schema against a committed snapshot or another preview database, and --exit-code makes it behave like git diff --exit-code - differences flip the exit status so CI can gate on drift:

# Has production drifted from the committed snapshot?
capydb schema diff --against schema.json --exit-code

# What does this PR's preview change relative to production?
capydb schema diff --against-preview <preview-id>

--against accepts either a bare capydb schema dump file or a raw API response envelope. Combine --preview with --against to check a branch database against the snapshot instead.

Scoped API keys for CI

The schema endpoints accept any API key with projects:read - or a key carrying only the narrow schema:read scope. A schema:read-only key can introspect and generate types but cannot read credentials, run SQL, or touch lifecycle operations, which makes it the right shape for typegen-only CI jobs. Create one in the dashboard under Settings → API keys, project-scoped to the repository's project.

API

Everything above is a thin wrapper over four endpoints (see the Schema tag in the API reference):

GET /v1/projects/{id}/schema
GET /v1/projects/{id}/schema/types?language=typescript|zod|drizzle&style=capydb|supabase
GET /v1/preview-databases/{id}/schema
GET /v1/preview-databases/{id}/schema/types?language=...&style=...

/schema returns the canonical schema document; /schema/types returns { language, style, filename, content }. style only applies to language=typescript.

For agents

The MCP server exposes the same capabilities as get_schema and generate_types. get_schema returns the whole schema in one call - agents should prefer it over introspecting pg_catalog with run_sql - and generate_types returns { filename, content } for the agent to write into the repo, with preview_id for branch databases. For the wider pattern of letting agents change schemas safely, see the agent safety loop.