supabase-typegen

π Advanced SQL-to-TypeScript type generator for Supabase projects with comprehensive schema support
Generate type-safe TypeScript definitions from your Supabase SQL migration files with support for tables, enums, functions, composite types, JSONB inference, indexes, and more.
β¨ Features
Core Type Generation
- β
Tables - Full support for Row, Insert, and Update types
- β
Enums - Type-safe enum definitions with runtime constants
- β
Functions - Function signatures with typed arguments and returns
- β
Composite Types - PostgreSQL composite type definitions
- β
Views - Read-only view types with intelligent type inference (including materialized views)
- β
Indexes - Index metadata (optional with
--include-indexes)
Advanced Features
- β
JSONB Type Inference - Automatically infer TypeScript types from JSONB defaults
- β
Nested Type Extraction - Deep type extraction for complex JSONB structures
- β
Geometric Types - Point, Line, Box, Circle, Polygon support
- β
Relationships - Foreign key relationships with one-to-one detection
- β
Comments - Preserve SQL comments as JSDoc
- β
Multi-Schema - Support for multiple database schemas
- β
Type Deduplication - Automatic removal of duplicate JSONB types
Developer Experience
- π¨ Naming Conventions -
preserve, PascalCase, camelCase, snake_case, SCREAMING_SNAKE_CASE
- π Prettier Integration - Respects your Prettier configuration
- π€ Alphabetical Sorting - Deterministic output ordering
- π― Smart Defaults - Sensible defaults, minimal configuration needed
- π¦ Runtime Constants - Enum values for dropdowns and validation
π¦ Installation
npm install supabase-typegen --save-dev
yarn add -D supabase-typegen
pnpm add -D supabase-typegen
π Quick Start
Basic Usage
npx supabase-typegen
npx supabase-typegen --local ./my-supabase
npx supabase-typegen --output ./src/types
Using Generated Types
import type {
Database,
Tables,
TablesInsert,
TablesUpdate,
Enums,
} from "./database";
type User = Tables<"users">;
type UserInsert = TablesInsert<"users">;
type UserUpdate = TablesUpdate<"users">;
type UserStatus = Enums<"user_status">;
type UserRow = Database["public"]["Tables"]["users"]["Row"];
import { createClient } from "@supabase/supabase-js";
const supabase = createClient<Database>(url, anonKey);
const { data } = await supabase
.from("users")
.select("*")
.eq("status", "active");
π Usage
Command Line Options
Source & Schema
npx supabase-typegen --local [workdir]
npx supabase-typegen --db --connection-string "postgresql://..."
npx supabase-typegen --schema public
Output & Formatting
npx supabase-typegen --output ./src/lib/types
npx supabase-typegen --naming camelCase
npx supabase-typegen --alphabetical
npx supabase-typegen --indent-size 4
npx supabase-typegen --use-prettier
Type Features
npx supabase-typegen --include-indexes
npx supabase-typegen --extract-nested
npx supabase-typegen --deduplicate
npx supabase-typegen --no-deduplicate
npx supabase-typegen --no-comments
Logging
npx supabase-typegen --silent
npx supabase-typegen --no-logs
npx supabase-typegen --quiet
Configuration File
The generator reads your supabase/config.toml automatically:
[db.migrations]
schema_paths = [
"migrations/*.sql",
"migrations/schemas/**/*.sql"
]
π― Type Generation Examples
Tables
Input SQL:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
status user_status DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT now()
);
COMMENT ON TABLE users IS 'Application users';
COMMENT ON COLUMN users.email IS 'User email address';
Generated Types:
users: {
Row: {
id: string
email: string
status: Database['public']['Enums']['user_status']
created_at: string
}
Insert: {
id?: string
email: string
status?: Database['public']['Enums']['user_status']
created_at?: string
}
Update: {
id?: string
email?: string
status?: Database['public']['Enums']['user_status']
created_at?: string
}
Relationships: []
}
Enums
Input SQL:
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'pending');
Generated Types:
Enums: {
user_status: "active" | "inactive" | "pending";
}
Constants: {
public: {
Enums: {
user_status: ["active", "inactive", "pending"];
}
}
}
Functions
Input SQL:
CREATE FUNCTION get_user_posts(user_id UUID, limit_count INT DEFAULT 10)
RETURNS TABLE (id UUID, title TEXT, created_at TIMESTAMPTZ)
LANGUAGE SQL;
Generated Types:
Functions: {
get_user_posts: {
Args: {
user_id: string
limit_count?: number
}
Returns: {
id: string
title: string
created_at: string
}[]
}
}
JSONB Types
Input SQL:
CREATE TABLE products (
id UUID PRIMARY KEY,
metadata JSONB DEFAULT '{"tags": [], "featured": false, "rating": 0}'::jsonb
);
Generated Types:
export type products_metadata = {
tags: unknown[];
featured: boolean;
rating: number;
};
products: {
Row: {
id: string;
metadata: products_metadata | null;
}
}
Views
Input SQL:
CREATE TABLE users (
id UUID PRIMARY KEY,
email TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE VIEW active_users AS
SELECT
id,
email,
created_at,
COUNT(*) OVER () as total_count
FROM users
WHERE deleted_at IS NULL;
Generated Types:
Views: {
active_users: {
Row: {
id: string;
email: string;
created_at: string;
total_count: number;
}
Relationships: [];
}
}
Relationships
Input SQL:
CREATE TABLE posts (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, id)
);
Generated Types:
posts: {
Row: { }
Insert: { }
Update: { }
Relationships: [
{
foreignKeyName: "posts_user_id_fkey"
columns: ["user_id"]
isOneToOne: true
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
Composite Types
Input SQL:
CREATE TYPE address AS (
street TEXT,
city TEXT,
zip TEXT
);
Generated Types:
CompositeTypes: {
address: {
street: string;
city: string;
zip: string;
}
}
π¨ Naming Conventions
The generator supports multiple naming conventions for generated types:
npx supabase-typegen --naming preserve
npx supabase-typegen --naming PascalCase
npx supabase-typegen --naming camelCase
npx supabase-typegen --naming snake_case
npx supabase-typegen --naming SCREAMING_SNAKE_CASE
π Advanced Features
JSONB Type Inference
The generator can automatically infer TypeScript types from JSONB default values:
settings JSONB DEFAULT '{"theme": "dark", "notifications": true}'::jsonb
export type table_settings = {
theme: string
notifications: boolean
}
metadata JSONB DEFAULT jsonb_build_object(
'user', jsonb_build_object('name', '', 'age', 0),
'preferences', jsonb_build_object('theme', 'light')
)
export type table_metadata_user = {
name: string
age: number
}
export type table_metadata_preferences = {
theme: string
}
export type table_metadata = {
user: table_metadata_user
preferences: table_metadata_preferences
}
Index Metadata
With --include-indexes, index information is included in the generated types:
users: {
Row: { }
Insert: { }
Update: { }
Relationships: []
Indexes: [
{
name: "users_email_idx"
columns: ["email"]
isUnique: true
method: "btree"
},
{
name: "users_created_at_idx"
columns: ["created_at"]
isUnique: false
method: "btree"
where: "deleted_at IS NULL"
}
]
}
View Type Inference
Views are automatically parsed with intelligent type inference for columns. The generator analyzes SELECT expressions to determine accurate types:
Input SQL:
CREATE TABLE orders (
id UUID PRIMARY KEY,
total NUMERIC NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE VIEW order_stats AS
SELECT
COUNT(*) as order_count,
SUM(total) as total_revenue,
AVG(total) as average_order,
MAX(created_at) as last_order_date,
ARRAY_AGG(id) as order_ids,
total::TEXT as total_text
FROM orders;
CREATE MATERIALIZED VIEW daily_sales AS
SELECT
DATE(created_at) as sale_date,
COUNT(*) as num_orders,
SUM(total) as daily_revenue
FROM orders
GROUP BY DATE(created_at);
Note on Materialized Views:
- Materialized views are read-only and do not support INSERT, UPDATE, or DELETE operations
- They must be refreshed using
REFRESH MATERIALIZED VIEW
- The generator only produces
Row and Relationships types for materialized views
Generated Types:
Views: {
order_stats: {
Row: {
order_count: number
total_revenue: number | null
average_order: number | null
last_order_date: string | null
order_ids: string[] | null
total_text: string | null
}
Relationships: []
}
daily_sales: {
Row: {
sale_date: string | null
num_orders: number
daily_revenue: number | null
}
Relationships: []
}
}
Type Inference Rules:
The generator intelligently infers types from common SQL patterns:
- Aggregate Functions:
COUNT() β bigint (maps to TypeScript number), SUM()/AVG() β numeric, MIN()/MAX() β unknown (type depends on input)
- Array Functions:
ARRAY_AGG() β array type, STRING_AGG() β text
- JSON Functions:
JSON_AGG()/JSONB_AGG() β json/jsonb
- Date/Time Functions:
NOW() β timestamp with time zone, CURRENT_DATE β date
- Type Casts:
column::type and CAST(column AS type) β specified type
- Column References: Direct column references inherit the source table's type
- Complex Expressions: When type cannot be inferred, defaults to
unknown
Geometric Types
PostgreSQL geometric types are automatically detected and typed:
CREATE TABLE locations (
id UUID PRIMARY KEY,
position POINT,
area POLYGON
);
export type Point = { x: number; y: number } | string;
export type Polygon = { points: Point[] } | string;
locations: {
Row: {
id: string;
position: Point | null;
area: Polygon | null;
}
}
Multi-Schema Support
Generate types for multiple schemas:
export interface Database {
public: {
Tables: {
};
Enums: {
};
};
auth: {
Tables: {
};
};
storage: {
Tables: {
};
};
}
type AuthUser = Database["auth"]["Tables"]["users"]["Row"];
ποΈ Project Structure
your-project/
βββ supabase/
β βββ config.toml
β βββ migrations/
β βββ 20240101000000_initial.sql
β βββ 20240102000000_add_users.sql
β βββ 20240103000000_add_posts.sql
βββ src/
β βββ lib/
β βββ types/
β βββ generated/
β βββ database.ts β Generated types
βββ package.json
π€ Integration Examples
With Supabase Client
import { createClient } from "@supabase/supabase-js";
import type { Database } from "./database";
const supabase = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
const { data: users } = await supabase
.from("users")
.select("id, email, status")
.eq("status", "active");
With React Hook Form + Zod
import { useForm } from "react-hook-form";
import type { TablesInsert } from "./database";
type UserInsert = TablesInsert<"users">;
function CreateUserForm() {
const { register, handleSubmit } = useForm<UserInsert>();
const onSubmit = async (data: UserInsert) => {
await supabase.from("users").insert(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} type="email" required />
<button type="submit">Create User</button>
</form>
);
}
With tRPC
import type { Database, TablesInsert } from "./database";
export const userRouter = router({
create: publicProcedure
.input(
z.object({
email: z.string().email(),
name: z.string(),
})
)
.mutation(
async ({
input,
}): Promise<Database["public"]["Tables"]["users"]["Row"]> => {
const { data } = await supabase
.from("users")
.insert(input)
.select()
.single();
return data;
}
),
});
π Helper Types
The generator provides several helper types for convenience:
type User = Tables<"users">;
type Post = Tables<"posts">;
type UserInsert = TablesInsert<"users">;
type PostInsert = TablesInsert<"posts">;
type UserUpdate = TablesUpdate<"users">;
type PostUpdate = TablesUpdate<"posts">;
type UserStatus = Enums<"user_status">;
type AuthUser = Tables<"users", { schema: "auth" }>;
type Address = CompositeTypes<"address">;
π― Best Practices
1. Version Control Generated Files
# Don't ignore generated types!
# src/lib/types/generated/database.ts
Commit generated types so team members get type safety without running the generator.
2. Regenerate After Migrations
Add to your migration workflow:
{
"scripts": {
"db:migrate": "supabase migration up && npm run types:generate",
"types:generate": "supabase-typegen"
}
}
3. Use Type-Safe Queries
const { data } = await supabase
.from("users")
.select("id, email, posts(title)")
.eq("status", "active");
const { data } = await supabase.from("users").select("*");
4. Leverage Enums
import { Constants } from "./database";
const statusOptions = Constants.public.Enums.user_status.map((status) => ({
value: status,
label: status.charAt(0).toUpperCase() + status.slice(1),
}));
π§ Configuration Options
CLI Flags Summary
--local [workdir] | Use local SQL files | ./supabase |
--workdir <path> | Explicit workdir path | - |
--db | Use database connection | false |
--connection-string <url> | Database connection URL | $DATABASE_URL |
--schema <name> | Target schema | public |
--output <dir> | Output directory | ./src/lib/types/generated |
--naming <convention> | Naming convention | preserve |
--alphabetical | Sort alphabetically | false |
--extract-nested | Extract nested JSONB types | false |
--deduplicate | Deduplicate types | true |
--no-deduplicate | Disable deduplication | - |
--indent-size <n> | Indentation (1-8) | 2 |
--use-prettier | Use Prettier config | false |
--include-indexes | Include index metadata | false |
--no-comments | Exclude SQL comments | false |
--silent | Disable logging | false |
π§ Roadmap
Coming Soon
Under Consideration
π Known Limitations
- Complex View Expressions: Some advanced SQL expressions in views may infer as
unknown type and need manual refinement
- Complex JSONB: Very deep nesting (5+ levels) may need manual type refinement
- Recursive Types: Self-referential types need manual handling
- Computed Columns: Generated/computed columns not yet supported
π‘ Tips & Tricks
Custom Type Overrides
Override generated types for special cases:
import type { Database as GeneratedDatabase } from "./generated/database";
export interface Database extends GeneratedDatabase {
public: GeneratedDatabase["public"] & {
Tables: GeneratedDatabase["public"]["Tables"] & {
users: {
Row: GeneratedDatabase["public"]["Tables"]["users"]["Row"] & {
fullName: string;
};
};
};
};
}
Monorepo Setup
{
"scripts": {
"types:generate:app1": "supabase-typegen --workdir ./apps/app1/supabase --output ./apps/app1/src/types",
"types:generate:app2": "supabase-typegen --workdir ./apps/app2/supabase --output ./apps/app2/src/types",
"types:generate:all": "npm run types:generate:app1 && npm run types:generate:app2"
}
}
CI/CD Integration
name: Check Types
on: [pull_request]
jobs:
check-types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run types:generate
- run: git diff --exit-code src/lib/types/generated/
name: Verify types are up to date
π€ Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
π License
MIT Β© chizaruu
π Acknowledgments
- Built for Supabase projects
- Inspired by the official
supabase gen types command
- Uses battle-tested SQL parsing techniques
π Support
Made with β€οΈ for the Supabase community