Type-Safe Supabase Development
TypeScript and Supabase work together seamlessly. The Supabase CLI can generate TypeScript types directly from your database schema, giving you autocomplete, compile-time checks, and confidence that your queries match your actual tables and columns.
🚀 Benefits of Typed Supabase
- Autocomplete: IDE suggestions for table names, columns, and filters
- Compile-time safety: Catch typos and schema mismatches before runtime
- Refactoring: Rename columns with confidence across your codebase
- Documentation: Types serve as living documentation of your schema
Generating Types from Your Database
# Install the Supabase CLI
npm install -D supabase
# Login to Supabase
npx supabase login
# Generate types from your remote database
npx supabase gen types typescript \
--project-id your-project-id \
--schema public \
> src/types/database.types.ts
# Or from a local database
npx supabase gen types typescript --local > src/types/database.types.ts
The Generated Database Type
// database.types.ts (auto-generated)
export type Database = {
public: {
Tables: {
posts: {
Row: {
id: string
title: string
content: string | null
user_id: string
created_at: string
}
Insert: {
id?: string
title: string
content?: string | null
user_id: string
created_at?: string
}
Update: {
id?: string
title?: string
content?: string | null
user_id?: string
created_at?: string
}
}
}
Functions: {
get_user_stats: {
Args: { user_id: string }
Returns: { post_count: number; comment_count: number }
}
}
}
}
Creating a Typed Supabase Client
import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/database.types'
// Pass the Database type to createClient
export const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// Now all queries are fully typed!
const { data } = await supabase
.from('posts') // autocomplete for table names
.select('id, title') // autocomplete for column names
.eq('user_id', userId) // type-checked filter values
// data is typed as Pick<Post, 'id' | 'title'>[]
Helper Types for Convenience
import type { Database } from '@/types/database.types'
// Shorthand types for your tables
type Tables = Database['public']['Tables']
type Post = Tables['posts']['Row']
type PostInsert = Tables['posts']['Insert']
type PostUpdate = Tables['posts']['Update']
// Use in your components and functions
async function createPost(post: PostInsert) {
const { data, error } = await supabase
.from('posts')
.insert(post)
.select()
.single()
return data // typed as Post
}
// Type-safe RPC calls
const { data } = await supabase.rpc('get_user_stats', {
user_id: 'some-uuid' // Args type is enforced
})
// data is typed as { post_count: number; comment_count: number }
⚠️ Keep Types in Sync
Re-generate types every time you change your database schema. Add npx supabase gen types
to your CI pipeline or create a script in package.json: "gen:types": "supabase gen types typescript --project-id $PROJECT_ID > src/types/database.types.ts"
💡 Key Takeaways
- • Generate types with
supabase gen types typescript - • Pass the
Databasetype tocreateClientfor full type safety - • Create helper types like
Post,PostInsert,PostUpdate - • Re-generate types whenever your schema changes
- • Typed RPC calls catch argument mismatches at compile time
📚 Learn More
-
Generating TypeScript Types →
Official guide to generating and using TypeScript types.