To generate TypeScript types from your Supabase database schema, run supabase gen types typescript --linked > src/types/database.ts from the CLI. This introspects your database and outputs type definitions for all tables, views, and functions. Pass the generated Database type to createClient<Database>() for end-to-end type safety on queries, inserts, and updates. Regenerate types after every migration to keep them in sync.
Generating TypeScript Types from Your Supabase Database Schema
Supabase provides a CLI command that introspects your PostgreSQL database and generates TypeScript type definitions for every table, view, enum, and function. These types integrate directly with the Supabase JavaScript client, giving you autocomplete on column names, type checking on insert/update values, and compile-time error detection. This tutorial covers generating types from both local and remote databases, using them in your code, and automating regeneration.
Prerequisites
- Supabase CLI installed (brew install supabase/tap/supabase or npm install supabase --save-dev)
- A Supabase project with at least one table
- TypeScript configured in your project
- CLI linked to your project (supabase link --project-ref your-ref)
Step-by-step guide
Generate types from your remote database
Generate types from your remote database
Link your CLI to your remote Supabase project and run the type generation command. This connects to your hosted database, reads the schema, and outputs TypeScript interfaces for every table and view in the public schema. Redirect the output to a file in your project.
1# Link to your remote project (one-time setup)2supabase link --project-ref your-project-ref34# Generate types from the remote database5supabase gen types typescript --linked > src/types/database.ts67# Or generate from your local database8supabase gen types typescript --local > src/types/database.tsExpected result: A database.ts file is created with TypeScript interfaces for all your tables, views, and functions.
Use the generated types with the Supabase client
Use the generated types with the Supabase client
Import the generated Database type and pass it as a generic parameter to createClient. This enables full type inference on all Supabase operations: select() knows the column types, insert() validates the input shape, and update() only accepts valid column names and types. The IDE provides autocomplete for table names and column names.
1import { createClient } from '@supabase/supabase-js'2import type { Database } from '@/types/database'34// Create a typed Supabase client5const supabase = createClient<Database>(6 process.env.NEXT_PUBLIC_SUPABASE_URL!,7 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!8)910// TypeScript now knows the shape of your tables11const { data: todos } = await supabase12 .from('todos') // autocomplete on table names13 .select('id, title, is_complete, created_at') // autocomplete on columns14 .eq('is_complete', false) // type-checked filter value1516// todos is typed as: { id: number; title: string; is_complete: boolean; created_at: string }[]1718// Insert is type-checked19const { error } = await supabase20 .from('todos')21 .insert({ title: 'Buy groceries', is_complete: false })22 // TypeScript error if you pass invalid columns or wrong typesExpected result: Your IDE provides autocomplete for table names and columns, and TypeScript catches type errors at compile time.
Extract row types for use in components and functions
Extract row types for use in components and functions
The generated Database type contains nested types for each table's Row (full row), Insert (required and optional columns for insert), and Update (all columns optional for partial update). Extract these types using TypeScript's indexed access types so you can use them as function parameters and component props.
1import type { Database } from '@/types/database'23// Extract row types for a specific table4type Todo = Database['public']['Tables']['todos']['Row']5type TodoInsert = Database['public']['Tables']['todos']['Insert']6type TodoUpdate = Database['public']['Tables']['todos']['Update']78// Use in function signatures9async function createTodo(todo: TodoInsert): Promise<Todo | null> {10 const { data, error } = await supabase11 .from('todos')12 .insert(todo)13 .select()14 .single()1516 if (error) throw error17 return data18}1920// Use as component props21function TodoItem({ todo }: { todo: Todo }) {22 return <div>{todo.title}</div>23}Expected result: You have reusable types for each table's rows, inserts, and updates that are always in sync with the database schema.
Add a type generation script to package.json
Add a type generation script to package.json
Automate type generation by adding a script to your package.json. Run it after every migration or schema change to keep types in sync. You can also add it to your CI pipeline to verify types match the database before deploying.
1// package.json2{3 "scripts": {4 "generate-types": "supabase gen types typescript --linked > src/types/database.ts",5 "generate-types:local": "supabase gen types typescript --local > src/types/database.ts",6 "db:migrate": "supabase db push && npm run generate-types",7 "typecheck": "tsc --noEmit"8 }9}Expected result: Running npm run generate-types regenerates the database types file, keeping types in sync with the schema.
Handle enums and custom types
Handle enums and custom types
The type generator also picks up PostgreSQL enums and composite types. Enums are generated as TypeScript union types, and composite types are generated as interfaces. If you create a custom enum in SQL, it appears in the generated types under Database['public']['Enums'].
1-- In your migration SQL2create type public.status as enum ('pending', 'active', 'completed', 'cancelled');34create table public.tasks (5 id uuid primary key default gen_random_uuid(),6 title text not null,7 status public.status default 'pending',8 created_at timestamptz default now()9);1011alter table public.tasks enable row level security;Expected result: PostgreSQL enums are available as TypeScript union types, providing compile-time validation on status values.
Complete working example
1// src/lib/supabase.ts2// Typed Supabase client with helper types34import { createClient } from '@supabase/supabase-js'5import type { Database } from '@/types/database'67// Create a typed Supabase client8export const supabase = createClient<Database>(9 process.env.NEXT_PUBLIC_SUPABASE_URL!,10 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!11)1213// ==========================================14// Helper types extracted from generated types15// ==========================================1617// Table row types (full row as returned by SELECT)18export type Todo = Database['public']['Tables']['todos']['Row']19export type Profile = Database['public']['Tables']['profiles']['Row']2021// Insert types (required + optional columns)22export type TodoInsert = Database['public']['Tables']['todos']['Insert']23export type ProfileInsert = Database['public']['Tables']['profiles']['Insert']2425// Update types (all columns optional for partial updates)26export type TodoUpdate = Database['public']['Tables']['todos']['Update']27export type ProfileUpdate = Database['public']['Tables']['profiles']['Update']2829// Enum types (if any)30// export type Status = Database['public']['Enums']['status']3132// ==========================================33// Type-safe query helpers34// ==========================================3536export async function getTodos(userId: string): Promise<Todo[]> {37 const { data, error } = await supabase38 .from('todos')39 .select('*')40 .eq('user_id', userId)41 .order('created_at', { ascending: false })4243 if (error) throw error44 return data45}4647export async function createTodo(todo: TodoInsert): Promise<Todo> {48 const { data, error } = await supabase49 .from('todos')50 .insert(todo)51 .select()52 .single()5354 if (error) throw error55 return data56}5758export async function updateTodo(59 id: string,60 updates: TodoUpdate61): Promise<Todo> {62 const { data, error } = await supabase63 .from('todos')64 .update(updates)65 .eq('id', id)66 .select()67 .single()6869 if (error) throw error70 return data71}Common mistakes when generating TypeScript Types from Supabase Schema
Why it's a problem: Forgetting to regenerate types after schema changes, causing type mismatches at runtime
How to avoid: Add a generate-types script to package.json and run it after every migration. Chain it: npm run db:migrate && npm run generate-types.
Why it's a problem: Using createClient without the Database generic parameter, losing all type safety
How to avoid: Always pass the Database type: createClient<Database>(url, key). Without it, all queries return 'any' types and you lose autocomplete and type checking.
Why it's a problem: Manually writing type definitions instead of generating them, leading to drift
How to avoid: Always use supabase gen types typescript to generate types from the actual database schema. Manual types inevitably drift from the real schema after migrations.
Best practices
- Regenerate types after every migration to keep them in sync with the database schema
- Always pass the Database type to createClient<Database>() for full type inference
- Extract commonly used row types into a helpers file to avoid repeating long index paths
- Add a generate-types script to package.json and chain it with your migration workflow
- Use the Insert type for create operations and the Update type for partial updates
- Generate types from --local during development and --linked in CI for production verification
- Commit the generated types file to version control so the team always has the latest types
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to generate TypeScript types from my Supabase database so I get autocomplete and type checking on all my queries. Show me how to use supabase gen types typescript, integrate the types with createClient, and extract reusable row types.
Generate TypeScript types from my Supabase schema using the CLI. Create a typed Supabase client, extract Row/Insert/Update types for my todos and profiles tables, and add a package.json script that regenerates types after every migration.
Frequently asked questions
Do I need to regenerate types every time I change the database?
Yes. The generated types are a snapshot of your schema at the time of generation. After adding tables, columns, or changing types, run supabase gen types typescript again to update the types file.
Can I generate types for schemas other than public?
Yes, use the --schema flag: supabase gen types typescript --linked --schema public,storage. This generates types for multiple schemas in a single output file.
Do generated types include RLS policies?
No. The generated types reflect the database schema (tables, columns, types) but not the RLS policies. RLS is enforced at runtime by PostgreSQL, not at the TypeScript level.
How do I type the response of a query with joins?
When using select() with relations (e.g., select('*, profiles(*)')), the TypeScript types are automatically inferred based on the foreign key relationships in your schema. The generated types include relationship information.
Can I use the generated types with other ORMs like Prisma?
The Supabase-generated types are specific to the Supabase JavaScript client. Prisma generates its own types from its schema file. You can use both in the same project but they are separate type systems.
Should I commit the generated types file to version control?
Yes. Committing the types file ensures all team members have the same types without needing to regenerate locally. It also serves as documentation of the current database schema.
Can RapidDev help set up type-safe Supabase workflows?
Yes, RapidDev can configure type generation pipelines, create typed client helpers, integrate type checking into your CI, and build type-safe data access layers for your Supabase project.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation