Skip to main content
supaschema reads your declarative schema SQL files and generates two output files: a TypeScript interface that matches the Supabase client’s Database type shape, and a set of Zod validators for runtime parsing of database rows. Both files are derived entirely from your schema files — no running database, no introspection query, no network call required.

What is generated

database.types.ts

A Supabase-compatible TypeScript interface named Database. It follows the exact structure the @supabase/supabase-js client expects, so you can pass it as a type parameter and get end-to-end type safety on every query:
export interface Database {
  app: {
    Tables: {
      accounts: {
        Row: {
          id: string
          name: string
          status: string
          created_at: string
        }
        Insert: {
          id?: string
          name: string
          status?: string
          created_at?: string
        }
        Update: {
          id?: string
          name?: string
          status?: string
          created_at?: string
        }
      }
      // ... other tables
    }
    Views: { /* ... */ }
    Functions: { /* ... */ }
    Enums: {
      account_status: 'active' | 'suspended' | 'deleted'
    }
  }
}

database.zod.ts

A Zod schema for every table row, insert shape, and update shape in your database. Each validator is exported by name so you can import exactly what you need:
export const AccountsRowSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  status: z.string(),
  created_at: z.string().datetime(),
})

export type AccountsRow = z.infer<typeof AccountsRowSchema>

Running the command

1

Generate types from your schema files

npx supaschema types
This reads every file matched by schemaPaths in your config and writes database.types.ts and database.zod.ts to the project root (or the paths configured in supaschema.config.json).
2

Optionally override output paths

npx supaschema types --types-file src/lib/database.types.ts
The --types-file flag sets the TypeScript output path. The Zod file is placed alongside it automatically unless you also set --zod-file.
3

Let diff refresh types automatically

You do not need to run types separately after generating a migration. diff refreshes both output files as part of its normal run:
npx supaschema diff
# → generates supabase/migrations/20240601_...sql
# → regenerates database.types.ts
# → regenerates database.zod.ts

Configuring output paths

Set permanent output paths in supaschema.config.json so every team member and every CI run writes to the same locations:
{
  "$schema": "./node_modules/supaschema/config-schema.json",
  "typesFile": "src/lib/database.types.ts",
  "zodFile": "src/lib/database.zod.ts"
}
Keep both files out of .gitignore. Committing them makes type errors visible in pull request diffs and lets your IDE provide correct completions even without running supaschema types locally.

Using generated types with the Supabase client

Pass Database as the type parameter when creating your Supabase client. Every subsequent query is fully typed — table names, column names, filter values, and return shapes are all checked at compile time:
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'

const supabase = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

// Fully typed — TypeScript knows the shape of every returned row
const { data, error } = await supabase
  .from('accounts')
  .select('id, name, status')

// data is inferred as Array<{ id: string; name: string; status: string }> | null
TypeScript will catch misspelled column names, incorrect filter types, and missing required fields in insert operations before the code ever runs.

Insert with type checking

import type { Database } from './database.types'

type AccountInsert = Database['app']['Tables']['accounts']['Insert']

async function createAccount(payload: AccountInsert) {
  const { data, error } = await supabase
    .from('accounts')
    .insert(payload)
    .select()
    .single()

  return data
}

Enum types

Enums declared in your schema are reflected as union types:
import type { Database } from './database.types'

type AccountStatus = Database['app']['Enums']['account_status']
// → 'active' | 'suspended' | 'deleted'

Using Zod schemas for runtime validation

The TypeScript types provide compile-time safety. Use Zod validators when you need to verify data at runtime — for example, validating an API response body, parsing a webhook payload, or checking rows read from an external system:
import { AccountsRowSchema } from './database.zod'

// Throws ZodError if rawData does not match the schema
const account = AccountsRowSchema.parse(rawData)

// Returns { success, data, error } instead of throwing
const result = AccountsRowSchema.safeParse(rawData)
if (!result.success) {
  console.error(result.error.flatten())
}

API route example

import { AccountsInsertSchema } from './database.zod'

export async function POST(request: Request) {
  const body = await request.json()

  const parsed = AccountsInsertSchema.safeParse(body)
  if (!parsed.success) {
    return Response.json(
      { errors: parsed.error.flatten() },
      { status: 400 }
    )
  }

  const { data, error } = await supabase
    .from('accounts')
    .insert(parsed.data)
    .select()
    .single()

  return Response.json(data)
}

Keeping types in sync

Types drift when schema files change but types is not re-run. To prevent this:
1

Commit generated files to version control

Add database.types.ts and database.zod.ts to your repository. Reviewers can see exactly how a schema change affects the TypeScript surface area in the pull request diff.
2

Run types in CI after schema changes

Add a step to your CI workflow that regenerates types and fails if the output differs from what is committed:
- name: Check types are up to date
  run: |
    npx supaschema types
    git diff --exit-code database.types.ts database.zod.ts
If the files have diverged — because a developer edited schema files without re-running types — the git diff step exits non-zero and the PR is blocked.
3

Use the Husky pre-commit hook

Regenerate types automatically whenever a schema file is staged:
# .husky/pre-commit
staged=$(git diff --cached --name-only --diff-filter=ACM -- 'supabase/schemas/*.sql')
if [ -n "$staged" ]; then
  npx supaschema types
  git add database.types.ts database.zod.ts
fi
supaschema types is a pure read operation. It never connects to a database, never writes migration files, and never modifies your schema files. It is safe to run at any point in your workflow.

Reference: generated file contents by schema object

Schema objectdatabase.types.tsdatabase.zod.ts
TableRow, Insert, Update types*RowSchema, *InsertSchema, *UpdateSchema
ViewRow type (read-only)*RowSchema
Function (set-returning)Return row type*RowSchema
EnumUnion type under Enumsz.enum([...])
Composite typeInterfacez.object({...})