Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Use Supabase with Prisma

To use Supabase with Prisma, configure your Prisma schema to connect to Supabase's PostgreSQL database using the connection string from the Dashboard. Use the connection pooler URL (port 6543) for Prisma Client queries and the direct connection URL (port 5432) for migrations. Set the schema to prisma (not public) to avoid conflicts with Supabase's auto-generated API, and use prisma db pull to generate your schema from the existing database.

What you'll learn

  • How to configure Prisma to connect to Supabase's PostgreSQL database
  • How to handle connection pooling with Supavisor for Prisma Client
  • How to avoid schema conflicts between Prisma and Supabase's auto-generated API
  • How to run Prisma migrations alongside Supabase's existing schema
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read15-20 minSupabase (all plans), Prisma 5+, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

To use Supabase with Prisma, configure your Prisma schema to connect to Supabase's PostgreSQL database using the connection string from the Dashboard. Use the connection pooler URL (port 6543) for Prisma Client queries and the direct connection URL (port 5432) for migrations. Set the schema to prisma (not public) to avoid conflicts with Supabase's auto-generated API, and use prisma db pull to generate your schema from the existing database.

Connecting Prisma ORM to a Supabase PostgreSQL Database

Prisma is a popular TypeScript ORM that provides type-safe database queries, schema management, and migrations. Supabase's PostgreSQL database works with Prisma, but there are important configuration details: using the connection pooler for queries, the direct connection for migrations, and a separate schema to avoid conflicts with PostgREST. This tutorial covers the complete setup.

Prerequisites

  • A Supabase project with the database password available
  • Node.js 18+ installed
  • Prisma CLI installed (npm install prisma --save-dev)
  • Connection strings from Supabase Dashboard > Settings > Database

Step-by-step guide

1

Get the connection strings from the Supabase Dashboard

Supabase provides two connection endpoints: the connection pooler (port 6543) through Supavisor, and the direct connection (port 5432). The pooler is required for serverless environments and Prisma Client queries because it handles connection reuse. The direct connection is needed for Prisma migrations because the migration engine requires a non-pooled connection. Find both in Settings > Database > Connection string in the Supabase Dashboard.

typescript
1# Connection pooler (for Prisma Client queries)
2# Settings > Database > Connection string > Mode: Transaction
3postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true
4
5# Direct connection (for Prisma Migrate)
6# Settings > Database > Connection string > Mode: Session
7postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres

Expected result: You have both connection strings ready to add to your environment variables.

2

Initialize Prisma and configure the schema

Run npx prisma init to create the prisma/ directory with a schema.prisma file and a .env file. Configure the datasource to use the connection pooler URL for queries and the direct URL for migrations. Use the prisma schema (not public) to keep Prisma-managed tables separate from Supabase's auto-generated PostgREST API tables.

typescript
1# Initialize Prisma
2npx prisma init
3
4# .env
5DATABASE_URL="postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true"
6DIRECT_URL="postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres"
7
8# prisma/schema.prisma
9generator client {
10 provider = "prisma-client-js"
11}
12
13datasource db {
14 provider = "postgresql"
15 url = env("DATABASE_URL")
16 directUrl = env("DIRECT_URL")
17}
18
19model Post {
20 id Int @id @default(autoincrement())
21 title String
22 content String?
23 published Boolean @default(false)
24 authorId String @map("author_id")
25 createdAt DateTime @default(now()) @map("created_at")
26 updatedAt DateTime @updatedAt @map("updated_at")
27
28 @@map("posts")
29}

Expected result: Prisma is initialized with separate URLs for client queries (pooled) and migrations (direct).

3

Pull the existing Supabase schema into Prisma

If you already have tables in Supabase, use prisma db pull to introspect the database and generate Prisma models from the existing schema. This is the recommended starting point if you have been using Supabase's SQL Editor or Dashboard to manage your schema. After pulling, review the generated schema and adjust model names, relations, and mappings.

typescript
1# Pull existing schema from Supabase
2npx prisma db pull
3
4# Generate the Prisma Client
5npx prisma generate
6
7# The schema.prisma file is now populated with models
8# matching your existing Supabase tables

Expected result: Your prisma/schema.prisma file contains models matching the existing Supabase database tables. The Prisma Client is generated with type-safe query methods.

4

Run Prisma migrations without conflicting with Supabase

When creating new tables with Prisma, be aware that Prisma migrations modify the database schema directly. Tables created by Prisma in the public schema will automatically appear in Supabase's REST API. If you want to keep Prisma tables separate, use a dedicated schema. Also note that Prisma does not manage RLS — you need to add RLS policies separately using SQL or Supabase migrations.

typescript
1# Create and apply a migration
2npx prisma migrate dev --name add_comments_table
3
4# After the migration, add RLS policies via Supabase SQL Editor:
5# alter table public.comments enable row level security;
6# create policy "Users can read comments" on public.comments
7# for select to authenticated using (true);
8# create policy "Users can insert own comments" on public.comments
9# for insert to authenticated
10# with check ((select auth.uid())::text = author_id);
11
12# Deploy migrations to production
13npx prisma migrate deploy

Expected result: New tables are created by Prisma. RLS policies are added separately to ensure the tables are secure when accessed via Supabase's REST API.

5

Use Prisma Client alongside the Supabase JS client

You can use both Prisma Client and the Supabase JS client in the same project. Use Prisma for complex queries, transactions, and type-safe data access on the server. Use the Supabase JS client for auth, real-time subscriptions, storage, and client-side operations. This hybrid approach gives you the best of both tools.

typescript
1import { PrismaClient } from '@prisma/client'
2import { createClient } from '@supabase/supabase-js'
3
4const prisma = new PrismaClient()
5const supabase = createClient(
6 process.env.NEXT_PUBLIC_SUPABASE_URL!,
7 process.env.SUPABASE_SERVICE_ROLE_KEY! // Server-side only
8)
9
10// Use Prisma for complex queries
11async function getPostsWithCommentCount() {
12 return prisma.post.findMany({
13 where: { published: true },
14 include: { _count: { select: { comments: true } } },
15 orderBy: { createdAt: 'desc' },
16 })
17}
18
19// Use Supabase for auth operations
20async function getUserFromToken(token: string) {
21 const { data: { user }, error } = await supabase.auth.getUser(token)
22 return user
23}

Expected result: Both Prisma and Supabase clients work in the same project. Prisma handles server-side data access while Supabase handles auth and client-side operations.

Complete working example

prisma/schema.prisma
1// Prisma schema for Supabase PostgreSQL
2// Connection pooler URL for queries, direct URL for migrations
3
4generator client {
5 provider = "prisma-client-js"
6}
7
8datasource db {
9 provider = "postgresql"
10 url = env("DATABASE_URL")
11 directUrl = env("DIRECT_URL")
12}
13
14model Profile {
15 id String @id @db.Uuid
16 username String? @unique
17 fullName String? @map("full_name")
18 avatarUrl String? @map("avatar_url")
19 createdAt DateTime @default(now()) @map("created_at")
20 posts Post[]
21
22 @@map("profiles")
23}
24
25model Post {
26 id Int @id @default(autoincrement())
27 title String
28 content String?
29 published Boolean @default(false)
30 authorId String @map("author_id") @db.Uuid
31 author Profile @relation(fields: [authorId], references: [id], onDelete: Cascade)
32 comments Comment[]
33 createdAt DateTime @default(now()) @map("created_at")
34 updatedAt DateTime @updatedAt @map("updated_at")
35
36 @@index([authorId])
37 @@map("posts")
38}
39
40model Comment {
41 id Int @id @default(autoincrement())
42 body String
43 postId Int @map("post_id")
44 post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
45 authorId String @map("author_id") @db.Uuid
46 createdAt DateTime @default(now()) @map("created_at")
47
48 @@index([postId])
49 @@index([authorId])
50 @@map("comments")
51}

Common mistakes when using Supabase with Prisma

Why it's a problem: Using the direct connection URL for Prisma Client queries in serverless environments, exhausting database connections

How to avoid: Use the connection pooler URL (port 6543 with ?pgbouncer=true) for DATABASE_URL and the direct connection (port 5432) for DIRECT_URL. Prisma automatically uses DIRECT_URL for migrations.

Why it's a problem: Running Prisma migrations without adding RLS policies, leaving new tables exposed via the REST API

How to avoid: After every Prisma migration, add RLS policies to the new tables using the Supabase SQL Editor or a separate Supabase migration. Prisma does not manage RLS.

Why it's a problem: Using Prisma to manage the auth schema or storage schema, which can break Supabase's internal services

How to avoid: Only manage your application tables with Prisma. Never modify the auth, storage, or realtime schemas with Prisma migrations. Use Supabase's own tools for those.

Best practices

  • Use the connection pooler URL for Prisma Client and the direct URL for migrations to avoid connection exhaustion
  • Add ?pgbouncer=true to the pooler URL so Prisma correctly handles transaction-mode connection pooling
  • Use @map and @@map annotations to maintain snake_case in the database while using camelCase in TypeScript
  • Run prisma db pull before writing migrations if you have existing tables to avoid schema conflicts
  • Add RLS policies to every table created by Prisma migrations to secure the Supabase REST API
  • Keep Prisma Client usage on the server side only — use the Supabase JS client for browser operations
  • Add @@index annotations for columns used in WHERE clauses and RLS policies

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Supabase project and want to use Prisma for type-safe queries. Walk me through configuring Prisma with the connection pooler and direct URLs, pulling the existing schema, and running migrations without breaking Supabase's auto-generated API.

Supabase Prompt

Configure Prisma to connect to my Supabase database using the connection pooler for queries and direct connection for migrations. Create a Post model with id, title, content, published, author_id (UUID referencing profiles), and timestamps using snake_case column names.

Frequently asked questions

Can I use Prisma and Supabase migrations at the same time?

It is possible but not recommended. Using two migration systems on the same schema creates conflicts and drift. Choose one: use Prisma for application tables and Supabase migrations for RLS and auth-related changes, or use Supabase migrations for everything.

Why do I need two different connection URLs?

The connection pooler (port 6543) reuses connections efficiently for serverless environments but does not support the migration engine. The direct connection (port 5432) is required for Prisma Migrate because it needs a persistent, non-pooled connection to run DDL statements.

Does Prisma respect Supabase RLS policies?

No. Prisma Client connects with the postgres role by default, which bypasses RLS. To enforce RLS with Prisma, you would need to set the role per-query using raw SQL, which is complex. For RLS-protected access, use the Supabase JS client instead.

Will Prisma-created tables show up in the Supabase REST API?

Yes, any table in the public schema is automatically exposed via PostgREST. Ensure you add RLS policies to protect them. Alternatively, create Prisma tables in a separate schema to keep them out of the REST API.

How do I handle Prisma schema drift with Supabase Dashboard changes?

If someone changes the schema via the Dashboard, run prisma db pull to update your Prisma schema and then create a new migration to capture the changes. Avoid making ad-hoc Dashboard changes in a Prisma-managed project.

Can RapidDev help set up Prisma with my Supabase project?

Yes, RapidDev can configure the Prisma-Supabase integration, set up proper connection pooling, establish a migration workflow, and ensure RLS policies are in place for all Prisma-managed tables.

Should I use Prisma or the Supabase JS client for my project?

Use Prisma if you need complex queries, transactions, or a type-safe ORM on the server. Use the Supabase JS client for auth, real-time, storage, and client-side operations. Many projects use both: Prisma on the server and Supabase JS on the client.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.