Generating TypeScript types
Generate TypeScript types for a SupaHost project's database with the MCP server or the Supabase CLI over an SSH tunnel, and use them with supabase-js for typed queries.
supabase-js can type every query if you give it a description of your database: table names, column types, which columns are nullable or have defaults, relationships, functions and enums. That description is a generated database.types.ts file. With it, supabase.from('recipes').select('title') returns { title: string }[] instead of any, and a typo in a column name fails at compile time.
Ways to generate types on SupaHost
| Method | Plans | Notes |
|---|---|---|
MCP generate_typescript_types |
All | Your AI agent writes the file for you |
| Supabase CLI against the SSH tunnel | Business, Enterprise | Scriptable, good for CI |
| Supabase CLI against your local database | All | When you develop locally with the CLI |
The CLI's project-ref mode (--project-id, or --remote=<ref>) talks to Supabase's Management API, which SupaHost doesn't provide, so it won't work with a SupaHost project. See Supabase CLI commands that don't work with SupaHost.
With the MCP server
If you've connected Claude Code, Cursor or another client to the SupaHost MCP server, ask it directly:
Generate TypeScript types for this project and save them to
src/lib/database.types.ts.
The agent calls generate_typescript_types, which covers the public schema by default. It accepts a schemas list if you want more, for example ["public", "storage"]. Read-only tokens can use this tool. See The SupaHost MCP server.
A good habit is to ask for fresh types right after each apply_migration, so the code and the schema change together.
With the Supabase CLI (Business and Enterprise)
Open the SSH tunnel, then point the CLI at the tunnel's connection string. See Connecting to your database for the tunnel and where to find the string.
# terminal 1: keep the tunnel open
ssh -N -L 5432:127.0.0.1:5432 [email protected]
# terminal 2: Supabase CLI v3
npx supabase gen types typescript \
--remote="postgresql://postgres:<password>@127.0.0.1:5432/p_app_one" \
--schema public > src/lib/database.types.ts
On Supabase CLI v2 the flag is --db-url instead of --remote. Keep the connection string in an environment variable rather than in a script you commit.
From a local database
If you develop against a local stack started with supabase start, generate from that and apply the same migrations to SupaHost:
npx supabase gen types typescript > src/lib/database.types.ts # CLI v3
npx supabase gen types typescript --local > src/lib/database.types.ts # CLI v2
The types only match production if both databases have the same migrations. See Migrations workflow: local to SupaHost.
Using the types
Pass the Database type to createClient:
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
export const supabase = createClient<Database>(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
)
const { data } = await supabase
.from('recipes')
.select('id, title, servings')
.eq('servings', 4)
// data: { id: number; title: string; servings: number | null }[] | null
Inserts and updates are checked too: required columns without defaults must be present, and unknown columns are errors.
The generated file also exports helpers for row shapes, which are handy for component props and function signatures:
import type { Tables, TablesInsert, TablesUpdate, Enums } from './database.types'
type Recipe = Tables<'recipes'>
type NewRecipe = TablesInsert<'recipes'>
type RecipePatch = TablesUpdate<'recipes'>
type InvoiceStatus = Enums<'invoice_status'>
For queries with joins, let supabase-js infer the result type from the query itself:
import type { QueryData } from '@supabase/supabase-js'
const recipesWithAuthor = supabase.from('recipes').select('id, title, author:profiles ( display_name )')
type RecipesWithAuthor = QueryData<typeof recipesWithAuthor>
const { data } = await recipesWithAuthor
Function calls are typed from their signatures: supabase.rpc('quick_recipes', { max_minutes: 30 }) checks the argument names and returns the declared row shape.
Keeping types current
Types are a snapshot. After any schema change (a migration, a column added in Studio), regenerate them, or the compiler will trust a schema that no longer exists. Some teams commit the file and regenerate it in CI on Business; others have their AI agent regenerate it as part of each migration.
A few things the generator can't know:
- Views are typed, but every view column is marked nullable, because Postgres doesn't record view nullability.
jsonbcolumns are typed as a genericJson. Narrow them in your own code.- Columns filled by triggers look optional only if they also have a default.