The Supabase error about the maximum number of clients being reached means you have hit your connection pooler's client limit, almost always because too many database connections are open. The fix is to use the connection pooler correctly: switch to transaction mode, on port 6543, instead of session mode, on port 5432, for serverless or many short-lived connections, because session mode caps clients at your configured pool size while transaction mode reuses connections and supports far more. Just as important, reuse a single client instead of opening a new connection for every request or function call, which is the pattern that exhausts the pool. If you genuinely need more headroom, raise the pool size or max client connections in the dashboard, or move to a larger instance.
Short answer
This is a connection-handling problem, not a data or security one, so the fix is about how your app connects. Per Supabase's connecting to Postgres guide, the pooler offers transaction mode on port 6543 and session mode on port 5432, and transaction mode supports far more clients because it reuses connections, which is why it suits serverless. Per Supabase's connection management docs, in session mode clients are capped at your pool size, so exceeding it produces the max-clients error. So use the transaction-mode pooler for app traffic, reuse a single client, and raise the pool size only if you truly need to.
What the error means
Supabase puts a connection pooler in front of your Postgres database, and this error comes from that pooler telling you it has run out of client slots. Postgres itself can only handle a limited number of direct connections, so the pooler, currently Supavisor and previously PgBouncer, multiplexes many client connections onto fewer database connections. When the number of clients trying to use the pool exceeds its limit, you get a message that the maximum number of clients has been reached, often phrased as max clients reached in session mode.
So the error is about connection capacity, not about your data, your keys, or your access rules. Nothing is broken in your schema or your security; you are simply opening more connections than the pool is configured to serve at once. That framing points you at how and where your app connects, which pooler mode it uses, and whether it reuses connections, rather than at anything in your database content. The rest of the fix follows from understanding the pooler's two modes.
Session mode versus transaction mode
The pooler has two modes, and choosing the right one is the core of the fix. In session mode, reached on port 5432 through the pooler, each client holds onto a database connection for its whole session, so the number of clients you can serve is capped at your pool size; if the pool size is fifteen, the fifteenth client is the last one served and the next hits the max-clients error. In transaction mode, reached on port 6543, a connection is handed back to the pool after each transaction and reused by the next client, so the same few database connections serve far more clients.
This is why transaction mode is the right choice for serverless and high-connection workloads. Serverless functions on platforms like Edge Functions, Vercel, or AWS Lambda each try to connect, and there can be many at once, so session mode's per-client hold exhausts the pool quickly, while transaction mode's reuse handles the load. So if you are hitting the max-clients error from serverless or from many short-lived connections, moving from the session-mode port to the transaction-mode port is usually the single most effective change.
Why AI-generated apps hit this
Apps generated quickly, including AI-built ones, tend to hit this error because they open connections without managing them. A common pattern is creating a new database client or connection on every request or every function invocation instead of creating one and reusing it, so a burst of traffic opens a burst of connections and the pool fills. Generated code often does the simplest thing that works in a demo, which is a fresh connection each time, and that does not survive real concurrency.
The other common cause is connecting directly to Postgres or using session mode from a serverless environment, where each invocation grabs and holds a connection. Neither is a flaw in Supabase; both are connection-handling habits that scale poorly. So if your app started throwing this error as traffic grew, look at whether it creates connections per request and which pooler mode it uses, because that is almost always where the exhaustion comes from, and it is fixable without changing your data or your platform.
Fix: use transaction mode and the pooler
The first fix is to use the transaction-mode pooler connection string, on port 6543, for your application traffic, especially anything serverless. This gives you the connection reuse that lets a small number of database connections serve many clients, which directly addresses the max-clients limit you are hitting in session mode. Point your app or your serverless functions at the transaction-mode pooler rather than at a direct database connection or the session-mode port.
Also make sure you are using the pooler at all rather than connecting straight to Postgres. A direct connection to the database uses one of a small number of raw Postgres connection slots, which you exhaust quickly under load, whereas the pooler exists precisely to sit between your app and the database and manage those slots. So for normal application traffic, the pooler in transaction mode is the intended path, and reserving direct connections for migrations or admin tasks keeps your limited raw connections free.
Fix: reuse the client and raise the pool size
The second fix is in your code: create the Supabase client or database connection once and reuse it, rather than instantiating a new one per request. Reusing a single client means your app is not constantly opening and holding new connections, which is often enough on its own to stop the pool from filling. In a serverless function, initialize the client outside the handler so it is reused across invocations where the platform allows, rather than inside on every call.
If, after using transaction mode and reusing connections, you still legitimately need more capacity, you can raise the limits. Supabase lets you set the pool size and the max client connections in your database settings, so increasing those gives the pooler more room, and moving to a larger instance raises the underlying Postgres connection limit. So the order is to fix the mode and the reuse first, which resolves most cases for free, and only raise the limits or upgrade if your genuine concurrency requires it.
Causes and fixes at a glance
Matching the cause to its fix makes this quick. The table below maps the common ones.
| Cause | Why the pool fills | Fix |
|---|---|---|
| Session mode from serverless | Each client holds a connection | Use transaction mode, port 6543 |
| New client per request | Bursts of connections open at once | Create one client and reuse it |
| Direct connection under load | Few raw Postgres slots exhausted | Connect through the pooler |
| Pool size too small | Capped at the configured size | Raise pool size or max client connections |
| Genuine high concurrency | More load than the instance allows | Upgrade to a larger instance |
Read the first two rows first, since session mode from serverless and creating a client per request cause most max-clients errors.
Direct connection versus the pooler
It helps to know when to use each connection type, because mixing them up is a common source of this error. A direct connection goes straight to Postgres and uses one of its limited raw connection slots, which is fine for a migration, a one-off admin task, or a long-lived server that holds a single connection, but poor for many short-lived or serverless connections. The pooler, by contrast, is built to serve application traffic by sharing a small set of database connections among many clients.
So the rule of thumb is to use the pooler, in transaction mode, for your app and serverless functions, and reserve direct connections for tooling that genuinely needs a persistent, direct link. If your connection string points at the direct database port for normal traffic, switching it to the transaction-mode pooler is frequently the fix.
Fix checklist
Working through these steps clears the max-clients error. The checklist below covers them.
| Step | Action | Done? |
|---|---|---|
| Use the pooler | Connect through the pooler, not directly | [ ] |
| Choose transaction mode | Use port 6543 for serverless and app traffic | [ ] |
| Reuse the client | Create one client and reuse it, not per request | [ ] |
| Initialize outside handlers | In serverless, reuse across invocations | [ ] |
| Raise limits if needed | Increase pool size or max client connections | [ ] |
| Upgrade if genuinely needed | Move to a larger instance for real concurrency | [ ] |
The step teams skip most is reusing the client, since creating a new connection per request is the habit that fills the pool regardless of configuration.
Where a scan fits
This is a database and connection-handling issue rather than a security one, so a security tool does not resolve it, but the same app is worth checking for the security problems that AI-generated code more commonly ships.
A scanner like PTKD.com analyzes your app build and flags security issues such as an exposed service role key, access relied on from the client without server-side rules, and other misconfigurations, mapped to OWASP MASVS. To be clear about the boundary: PTKD does not tune your connection pool or change your database settings, which are your Supabase configuration. It is useful because an app that mishandles connections often mishandles secrets too, so once the pooling is fixed, a scan catches the security gaps that a connection error would have hidden.
What to take away
- The max-clients error means you have exceeded your connection pooler's client limit, not that anything is wrong with your data or security.
- Session mode caps clients at your pool size, while transaction mode on port 6543 reuses connections and serves far more, which is why it suits serverless.
- AI-generated apps commonly hit this by creating a new connection per request or using session mode from serverless, so reuse a single client and switch to transaction mode.
- Use the pooler for application traffic and reserve direct connections for migrations and admin tasks that need a persistent link.
- Raise the pool size or upgrade the instance only after fixing the mode and the reuse, and use a tool like PTKD.com to check the same app for security gaps.



