DEV Community

Cover image for Fix: Can't Migrate Schema Using Prisma With Supabase
Mahdi BEN RHOUMA
Mahdi BEN RHOUMA

Posted on Originally published at iloveblogs.blog

Fix: Can't Migrate Schema Using Prisma With Supabase

TL;DR

npx prisma migrate dev fails against Supabase with a Rust panic mentioning
"bouncer config error" because the connection string is pointed at the pooled
port (6543). Migrations need a direct, single Postgres session — switch the
connection to port 5432, or add a separate directUrl in schema.prisma so
your app can keep using the pooler for everything else.

The error

You add a model, run the first migration against a fresh Supabase project,
and instead of a schema diff you get a stack trace:

Error: db error: FATAL: bouncer config error
   0: migration_core::state::DevDiagnostic
             at migration-engine/core/src/state.rs:251
Enter fullscreen mode Exit fullscreen mode

A developer hit this exact error on Stack Overflow
after copying the connection string straight out of the Supabase dashboard
and running npx prisma migrate dev --name init for the first time on a
fresh project. The question includes a screenshot comparing two runs: the
Supabase attempt panics with the bouncer error above, while the exact same
schema.prisma migrated cleanly seconds earlier against a plain Railway
Postgres database, with no pooler in front of it. That side-by-side is the
clue — nothing in the Prisma schema itself is wrong, only what the
connection string routes through before it reaches Postgres.

The accepted answer, with 31 upvotes, is a single sentence: replace the port
number in the connection string from 6543 to 5432, because "6543 is the
pooled port number which should not be used when migrating, instead the
non-pooled connection string using 5432 should be used." That one-line fix
is correct, but it's worth understanding why a port number changes whether a
schema migration panics or succeeds.

Why it happens

Supabase puts every project's Postgres database behind
Supavisor,
a connection pooler, and exposes two ports for it:

  • 6543 — the transaction pooler. Each query borrows a connection from the pool and returns it as soon as the transaction commits. It's built for short-lived, high-concurrency connections — the kind a serverless function or an edge route opens.
  • 5432 — the direct connection to Postgres itself (or, for very large projects, a session-mode pooler that behaves like one).

The Supabase docs are explicit about which one migration tools need: direct
connections are for "migrations, pg_dump, backup and restore, or
replication," because those are "single sessions and Postgres native
commands." A transaction-mode pooler recycles the underlying connection
between statements, which breaks anything that depends on a stable session —
advisory locks and prepared statements are the two Supabase's own docs call
out. Supabase doesn't document the exact internal step where Prisma's
migration engine hits this limit — only that migrations need the single,
stable session a transaction-mode pooler doesn't provide, and that mismatch
is what surfaces as the bouncer config error panic.

This is not a Supabase outage or a Prisma bug — it's the expected result of
running a migration tool against a pooler built for application traffic.

Fix

1. Get both connection strings from Supabase

In your project, open Settings → Database → Connection string. Supabase
lists a Direct connection (port 5432) and a Transaction pooler
(port 6543). Copy both.

2. Point migrations at the direct connection

The fastest fix, and the one accepted on the original Stack Overflow thread,
is to swap the port in your existing connection string:

```bash title=".env"

was: ...pooler.supabase.com:6543/postgres

DATABASE_URL="postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres"






```bash
npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

That's enough to unblock a single developer or a one-off script.

3. Keep the pooler for runtime, and add directUrl for migrations

Once the app has real traffic, don't run every query through the direct
port — it has a much lower connection ceiling than the pooler. Keep the
pooled URL for the app and give Prisma a second URL it only reaches for for
migrations and introspection:

```prisma title="prisma/schema.prisma"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}






```bash title=".env"
DATABASE_URL="postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres"
Enter fullscreen mode Exit fullscreen mode

The pgbouncer=true query parameter on the pooled URL tells Prisma's query
engine to fall back to unnamed, uncached prepared statements for that
connection, since the transaction pooler can't guarantee a named one
survives between calls. Prisma reads
directUrl automatically for migrate dev, migrate deploy and
db push — no CLI flag needed.

If you're paste-checking a connection string right now, run it through the
tool below: it parses the host, port, and pooler flags and tells you which
mode you're actually pointed at, without sending the string anywhere.

Verify the fix

npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

A working migration prints a schema diff, applies it, and ends with:

Your database is now in sync with your schema.
Enter fullscreen mode Exit fullscreen mode

Instead of the earlier three-line freeze, you should see Prisma walk through
each step: loading the schema, generating the migration SQL, applying it,
and regenerating the Prisma Client. If it still panics with the same
bouncer config error, double-check the port in the string you just edited
— it's easy to copy the wrong one from a dashboard that lists both side by
side.

Once the migration completes, confirm nothing else broke by checking the
migration history:

npx prisma migrate status
Enter fullscreen mode Exit fullscreen mode

This should report that your database schema is up to date with your
migrations history, with no pending or failed migrations listed. Then
confirm the app still works against the pooled connection by running your
normal dev server and checking that a query round-trips — if directUrl is
set correctly you should see no change in query behaviour, only in what
migrate uses. The pooled connection handles your app's queries exactly as
before; only the migration command itself now takes the direct path.

When the shadow database is also blocked

Once migrations get past the pooler, some plans surface a second error the
same way:

Error: P3014
Prisma Migrate could not create the shadow database.
Enter fullscreen mode Exit fullscreen mode

That's a separate permission issue, and it's worth telling apart from the
pooler error above precisely because the fix looks similar but isn't: the
shadow database problem happens after you've already switched to the
direct connection, because Prisma spins up a temporary throwaway database
alongside your real one to validate that a migration applies cleanly before
touching production data, and on some managed Postgres providers — Heroku
Postgres is the documented example — the default role isn't granted the
privilege to create new databases, which is exactly what this step needs.
Switching the port again won't fix P3014 — it needs its own
setting, shadowDatabaseUrl, pointed at a database you pre-create by hand.
That workflow, and how to pre-create the shadow database through the
Supabase dashboard or psql, is covered in
Prisma Migrate P3014: permission denied to create database.
If instead migrate dev hangs silently with no error at all rather than
throwing the bouncer panic above, you're hitting a related but distinct
symptom of the same pooler-vs-migration mismatch — the transaction-mode
pooler dropping an advisory lock instead of refusing the connection outright
— walked through in
Prisma Migrate Dev Hangs? Fix the PgBouncer Stall.

If the connection fails before Prisma even reaches the pooler — a special
character in the password breaking how the URL parses, rather than the
pooler rejecting a valid connection — that's a different, more basic fix;
see
Prisma Can't Connect to PostgreSQL: Fix invalid port.

Getting the pooler split right once, in schema.prisma, is the kind of thing
worth doing before the app has real users — retrofitting directUrl under
a stalled deploy is a worse time to learn which port does what.


Originally published at https://www.iloveblogs.blog

Top comments (0)