# How to Use Supabase with Prisma

- Tool: Supabase
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Supabase (all plans), Prisma 5+, Node.js 18+
- Last updated: March 2026

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

## Before you start

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

```
# Connection pooler (for Prisma Client queries)
# Settings > Database > Connection string > Mode: Transaction
postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true

# Direct connection (for Prisma Migrate)
# Settings > Database > Connection string > Mode: Session
postgresql://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.

```
# Initialize Prisma
npx prisma init

# .env
DATABASE_URL="postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres"

# prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  String   @map("author_id")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  @@map("posts")
}
```

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

```
# Pull existing schema from Supabase
npx prisma db pull

# Generate the Prisma Client
npx prisma generate

# The schema.prisma file is now populated with models
# 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.

```
# Create and apply a migration
npx prisma migrate dev --name add_comments_table

# After the migration, add RLS policies via Supabase SQL Editor:
# alter table public.comments enable row level security;
# create policy "Users can read comments" on public.comments
#   for select to authenticated using (true);
# create policy "Users can insert own comments" on public.comments
#   for insert to authenticated
#   with check ((select auth.uid())::text = author_id);

# Deploy migrations to production
npx 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.

```
import { PrismaClient } from '@prisma/client'
import { createClient } from '@supabase/supabase-js'

const prisma = new PrismaClient()
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!  // Server-side only
)

// Use Prisma for complex queries
async function getPostsWithCommentCount() {
  return prisma.post.findMany({
    where: { published: true },
    include: { _count: { select: { comments: true } } },
    orderBy: { createdAt: 'desc' },
  })
}

// Use Supabase for auth operations
async function getUserFromToken(token: string) {
  const { data: { user }, error } = await supabase.auth.getUser(token)
  return user
}
```

**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 code example

File: `prisma/schema.prisma`

```prisma
// Prisma schema for Supabase PostgreSQL
// Connection pooler URL for queries, direct URL for migrations

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}

model Profile {
  id        String   @id @db.Uuid
  username  String?  @unique
  fullName  String?  @map("full_name")
  avatarUrl String?  @map("avatar_url")
  createdAt DateTime @default(now()) @map("created_at")
  posts     Post[]

  @@map("profiles")
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  String   @map("author_id") @db.Uuid
  author    Profile  @relation(fields: [authorId], references: [id], onDelete: Cascade)
  comments  Comment[]
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  @@index([authorId])
  @@map("posts")
}

model Comment {
  id        Int      @id @default(autoincrement())
  body      String
  postId    Int      @map("post_id")
  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  authorId  String   @map("author_id") @db.Uuid
  createdAt DateTime @default(now()) @map("created_at")

  @@index([postId])
  @@index([authorId])
  @@map("comments")
}
```

## Common mistakes

- **Using the direct connection URL for Prisma Client queries in serverless environments, exhausting database connections** — undefined Fix: 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.
- **Running Prisma migrations without adding RLS policies, leaving new tables exposed via the REST API** — undefined Fix: 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.
- **Using Prisma to manage the auth schema or storage schema, which can break Supabase's internal services** — undefined Fix: 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

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

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-prisma
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-prisma
