Skip to content

Connection Pooling and Supavisor

Postgres has a hard limit on concurrent direct connections, so Supabase sits Supavisor in front of it as a connection pooler with two modes — transaction mode on port 6543 for the many short-lived clients serverless and edge environments create, and session mode on port 5432 for the few long-lived clients that need full Postgres feature parity.

Every Postgres connection costs memory and a backend process on the database server, so Postgres caps how many can be open at once — by default in the low hundreds, and Supabase’s managed Postgres is no different. That limit is easy to live with from a traditional long-running server that opens a handful of connections and reuses them for its whole lifetime.

Serverless and edge environments break that assumption. An Edge Function, a serverless API route, or any framework that spins up a new short-lived instance per request can each try to open their own direct connection to Postgres. Under real traffic that quickly adds up to far more concurrent connections than Postgres allows, and requests start failing with connection errors — not because the database is slow, but because it has simply run out of connection slots.

Supavisor: transaction mode vs session mode

Section titled “Supavisor: transaction mode vs session mode”

Supavisor is Supabase’s connection pooler. It sits between your application and Postgres, holds a smaller pool of real Postgres connections, and hands them out to many more logical clients than Postgres could serve directly. It has two modes, and the mode is selected by which port you connect to:

  • Transaction mode (port 6543) — a connection is only held for the duration of a single transaction, then returned to the pool immediately. This lets a small pool serve a very large number of short-lived clients, which is exactly the shape of serverless and Edge Function traffic. The trade-off: it does not support session-level Postgres features like prepared statements, since the underlying connection can be handed to a different client between transactions.
  • Session mode (port 5432) — a client gets a dedicated connection for the whole time it is connected, exactly like talking to Postgres directly. That gives full feature parity, including prepared statements, but each connected client consumes one slot in the pool for as long as it stays connected, so it does not scale to huge numbers of concurrent short-lived clients the way transaction mode does.
Terminal window
# Transaction mode — best for serverless functions, Edge Functions, and other short-lived clients
export DATABASE_URL="postgres://postgres.<project-ref>:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
# Session mode — for long-lived processes, or anything that specifically needs prepared statements
export DIRECT_URL="postgres://postgres.<project-ref>:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres"

Default to transaction mode for anything serverless or short-lived: Edge Functions, most application backends running on serverless platforms, and API routes that open a connection per request. It is the mode built for exactly that traffic shape.

Reach for session mode only when you specifically need session-level features — prepared statements, LISTEN/NOTIFY, or a long-lived server process (a traditional backend that holds a connection pool of its own, or a migration tool) that stays connected and benefits from a dedicated connection rather than a pooled one. If you are unsure which one your framework or ORM needs, check its docs for pooler compatibility before picking a mode — some ORMs need the session-mode URL for migrations and the transaction-mode URL for runtime queries.

flowchart LR
  subgraph short["Many short-lived clients"]
    e1["Edge Function"]
    e2["Serverless request"]
    e3["Serverless request"]
  end
  subgraph long["Few long-lived clients"]
    s1["Long-lived server process"]
    s2["Tool needing prepared statements"]
  end
  e1 --> tx["Supavisor - transaction mode, port 6543"]
  e2 --> tx
  e3 --> tx
  tx --> pool[("Small shared pool of Postgres connections")]
  s1 --> sess["Supavisor - session mode, port 5432"]
  s2 --> sess
  sess --> dedicated[("One dedicated Postgres connection per client")]
Transaction mode pools many short-lived clients; session mode dedicates one connection per client
What problem does Supavisor primarily solve?
Which port does Supavisor transaction mode use, and what does it not support?
Why does session mode not scale to huge numbers of concurrent short-lived clients the way transaction mode does?
Which mode should an Edge Function generally use, and why?