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

Source: https://supahost.app/docs/api/generating-types · Updated 2026-11-11

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](/kb/cli-forbidden-or-unsupported).

## 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](/docs/ai-tools/mcp).

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](/docs/database/connecting) for the tunnel and where to find the string.

```bash
# terminal 1: keep the tunnel open
ssh -N -L 5432:127.0.0.1:5432 supahost@acme.supahost.space

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

```bash
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](/docs/local-development/migrations-workflow).

## Using the types

Pass the `Database` type to `createClient`:

```ts
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:

```ts
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:

```ts
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.
- `jsonb` columns are typed as a generic `Json`. Narrow them in your own code.
- Columns filled by triggers look optional only if they also have a default.

## Related

- [Querying and filtering data](/docs/api/querying)
- [Calling database functions (RPC)](/docs/api/rpc)
- [MCP tools reference](/docs/ai-tools/mcp-tools)
- [supabase-js TypeScript support](https://supabase.com/docs/reference/javascript/typescript-support)
