# How to Generate TypeScript Types from Supabase Schema

- Tool: Supabase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Supabase CLI v1.50+, @supabase/supabase-js v2+, TypeScript 4.5+
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
# Link to your remote project (one-time setup)
supabase link --project-ref your-project-ref

# Generate types from the remote database
supabase gen types typescript --linked > src/types/database.ts

# Or generate from your local database
supabase gen types typescript --local > src/types/database.ts
```

**Expected result:** A database.ts file is created with TypeScript interfaces for all your tables, views, and functions.

### 2. 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.

```
import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/database'

// Create a typed Supabase client
const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

// TypeScript now knows the shape of your tables
const { data: todos } = await supabase
  .from('todos')       // autocomplete on table names
  .select('id, title, is_complete, created_at')  // autocomplete on columns
  .eq('is_complete', false)  // type-checked filter value

// todos is typed as: { id: number; title: string; is_complete: boolean; created_at: string }[]

// Insert is type-checked
const { error } = await supabase
  .from('todos')
  .insert({ title: 'Buy groceries', is_complete: false })
  // TypeScript error if you pass invalid columns or wrong types
```

**Expected result:** Your IDE provides autocomplete for table names and columns, and TypeScript catches type errors at compile time.

### 3. 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.

```
import type { Database } from '@/types/database'

// Extract row types for a specific table
type Todo = Database['public']['Tables']['todos']['Row']
type TodoInsert = Database['public']['Tables']['todos']['Insert']
type TodoUpdate = Database['public']['Tables']['todos']['Update']

// Use in function signatures
async function createTodo(todo: TodoInsert): Promise<Todo | null> {
  const { data, error } = await supabase
    .from('todos')
    .insert(todo)
    .select()
    .single()

  if (error) throw error
  return data
}

// Use as component props
function TodoItem({ todo }: { todo: Todo }) {
  return <div>{todo.title}</div>
}
```

**Expected result:** You have reusable types for each table's rows, inserts, and updates that are always in sync with the database schema.

### 4. 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.

```
// package.json
{
  "scripts": {
    "generate-types": "supabase gen types typescript --linked > src/types/database.ts",
    "generate-types:local": "supabase gen types typescript --local > src/types/database.ts",
    "db:migrate": "supabase db push && npm run generate-types",
    "typecheck": "tsc --noEmit"
  }
}
```

**Expected result:** Running npm run generate-types regenerates the database types file, keeping types in sync with the schema.

### 5. 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'].

```
-- In your migration SQL
create type public.status as enum ('pending', 'active', 'completed', 'cancelled');

create table public.tasks (
  id uuid primary key default gen_random_uuid(),
  title text not null,
  status public.status default 'pending',
  created_at timestamptz default now()
);

alter 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 code example

File: `src/lib/supabase.ts`

```typescript
// src/lib/supabase.ts
// Typed Supabase client with helper types

import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/database'

// Create a typed Supabase client
export const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

// ==========================================
// Helper types extracted from generated types
// ==========================================

// Table row types (full row as returned by SELECT)
export type Todo = Database['public']['Tables']['todos']['Row']
export type Profile = Database['public']['Tables']['profiles']['Row']

// Insert types (required + optional columns)
export type TodoInsert = Database['public']['Tables']['todos']['Insert']
export type ProfileInsert = Database['public']['Tables']['profiles']['Insert']

// Update types (all columns optional for partial updates)
export type TodoUpdate = Database['public']['Tables']['todos']['Update']
export type ProfileUpdate = Database['public']['Tables']['profiles']['Update']

// Enum types (if any)
// export type Status = Database['public']['Enums']['status']

// ==========================================
// Type-safe query helpers
// ==========================================

export async function getTodos(userId: string): Promise<Todo[]> {
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .eq('user_id', userId)
    .order('created_at', { ascending: false })

  if (error) throw error
  return data
}

export async function createTodo(todo: TodoInsert): Promise<Todo> {
  const { data, error } = await supabase
    .from('todos')
    .insert(todo)
    .select()
    .single()

  if (error) throw error
  return data
}

export async function updateTodo(
  id: string,
  updates: TodoUpdate
): Promise<Todo> {
  const { data, error } = await supabase
    .from('todos')
    .update(updates)
    .eq('id', id)
    .select()
    .single()

  if (error) throw error
  return data
}
```

## Common mistakes

- **Forgetting to regenerate types after schema changes, causing type mismatches at runtime** — undefined Fix: Add a generate-types script to package.json and run it after every migration. Chain it: npm run db:migrate && npm run generate-types.
- **Using createClient without the Database generic parameter, losing all type safety** — undefined Fix: Always pass the Database type: createClient<Database>(url, key). Without it, all queries return 'any' types and you lose autocomplete and type checking.
- **Manually writing type definitions instead of generating them, leading to drift** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-generate-types-from-supabase-schema
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-generate-types-from-supabase-schema
