Last Updated: 3/20/2026
Supabase Integration
Supabase is an open-source Firebase alternative built on PostgreSQL. While Supabase provides its own JavaScript client (@supabase/supabase-js) that wraps a PostgREST API, you can use Kysely to write type-safe SQL queries directly against your Supabase PostgreSQL database.
Why Use Kysely with Supabase
Supabase’s JavaScript client is convenient for simple queries, but it has limitations:
- No raw SQL support: You can’t write custom SQL queries
- Limited query complexity: Complex joins, subqueries, and CTEs are difficult or impossible
- PostgREST constraints: The API layer adds overhead and restricts what you can do
Kysely gives you direct PostgreSQL access with full SQL capabilities while maintaining type safety. You can use Kysely alongside Supabase’s client, choosing the right tool for each query.
Prerequisites
Before integrating Kysely with Supabase, you need:
- Supabase project: Create a project at supabase.com or use the Supabase CLI for local development
- Supabase CLI: Install
supabaseCLI for type generation - Kysely: Install
kyselypackage - PostgreSQL driver: Install
pgorpostgres(withkysely-postgres-js) - kysely-supabase: Install the type translation package
npm install kysely pg
npm install -D kysely-supabaseGenerating Types with Supabase CLI
Supabase can generate TypeScript types from your database schema. Run this command to generate types:
npx supabase gen types typescript --local > src/database.types.tsFor a remote Supabase project, use:
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > src/database.types.tsThis creates a Database type that describes your database schema in Supabase’s format. The generated file includes types for tables, views, functions, and enums.
Translating Supabase Types to Kysely Types
Supabase’s generated types use a different structure than Kysely expects. The kysely-supabase package provides the KyselifyDatabase utility type to translate between formats:
// src/types.ts
import type { Database as SupabaseDatabase } from './database.types'
import type { KyselifyDatabase } from 'kysely-supabase'
export type Database = KyselifyDatabase<SupabaseDatabase>The KyselifyDatabase type transforms Supabase’s nested structure into Kysely’s flat table-to-columns mapping. It handles:
- Table types: Converts Supabase’s
Tablesstructure to Kysely’s table interface - Column types: Maps Supabase’s
Row,Insert, andUpdatetypes to Kysely’s column types - Nullable columns: Preserves nullability information
- Generated columns: Marks auto-generated columns appropriately
Connecting to Supabase
Use your Supabase connection string with Kysely’s PostgreSQL dialect:
// src/db.ts
import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
import type { Database } from './types'
export const db = new Kysely<Database>({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL,
}),
}),
})Get your connection string from the Supabase dashboard under Settings → Database → Connection string. Use the “Connection pooling” string for serverless environments.
Important: Supabase provides two connection strings:
- Direct connection: For long-lived connections (traditional servers)
- Connection pooling: For serverless functions and edge environments
Use the pooling connection string with Supabase’s connection pooler (port 6543) for serverless deployments.
Using postgres Driver
For serverless environments, the postgres driver is often more efficient than pg:
npm install postgres kysely-postgres-jsimport { Kysely } from 'kysely'
import { PostgresJSDialect } from 'kysely-postgres-js'
import postgres from 'postgres'
import type { Database } from './types'
export const db = new Kysely<Database>({
dialect: new PostgresJSDialect({
postgres: postgres(process.env.DATABASE_URL),
}),
})The postgres driver is designed for serverless environments and handles connection pooling more efficiently than pg.
Querying with Type Safety
Once configured, use Kysely normally. The types from Supabase ensure your queries are type-safe:
// Assuming your Supabase database has a 'person' table
const people = await db
.selectFrom('person')
.select(['id', 'first_name', 'last_name'])
.where('age', '>', 18)
.execute()
// TypeScript knows the shape of 'people'
people.forEach(person => {
console.log(person.first_name) // Type-safe
})Insert, update, and delete queries are also type-safe:
await db
.insertInto('person')
.values({
first_name: 'John',
last_name: 'Doe',
age: 30,
})
.execute()
await db
.updateTable('person')
.set({ age: 31 })
.where('id', '=', 1)
.execute()TypeScript validates that column names exist and that values have the correct types.
Working with Supabase Features
Kysely works with Supabase-specific PostgreSQL features:
Row Level Security (RLS): Kysely respects RLS policies. Set the user context before querying:
import { sql } from 'kysely'
await db.transaction().execute(async (trx) => {
// Set the user context for RLS
await sql`SELECT set_config('request.jwt.claims', '${sql.raw(JSON.stringify({ sub: userId }))}', true)`.execute(trx)
// Queries now respect RLS policies for this user
const data = await trx
.selectFrom('private_table')
.selectAll()
.execute()
return data
})Realtime subscriptions: Use Supabase’s client for realtime features. Kysely is for direct database queries, not realtime subscriptions.
Storage and Auth: Use Supabase’s client libraries for storage and authentication. Kysely is for database operations only.
Migrations
Supabase manages migrations through its CLI. Create migrations with:
npx supabase migration new create_person_tableEdit the generated SQL file in supabase/migrations/, then apply with:
npx supabase db pushAfter changing your schema, regenerate types:
npx supabase gen types typescript --local > src/database.types.tsYour Kysely queries will automatically pick up the new types.
Local Development
Use Supabase’s local development environment:
npx supabase startThis starts a local PostgreSQL instance, PostgREST API, and other Supabase services. Connect Kysely to the local database:
// Use the local connection string
const db = new Kysely<Database>({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: 'postgresql://postgres:postgres@localhost:54322/postgres',
}),
}),
})The local connection string is shown when you run supabase start.
Combining Kysely and Supabase Client
You can use both Kysely and Supabase’s client in the same application:
import { createClient } from '@supabase/supabase-js'
import { db } from './db'
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
)
// Use Supabase client for auth
const { data: { user } } = await supabase.auth.getUser()
// Use Kysely for complex queries
const result = await db
.selectFrom('person')
.innerJoin('pet', 'pet.owner_id', 'person.id')
.select(['person.first_name', 'pet.name as pet_name'])
.where('person.id', '=', user.id)
.execute()This pattern lets you use Supabase’s auth and realtime features while leveraging Kysely for complex database queries.
Type Generation Workflow
Establish a workflow for keeping types in sync:
- Make schema changes in Supabase (via migrations or dashboard)
- Apply migrations:
npx supabase db push - Regenerate types:
npx supabase gen types typescript --local > src/database.types.ts - TypeScript recompiles and catches any breaking changes in your Kysely queries
Add type generation to your CI/CD pipeline to ensure types stay synchronized with your database schema.
What’s Next
- Dialects: Learn about Kysely’s PostgreSQL dialect and other built-in dialects.
- Transactions: Use transactions to ensure data consistency in complex operations.
- Migrations: Explore advanced migration patterns and strategies.