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
Generate types from your schema files
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). 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.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:
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.
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. 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 object | database.types.ts | database.zod.ts |
|---|
| Table | Row, Insert, Update types | *RowSchema, *InsertSchema, *UpdateSchema |
| View | Row type (read-only) | *RowSchema |
| Function (set-returning) | Return row type | *RowSchema |
| Enum | Union type under Enums | z.enum([...]) |
| Composite type | Interface | z.object({...}) |