From 8c7a4d9dbbaf8b552893822e89d7bf06f33f9220 Mon Sep 17 00:00:00 2001
From: "Andrey A." <56412611+aantti@users.noreply.github.com>
Date: Wed, 9 Sep 2026 15:45:54 +0200
Subject: [PATCH 001/614] chore(self-hosted): update 2026-09-09 - 0.8.1
(#50172)
---
.../content/guides/self-hosting/docker.mdx | 8 +--
docker/CHANGELOG.md | 59 +++++++++++++++++--
docker/docker-compose.logs.yml | 2 +-
docker/docker-compose.nginx.yml | 2 +-
docker/docker-compose.rustfs.yml | 2 +-
docker/docker-compose.yml | 20 +++----
docker/versions.md | 13 ++++
7 files changed, 85 insertions(+), 21 deletions(-)
diff --git a/apps/docs/content/guides/self-hosting/docker.mdx b/apps/docs/content/guides/self-hosting/docker.mdx
index 0573d84fed6..94ceab3be31 100644
--- a/apps/docs/content/guides/self-hosting/docker.mdx
+++ b/apps/docs/content/guides/self-hosting/docker.mdx
@@ -122,7 +122,7 @@ A shallow clone of the full Supabase repository. Works on any OS with `git` inst
```sh
# Get the code
-git clone --depth 1 --branch self-hosted/v0.8.0 https://github.com/supabase/supabase
+git clone --depth 1 --branch self-hosted/v0.8.1 https://github.com/supabase/supabase
# Make your new supabase project directory
mkdir supabase-project
@@ -139,7 +139,7 @@ cp -rf supabase/docker/. supabase-project
cd supabase-project && cp .env.example .env
# Record the base version so update.sh can upgrade this install later
-printf 'ref=self-hosted/v0.8.0\n' > .supabase-version
+printf 'ref=self-hosted/v0.8.1\n' > .supabase-version
# Pull the latest images
docker compose pull
@@ -153,7 +153,7 @@ Only downloads the `docker/` directory from the repository, saving bandwidth and
```sh
# Get the code using git sparse checkout
-git clone --filter=blob:none --no-checkout --depth=1 --quiet --branch self-hosted/v0.8.0 https://github.com/supabase/supabase
+git clone --filter=blob:none --no-checkout --depth=1 --quiet --branch self-hosted/v0.8.1 https://github.com/supabase/supabase
cd supabase
git sparse-checkout init --cone
git sparse-checkout set docker
@@ -175,7 +175,7 @@ cp -rf supabase/docker/. supabase-project
cd supabase-project && cp .env.example .env
# Record the base version so update.sh can upgrade this install later
-printf 'ref=self-hosted/v0.8.0\n' > .supabase-version
+printf 'ref=self-hosted/v0.8.1\n' > .supabase-version
# Pull the latest images
docker compose pull
diff --git a/docker/CHANGELOG.md b/docker/CHANGELOG.md
index cefc0552a63..961173b0d0c 100644
--- a/docker/CHANGELOG.md
+++ b/docker/CHANGELOG.md
@@ -10,6 +10,57 @@ See per-service updates below for details. Only the most important changes relev
---
+## [0.8.1](https://github.com/supabase/supabase/releases/tag/self-hosted/v0.8.1) - 2026-09-09
+
+### Configuration
+- Added an optional [PgBouncer](https://www.pgbouncer.org/) override (requires `docker-compose.pgbouncer.yml`) as an alternative to the default Supavisor pooler - PR [#49052](https://github.com/supabase/supabase/pull/49052) (via [@singh-inder](https://github.com/singh-inder/))
+
+### Documentation
+- Added a new guide [Accessing Postgres](https://supabase.com/docs/guides/self-hosting/accessing-postgres) - PR [#49303](https://github.com/supabase/supabase/pull/49303)
+- Added new how-to guides (configuring [auth hooks](https://supabase.com/docs/guides/self-hosting/self-hosted-auth-hooks), [passkeys](https://supabase.com/docs/guides/self-hosting/self-hosted-passkeys)) - PR [#43372](https://github.com/supabase/supabase/pull/43372), PR [#48954](https://github.com/supabase/supabase/pull/48954) (via [@singh-inder](https://github.com/singh-inder/))
+
+### API gateway
+- Updated Envoy to `v1.39.1` - [Release](https://github.com/envoyproxy/envoy/releases/tag/v1.39.1)
+- Changed CORS configuration for `/pg` route (requires `docker-compose.yml`, `volumes/api/envoy/docker-entrypoint.sh`, `volumes/api/envoy/lds.template.yaml` update) - PR [#49136](https://github.com/supabase/supabase/pull/49136)
+- Updated [nginx-certbot](https://github.com/JonasAlfredsson/docker-nginx-certbot) to `6.2.0-nginx1.31.5` (requires `docker-compose.nginx.yml` update)
+
+### Studio
+- Updated to `2026.09.07-sha-7996410`
+
+### MCP Server
+- Updated to `v0.11.0` - [Release](https://github.com/supabase/mcp/releases/tag/mcp-server-supabase-v0.11.0)
+
+### Auth
+- Updated to `v2.196.0` - [Changelog](https://github.com/supabase/auth/blob/master/CHANGELOG.md) | [Release](https://github.com/supabase/auth/releases/tag/v2.196.0)
+
+### PostgREST
+- Updated to `v14.17` - [Changelog](https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md) | [Release](https://github.com/PostgREST/postgrest/releases/tag/v14.17)
+
+### Realtime
+- Updated to `v2.134.10` - [Release](https://github.com/supabase/realtime/releases/tag/v2.134.10)
+
+### Storage
+- Updated to `v1.74.0` - [Release](https://github.com/supabase/storage/releases/tag/v1.74.0)
+- Updated RustFS to `v1.0.0-rc.5` (requires `docker-compose.rustfs.yml` update)
+
+### imgproxy
+- Updated to `v3.31.4` - [Changelog](https://github.com/imgproxy/imgproxy/blob/master/CHANGELOG.md) | [Release](https://github.com/imgproxy/imgproxy/releases/tag/v3.31.4)
+
+### Postgres Meta
+- Updated to `v0.99.0` - [Release](https://github.com/supabase/postgres-meta/releases/tag/v0.99.0)
+
+### Edge Runtime
+- Updated to `v1.76.2` - [Release](https://github.com/supabase/edge-runtime/releases/tag/v1.76.2)
+- Changed the main worker to use `@supabase/server` (requires `volumes/functions/deno.jsonc` and `volumes/functions/main/index.ts` update) - PR [#48996](https://github.com/supabase/supabase/pull/48996)
+
+### Supavisor
+- Updated to `2.9.12` - [Release](https://github.com/supabase/supavisor/releases/tag/v2.9.12)
+
+### Analytics (Logflare)
+- Updated to `1.50.10` - [Release](https://github.com/Logflare/logflare/releases/tag/v1.50.10)
+
+---
+
## [0.8.0](https://github.com/supabase/supabase/releases/tag/self-hosted/v0.8.0) - 2026-08-11
⚠️ **Note:** This update contains **breaking changes**. Make sure to read the **important** details below:
@@ -60,7 +111,7 @@ See per-service updates below for details. Only the most important changes relev
### API gateway
- Updated Kong to `3.9.3`
- Added `KONG_DNS_VALID_TTL` configuration environment variable (requires `docker-compose.yml` update) - PR [#47846](https://github.com/supabase/supabase/pull/47846)
-- Updated Envoy to `1.39.0` (requires `docker-compose.envoy.yml` update)
+- Updated Envoy to `v1.39.0` (requires `docker-compose.envoy.yml` update) - [Release](https://github.com/envoyproxy/envoy/releases/tag/v1.39.0)
- Updated [nginx-certbot](https://github.com/JonasAlfredsson/docker-nginx-certbot) to `6.2.0-nginx1.31.3` (requires `docker-compose.nginx.yml` update)
### Studio
@@ -69,7 +120,7 @@ See per-service updates below for details. Only the most important changes relev
- Fixed the Logs tab visibility in **Auth > Users** - PR [#48122](https://github.com/supabase/supabase/pull/48122) (via [@luizfelmach](https://github.com/luizfelmach/))
### Storage
-- Changed RustFS image to `1.0.0-beta.11` temporarily (requires `docker-compose.rustfs.yml` update) - PR [#48500](https://github.com/supabase/supabase/pull/48500)
+- Changed RustFS image to `v1.0.0-beta.11` temporarily (requires `docker-compose.rustfs.yml` update) - PR [#48500](https://github.com/supabase/supabase/pull/48500)
### Edge Runtime
- Changed JWKS configuration mechanism for main worker (requires `docker-compose.yml` and `volumes/functions/main/index.ts` update) - PR [#45635](https://github.com/supabase/supabase/pull/45635)
@@ -176,8 +227,8 @@ See per-service updates below for details. Only the most important changes relev
- Updated `tests/test-container-logs.sh` to skip checks for `kong`, `analytics` and `vector` when the services are not running - PR [#46099](https://github.com/supabase/supabase/pull/46099)
### API gateway
-- Updated Envoy version to `1.38.0` (see `docker-compose.envoy.yml`) - PR [#46023](https://github.com/supabase/supabase/pull/46023)
-- Updated Envoy configuration to address a discrepancy in API key checking (requires `volumes/api/envoy` update) - PR [#46023](https://github.com/supabase/supabase/pull/46023)
+- Updated Envoy to `v1.38.0` (requires `docker-compose.envoy.yml` update) - [Release](https://github.com/envoyproxy/envoy/releases/tag/v1.38.0)
+- Changed Envoy configuration to address a discrepancy in API key checking (requires `volumes/api/envoy` update) - PR [#46023](https://github.com/supabase/supabase/pull/46023)
### Studio
- Updated to `2026.06.03-sha-0bca601`
diff --git a/docker/docker-compose.logs.yml b/docker/docker-compose.logs.yml
index 2249a72f87e..ef31af03051 100644
--- a/docker/docker-compose.logs.yml
+++ b/docker/docker-compose.logs.yml
@@ -18,7 +18,7 @@ services:
analytics:
container_name: supabase-analytics
- image: supabase/logflare:1.43.1
+ image: supabase/logflare:1.50.10
restart: unless-stopped
#ports:
# - 4000:4000
diff --git a/docker/docker-compose.nginx.yml b/docker/docker-compose.nginx.yml
index 0414b3ade28..5e6fb54d233 100644
--- a/docker/docker-compose.nginx.yml
+++ b/docker/docker-compose.nginx.yml
@@ -11,7 +11,7 @@ services:
nginx:
container_name: supabase-nginx
- image: jonasal/nginx-certbot:6.2.0-nginx1.31.3
+ image: jonasal/nginx-certbot:6.2.0-nginx1.31.5
restart: unless-stopped
ports:
- "80:80"
diff --git a/docker/docker-compose.rustfs.yml b/docker/docker-compose.rustfs.yml
index 65026567b10..0f6552b70ff 100644
--- a/docker/docker-compose.rustfs.yml
+++ b/docker/docker-compose.rustfs.yml
@@ -1,7 +1,7 @@
services:
rustfs:
- image: rustfs/rustfs:1.0.0-beta.11
+ image: rustfs/rustfs:v1.0.0-rc.5
environment:
RUSTFS_ACCESS_KEY: ${MINIO_ROOT_USER}
RUSTFS_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index d05807667aa..ce94117ffd4 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -14,7 +14,7 @@ services:
studio:
container_name: supabase-studio
- image: supabase/studio:2026.08.03-sha-022b374
+ image: supabase/studio:2026.09.07-sha-7996410
restart: unless-stopped
healthcheck:
test:
@@ -69,7 +69,7 @@ services:
# See: https://github.com/orgs/supabase/discussions/48048
api-gw:
container_name: supabase-envoy
- image: envoyproxy/envoy:v1.39.0
+ image: envoyproxy/envoy:v1.39.1
restart: unless-stopped
networks:
default:
@@ -108,7 +108,7 @@ services:
auth:
container_name: supabase-auth
- image: supabase/gotrue:v2.189.0
+ image: supabase/gotrue:v2.196.0
restart: unless-stopped
healthcheck:
test:
@@ -248,7 +248,7 @@ services:
rest:
container_name: supabase-rest
- image: postgrest/postgrest:v14.12
+ image: postgrest/postgrest:v14.17
restart: unless-stopped
depends_on:
db:
@@ -285,7 +285,7 @@ services:
realtime:
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
container_name: realtime-dev.supabase-realtime
- image: supabase/realtime:v2.102.3
+ image: supabase/realtime:v2.134.10
restart: unless-stopped
depends_on:
db:
@@ -331,7 +331,7 @@ services:
# To use S3 backed storage: docker compose -f docker-compose.yml -f docker-compose.s3.yml up
storage:
container_name: supabase-storage
- image: supabase/storage-api:v1.60.4
+ image: supabase/storage-api:v1.74.0
restart: unless-stopped
depends_on:
db:
@@ -394,7 +394,7 @@ services:
imgproxy:
container_name: supabase-imgproxy
- image: darthsim/imgproxy:v3.30.1
+ image: darthsim/imgproxy:v3.31.4
restart: unless-stopped
volumes:
- ./volumes/storage:/var/lib/storage:z
@@ -417,7 +417,7 @@ services:
meta:
container_name: supabase-meta
- image: supabase/postgres-meta:v0.96.6
+ image: supabase/postgres-meta:v0.99.0
restart: unless-stopped
depends_on:
db:
@@ -434,7 +434,7 @@ services:
functions:
container_name: supabase-edge-functions
- image: supabase/edge-runtime:v1.74.0
+ image: supabase/edge-runtime:v1.76.2
restart: unless-stopped
volumes:
- ./volumes/functions:/home/deno/functions:z
@@ -531,7 +531,7 @@ services:
# Update the DATABASE_URL if you are using an external Postgres database
supavisor:
container_name: supabase-pooler
- image: supabase/supavisor:2.9.5
+ image: supabase/supavisor:2.9.12
restart: unless-stopped
ports:
- ${POSTGRES_PORT}:5432
diff --git a/docker/versions.md b/docker/versions.md
index 36792c99c09..6eb4e17fe2d 100644
--- a/docker/versions.md
+++ b/docker/versions.md
@@ -1,5 +1,18 @@
# Docker image version updates in docker-compose.yml
+## 2026-09-09
+- supabase/studio:2026.09.07-sha-7996410 (prev supabase/studio:2026.08.03-sha-022b374)
+- envoyproxy/envoy:v1.39.1 (prev envoyproxy/envoy:v1.39.0)
+- supabase/gotrue:v2.196.0 (prev supabase/gotrue:v2.189.0)
+- postgrest/postgrest:v14.17 (prev postgrest/postgrest:v14.12)
+- supabase/realtime:v2.134.10 (prev supabase/realtime:v2.102.3)
+- supabase/storage-api:v1.74.0 (prev supabase/storage-api:v1.60.4)
+- darthsim/imgproxy:v3.31.4 (prev darthsim/imgproxy:v3.30.1)
+- supabase/postgres-meta:v0.99.0 (prev supabase/postgres-meta:v0.96.6)
+- supabase/edge-runtime:v1.76.2 (prev supabase/edge-runtime:v1.74.0)
+- supabase/supavisor:2.9.12 (prev supabase/supavisor:2.9.5)
+- supabase/logflare:1.50.10 (prev supabase/logflare:1.43.1)
+
## 2026-08-03
- supabase/studio:2026.08.03-sha-022b374 (prev supabase/studio:2026.07.07-sha-a6a04f2)
- kong/kong:3.9.3 (prev kong/kong:3.9.1)
From a96a587f65f317ae56d4ff3d403e36d0d61a8473 Mon Sep 17 00:00:00 2001
From: Guilherme Souza
Date: Wed, 9 Sep 2026 11:55:54 -0300
Subject: [PATCH 002/614] fix(studio): update Swift package URL to official
supabase org (#50184)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
- The Studio Connect sheet's Swift install command pointed at
`supabase-community/supabase-swift`, which is no longer where the
package lives — it's now official under `supabase/supabase-swift`.
- Updated the URL in `INSTALL_COMMANDS.supabaseswift`.
## Test plan
- [x] Verified no other references to the old URL remain in Studio code
(translated top-level READMEs under `i18n/` also reference the old URL
but are out of scope for this fix)
## Summary by CodeRabbit
- **Bug Fixes**
- Updated the Swift installation command to reference the official
Supabase Swift repository.
---
.../components/interfaces/ConnectSheet/connect.schema.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts b/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
index c7f1e3eb325..fb141fe0c8f 100644
--- a/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
+++ b/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
@@ -34,8 +34,7 @@ export const INSTALL_COMMANDS: Record = {
supabasejs: 'npm install @supabase/supabase-js',
supabasepy: 'pip install supabase',
supabaseflutter: 'flutter pub add supabase_flutter',
- supabaseswift:
- 'swift package add-dependency https://github.com/supabase-community/supabase-swift',
+ supabaseswift: 'swift package add-dependency https://github.com/supabase/supabase-swift',
supabasekt: 'implementation("io.github.jan-tennert.supabase:supabase-kt:VERSION")',
}
From 7fbaeb3dcd534b79bbfce76ed1576432875ceec0 Mon Sep 17 00:00:00 2001
From: Miranda Limonczenko
Date: Wed, 9 Sep 2026 15:24:02 -0700
Subject: [PATCH 003/614] docs: style edit for the connecting to Postgres guide
(#49868)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Docs update. Style only.
## What is the current behavior?
The connecting to Postgres guide and its serverless drivers child page
have drifted from `WORD_LIST.md` and `CONTRIBUTING.md`. They also carry
six defects:
- The connection pooling diagram's alt text describes migrations on a
preview instance.
- The SSL screenshot's alt text and the sentence above it both promise
connection info. The image shows the SSL Configuration panel: a toggle
and a Download Certificate button.
- "Where can you see current connection usage?" lists three
Observability reports, then says the Roles page is not real-time. The
Roles page appears nowhere else in that answer.
- The serverless drivers manual configuration step has no main clause.
- "For example, If you set the pool size to 30".
- One of the two monitoring queries uses uppercase SQL keywords.
Three of the four connection strings use `postgres://` and two carry
literal project refs. The Connect dialog emits `postgresql://` with
placeholders.
The pooler host is templated as `aws-[region]`, which reads as
composable and isn't. Hosts are
`aws--.pooler.supabase.com`, and the index is a pooler
cluster index, not part of the region. Both `aws-0-us-west-1` and
`aws-1-us-west-1` appear in this repo, so a reader can't derive it.
Studio doesn't compose the host either; it comes from the API.
Groundwork for [DOCS-1312](https://linear.app/supabase/issue/DOCS-1312).
The issue stays open until the paired eval is re-run.
## What is the new behavior?
Word-level edit. No section is added, moved, or reordered, so the
restructure in the next PR of this stack lands as a readable set of
moved lines. Headings are untouched; PR 2 owns all heading changes.
- Fix the six defects above.
- Align the connection strings with what the Connect dialog emits:
`postgresql://` on all four, and `[PROJECT-REF]` in place of two literal
project refs.
- Use `[POOLER-HOST]` in the copyable pooler strings, the convention the
newer quickstarts already use. Keep the full `aws-[INDEX]-[REGION]`
shape in the summary table, where showing the shape is the point.
- Settle on one name per concept: shared and dedicated pooler in
sentence case, persistent backend, serverless and edge functions, and
paid plans.
- Drop bold used for plain emphasis, parenthetical asides, and claims
the page doesn't support: "ideal for", "ensures best performance and
latency", "satisfactory on their own".
- Format the two literal error strings as code, not quotes.
- Split the pool size answer into one paragraph per subject, and turn
the two pooler limits into a table.
- Serverless drivers: sentence case title, an intent sentence, and a
four-step procedure in place of the sentence fragment.
## Additional context
PR 1 of 2. Base is `master`.
Second commit applies review feedback. Third fixes the pooler host
placeholder, which belongs here rather than later in the stack: the
evidence is in the repo, not in the eval.
## Manual testing
1. Open [Connect to your
database](https://docs-git-docs-connecting-to-postgres-style-supabase.vercel.app/docs/guides/database/connecting-to-postgres)
on the deploy preview.
2. Read the four connection strings. All four use `postgresql://`, and
the two pooler strings use `[POOLER-HOST]` rather than a composable
region template.
3. Inspect the two images. The pooling diagram's alt text describes
pooling, and the SSL screenshot's describes the SSL Configuration panel.
4. Read "Where can you see current connection usage?". The paragraph
after the report list refers to the reports, not the Roles page.
5. Read "What is the difference between client connections and backend
connections?". The two limits are a table.
6. Open [Serverless
drivers](https://docs-git-docs-connecting-to-postgres-style-supabase.vercel.app/docs/guides/database/connecting-to-postgres/serverless-drivers).
Manual configuration is four numbered steps.
## Summary by CodeRabbit
- **Documentation**
- Clarified the PostgreSQL connection guide with updated connection
examples, pooling guidance, connection-mode tables, SSL information,
FAQs, and SQL formatting.
- Replaced sample connection values with generic placeholders in
documentation examples.
- Added clearer guidance that frontend Data API access requires
appropriate RLS policies.
- Updated explanations of client/backend connections and long-lived
PostgreSQL sessions.
- Updated serverless driver documentation with clearer setup guidance
for Vercel, Cloudflare, and Supabase Edge Functions.
- Reorganized manual configuration into numbered steps and standardized
connection string examples.
- Improved descriptions of runtime behavior and supported connection
methods.
---
.../database/connecting-to-postgres.mdx | 204 +++++++++---------
.../serverless-drivers.mdx | 25 ++-
2 files changed, 121 insertions(+), 108 deletions(-)
diff --git a/apps/docs/content/guides/database/connecting-to-postgres.mdx b/apps/docs/content/guides/database/connecting-to-postgres.mdx
index 2ae0a4d3164..9b1a234e2c2 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres.mdx
@@ -1,27 +1,27 @@
---
title: 'Connect to your database'
description: 'Connect to Postgres from your frontend, backend, or serverless environment'
-subtitle: 'Supabase provides multiple methods to connect to your Postgres database, whether you’re working on the frontend, backend, or using serverless functions.'
+subtitle: 'Supabase provides several ways to connect to your Postgres database, whether your code runs in the frontend, in a persistent backend, or in a serverless function.'
---
## How to connect to your Postgres databases
-How you connect to your database depends on where you're connecting from:
+How you connect to your database depends on where your code runs:
-- For frontend applications, use the [Data API](#data-apis-and-client-libraries)
-- For Postgres clients, use a connection string
- - **Use the [direct connection string](#direct-connection) for single sessions or Postgres native commands**. For example, database GUIs, client applications like [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html), [migrations](/docs/guides/deployment/database-migrations), [backup-restore](/docs/guides/platform/migrating-within-supabase/backup-restore), or specifying connections for [replication](/docs/guides/database/postgres/setup-replication-external). The direct endpoint is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
- - **Use [pooler session mode](#pooler-session-mode)** for application traffic from persistent clients on IPv4-only networks,
- - **Use [pooler transaction mode](#pooler-transaction-mode)** for application traffic from temporary clients (for example, serverless or edge functions).
+- For frontend applications, use the [Data API](#data-apis-and-client-libraries).
+- For Postgres clients, use a connection string:
+ - Use the [direct connection string](#direct-connection) for single sessions and Postgres native commands. This covers database GUIs, client applications such as [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html), [migrations](/docs/guides/deployment/database-migrations), [backup and restore](/docs/guides/platform/migrating-within-supabase/backup-restore), and [replication](/docs/guides/database/postgres/setup-replication-external). The direct endpoint is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+ - Use [pooler session mode](#pooler-session-mode) for application traffic from persistent backends on IPv4-only networks.
+ - Use [pooler transaction mode](#pooler-transaction-mode) for application traffic from short-lived clients, such as serverless and edge functions.
-The table below summarizes each mode, its host and port, IP version support per project tier, and what it's best used for:
+The following table summarizes each mode, its host and port, the IP version it supports on each plan, and what it's best used for:
-| Mode | Host:Port | Free | Paid | Paid + IPv4 add-on | Best for |
-| ----------------------------------------------- | --------------------------------------- | ---- | ---- | ------------------ | ------------------------------------------ |
-| Direct connection | `db.[project-id].supabase.co:5432` | IPv6 | IPv6 | IPv4 | Migrations, `pg_dump`, long-lived backend |
-| Shared pooler (Supavisor) - session mode | `aws-[region].pooler.supabase.com:5432` | IPv4 | IPv4 | IPv4 | Persistent backend on IPv4-only networks |
-| Shared pooler (Supavisor) - transaction mode | `aws-[region].pooler.supabase.com:6543` | IPv4 | IPv4 | IPv4 | Serverless and edge functions |
-| Dedicated pooler (PgBouncer) - transaction mode | `db.[project-id].supabase.co:6543` | - | IPv6 | IPv4 | High-performance app traffic on paid tiers |
+| Mode | Host:Port | Free | Paid | Paid + IPv4 add-on | Best for |
+| ---------------------------------- | ----------------------------------------------- | ---- | ---- | ------------------ | ------------------------------------------ |
+| Direct connection | `db.[PROJECT-REF].supabase.co:5432` | IPv6 | IPv6 | IPv4 | Migrations, `pg_dump`, persistent backends |
+| Shared pooler, session mode | `aws-[INDEX]-[REGION].pooler.supabase.com:5432` | IPv4 | IPv4 | IPv4 | Persistent backends on IPv4-only networks |
+| Shared pooler, transaction mode | `aws-[INDEX]-[REGION].pooler.supabase.com:6543` | IPv4 | IPv4 | IPv4 | Serverless and edge functions |
+| Dedicated pooler, transaction mode | `db.[PROJECT-REF].supabase.co:6543` | - | IPv6 | IPv4 | High-performance app traffic on paid plans |
@@ -62,12 +62,12 @@ The IPv4 add-on is not dual-stack: enabling it swaps the project's IPv6 (AAAA) D
## Data APIs and client libraries
-The Data APIs allow you to interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as you have [RLS](/docs/guides/database/postgres/row-level-security) enabled.
+The Data APIs let you interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as your tables have [Row Level Security](/docs/guides/database/postgres/row-level-security) (RLS) enabled and policies that allow the access. RLS with no policies denies every request.
- [REST](/docs/guides/api)
- [GraphQL](/docs/guides/graphql/api)
-For convenience, you can also use the [Supabase client libraries](/docs/reference), which wrap the Data APIs with a developer-friendly interface and automatically handle authentication:
+For convenience, you can also use the [Supabase client libraries](/docs/reference), which wrap the Data APIs with a developer-friendly interface and handle authentication for you:
- [JavaScript](/docs/reference/javascript/introduction)
- [Flutter](/docs/reference/dart/introduction)
@@ -78,7 +78,7 @@ For convenience, you can also use the [Supabase client libraries](/docs/referenc
## Direct connection
-The direct connection string connects directly to your Postgres instance. It is ideal for persistent servers, such as virtual machines (VMs) and long-lasting containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets.
+The direct connection string connects directly to your Postgres instance. Use it for persistent backends, such as virtual machines (VMs) and long-running containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets.
@@ -88,31 +88,31 @@ Direct connections are on IPv6, or on IPv4 if the project has the [IPv4 add-on](
The connection string looks like this:
-```
-postgresql://postgres:[YOUR-PASSWORD]@db.abcdefghijklmnopqrst.supabase.co:5432/postgres
+```txt
+postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
```
-Get your project's direct connection string from your project dashboard by clicking [Connect](/dashboard/project/_?showConnect=true).
+Get your project's direct connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true).
## Poolers
-Supabase offers two poolers. The **Shared Pooler** ([Supavisor](https://github.com/supabase/supavisor)) is multi-tenant, available on every project, and IPv4-only. The **Dedicated Pooler** ([PgBouncer](https://www.pgbouncer.org/)) is available on paid plans and co-located with your Postgres instance; like the direct connection, it is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+Supabase offers two poolers. The shared pooler, [Supavisor](https://github.com/supabase/supavisor), is multi-tenant, available on every project, and IPv4-only. The dedicated pooler, [PgBouncer](https://www.pgbouncer.org/), is available on paid plans and runs alongside your Postgres instance. Like the direct connection, it is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
### Pooler session mode
-The session mode connection string connects to your Postgres instance via the Shared Pooler (Supavisor). This is only recommended as an alternative to a Direct Connection when connecting from an IPv4-only network.
+The session mode connection string connects to your Postgres instance through the shared pooler. Use it as an alternative to a direct connection when you connect from an IPv4-only network.
The connection string looks like this:
-```
-postgres://postgres.apbkobhfnmcqqzqeeqss:[YOUR-PASSWORD]@aws-[REGION].pooler.supabase.com:5432/postgres
+```txt
+postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:5432/postgres
```
-Get your project's Session pooler connection string from your project dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=session).
+Get your project's session mode connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=session) and choosing **Session pooler**.
### Pooler transaction mode
-The transaction mode connection string connects to your Postgres instance via the Shared Pooler (Supavisor) in transaction-pooling mode. This is ideal for serverless or edge functions, which require many transient connections.
+The transaction mode connection string connects to your Postgres instance through the shared pooler in transaction-pooling mode. Use it for serverless and edge functions, which open many short-lived connections.
@@ -122,44 +122,44 @@ Transaction mode does not support [prepared statements](https://postgresql.org/d
The connection string looks like this:
-```
-postgres://postgres.apbkobhfnmcqqzqeeqss:[YOUR-PASSWORD]@aws-[REGION].pooler.supabase.com:6543/postgres
+```txt
+postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgres
```
-Get your project's Transaction pooler connection string from your project dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
+Get your project's transaction mode connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction) and choosing **Transaction pooler**.
## Dedicated pooler
-For paying customers, we provision a Dedicated Pooler ([PgBouncer](https://www.pgbouncer.org/)) that's co-located with your Postgres database. The Dedicated Pooler runs in transaction mode only - for session mode, use the [Shared Pooler](#pooler-session-mode). It is reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+On paid plans, Supabase provisions a dedicated pooler, [PgBouncer](https://www.pgbouncer.org/), that runs alongside your Postgres database. The dedicated pooler runs in transaction mode only. For session mode, use the [shared pooler](#pooler-session-mode). It is reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
The connection string looks like this:
-```
-postgres://postgres:[YOUR-PASSWORD]@db.abcdefghijklmnopqrst.supabase.co:6543/postgres
+```txt
+postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres
```
-The Dedicated Pooler ensures best performance and latency, while using up more of your project's compute resources. If your network supports IPv6 or you have the IPv4 add-on, we encourage you to use the Dedicated Pooler over the Shared Pooler.
+The dedicated pooler runs on the same machine as your database, so it connects with lower latency than the shared pooler. It also uses more of your project's compute resources. If your network supports IPv6, or you have the IPv4 add-on, use the dedicated pooler instead of the shared pooler.
-Get your project's Dedicated pooler connection string from your project dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
+Get your project's dedicated pooler connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
## More about connection pooling
Connection pooling improves database performance by reusing existing connections between queries. This reduces the overhead of establishing connections and improves scalability.
-You can use an application-side pooler or a server-side pooler (Supabase automatically provides one called Supavisor), depending on whether your backend is persistent or serverless.
+You can use an application-side pooler or a server-side pooler, depending on whether your backend is persistent or serverless. Supabase provides a server-side pooler called Supavisor.
### Application-side poolers
-Application-side poolers are built into connection libraries and API servers, such as Prisma, SQLAlchemy, and PostgREST. They maintain several active connections with Postgres or a server-side pooler, reducing the overhead of establishing connections between queries. When deploying to static architecture, such as long-standing containers or VMs, application-side poolers are satisfactory on their own.
+Application-side poolers are built into connection libraries and API servers, such as Prisma, SQLAlchemy, and PostgREST. They maintain several active connections with Postgres or a server-side pooler, which reduces the overhead of establishing connections between queries. When you deploy to a persistent backend, such as a long-running container or VM, an application-side pooler is enough on its own.
### Server-side poolers
-Postgres connections are like a WebSocket. Once established, they are preserved until the client (application server) disconnects. A server might only make a single 10 ms query, but needlessly reserve its database connection for seconds or longer.
+A Postgres connection is a long-lived session. Once established, it stays open until the client disconnects, or until the server or the network closes it. A server might make a single 10 ms query but hold its database connection for seconds or longer.
-Server-side poolers, such as Supabase's [Supavisor](https://github.com/supabase/supavisor) in transaction mode, sit between clients and the database and can be thought of as load balancers for Postgres connections.
+Server-side poolers, such as Supabase's [Supavisor](https://github.com/supabase/supavisor) in transaction mode, sit between clients and the database. Think of them as load balancers for Postgres connections.
-They maintain hot connections with the database and intelligently share them with clients only when needed, maximizing the amount of queries a single connection can service. They're best used to manage queries from auto-scaling systems, such as edge and serverless functions.
+Server-side poolers maintain hot connections with the database and share them with clients only when needed, which maximizes the number of queries a single connection can serve. Use them for queries from auto-scaling systems, such as edge and serverless functions.
## Connecting with SSL
-You should connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
+Connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
-You can obtain your connection info and Server root certificate from your application's dashboard:
+Download your server root certificate from [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard. The same section has a toggle that rejects non-SSL connections to your database.
-
+
## Resources
@@ -188,37 +188,48 @@ You can obtain your connection info and Server root certificate from your applic
## Troubleshooting and Postgres connection string FAQs
-Below are answers to common challenges and queries.
+The following answers cover common connection problems and questions.
-### What is a “connection refused” error?
+### What is a `connection refused` error?
-A “Connection refused” error typically means your database isn’t reachable. Ensure your Supabase project is running, confirm your database’s connection string, check firewall settings, and validate network permissions.
+A `connection refused` error means your database isn't reachable. Check that your Supabase project is running, confirm your database's connection string, check your firewall settings, and validate your network permissions.
-### What is the “FATAL: Password authentication failed” error?
+### What is the `FATAL: Password authentication failed` error?
-This error occurs when your credentials are incorrect. Double-check your username and password from the Supabase dashboard. If the problem persists, reset your database password from the project settings.
+This error means your credentials are incorrect. Check your username and password in the Supabase Dashboard. If the problem persists, reset your database password in the project settings.
### How do you connect using IPv4?
-You have two options. The Shared Pooler (Supavisor) is IPv4-only on every project tier - use it in either session or transaction mode. Alternatively, add the [IPv4 add-on](/docs/guides/platform/ipv4-address) to your project, which makes the direct connection and Dedicated Pooler reachable over IPv4 instead of IPv6.
+You have two options. The shared pooler is IPv4-only on every plan, in both session and transaction mode. Alternatively, add the [IPv4 add-on](/docs/guides/platform/ipv4-address) to your project, which makes the direct connection and the dedicated pooler reachable over IPv4 instead of IPv6.
### Where is the Postgres connection string in Supabase?
-Your connection string is located in the Supabase Dashboard. Click the [Connect](/dashboard/project/_?showConnect=true) button at the top of the page.
+Your connection string is in the Supabase Dashboard. Click [Connect](/dashboard/project/_?showConnect=true) at the top of the page.
### Can you use Supavisor and PgBouncer together?
-You can technically use both, but it’s not recommended unless you’re specifically trying to increase the total number of concurrent client connections. In most cases, it is better to choose either PgBouncer or Supavisor for pooled or transaction-based traffic. Direct connections remain the best choice for long-lived sessions, and, if IPv4 is required for those sessions, Supavisor session mode can be used as an alternative. Running both poolers simultaneously increases the risk of hitting your database’s maximum connection limit on smaller compute tiers.
+You can use both, but don't do it unless you're trying to increase the total number of concurrent client connections. In most cases, choose either PgBouncer or Supavisor for pooled or transaction-based traffic. Direct connections remain the best choice for long-lived sessions, and shared pooler session mode is the alternative when those sessions need IPv4. Running both poolers at once increases the risk of hitting your database's maximum connection limit on smaller compute sizes.
### How does the default pool size work?
-Supavisor and PgBouncer work independently, but both reference the same pool size setting. For example, If you set the pool size to 30, Supavisor can open up to 30 server side connections to Postgres. These connections are shared between the session mode port (5432) and the transaction mode port (6543). Each mode can use up to 30 connections independently, or split them between both, but the total combined connections across both modes cannot exceed 30. PgBouncer can also open up to 30 connections under the same limit. If both poolers are active and reach their roles/modes limits at the same time, you could have as many as 60 backend connections hitting your database, in addition to any direct connections. You can adjust the pool size in [Database settings](/dashboard/project/_/database/settings) in the dashboard.
+Supavisor and PgBouncer work independently, but both reference the same pool size setting. You can adjust it in [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard.
+
+Say you set the pool size to 30. Supavisor can then open up to 30 server-side connections to Postgres. Those 30 are shared between the session mode port, `5432`, and the transaction mode port, `6543`. Each mode can use all 30 on its own, or the two can split them, but the total across both modes cannot exceed 30.
+
+PgBouncer can open up to 30 connections under the same limit. If both poolers reach their limits at the same time, you could have as many as 60 backend connections hitting your database, in addition to any direct connections.
### What is the difference between client connections and backend connections?
-There are two different limits to understand when working with poolers. The first is client connections, which refers to how many clients can connect to a pooler at the same time. This number is capped by your [compute tier’s “max pooler clients” limit](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections), and it applies independently to Supavisor and PgBouncer. The second is backend connections, which is the number of active connections a pooler opens to Postgres. This number is set by the pool size for that pooler.
+There are two limits to understand when working with poolers.
-```
+| Limit | What it counts | What sets it |
+| ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| Client connections | How many clients can connect to a pooler at the same time | Your [compute size's max pooler clients limit](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections) |
+| Backend connections | How many active connections a pooler opens to Postgres | The pool size for that pooler |
+
+Both limits apply independently to Supavisor and PgBouncer.
+
+```txt
Total backend load on Postgres =
Direct connections +
Supavisor backend connections (≤ supavisor_pool_size) +
@@ -228,17 +239,17 @@ Total backend load on Postgres =
### What is the max pooler clients limit?
-The “max pooler clients” limit for your compute tier applies separately to Supavisor and PgBouncer. One pooler reaching its client limit does not affect the other. When a pooler reaches this limit, it stops accepting new client connections until existing ones are closed, but the other pooler remains unaffected. You can check your tier’s connection limits in the [compute and disk limits documentation](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections).
+The max pooler clients limit for your compute size applies separately to Supavisor and PgBouncer. One pooler reaching its client limit doesn't affect the other. When a pooler reaches this limit, it stops accepting new client connections until existing ones close. The other pooler is unaffected. You can check your connection limits in the [compute and disk documentation](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections).
### Where can you see current connection usage?
-You can track connection usage from the [Observability](/dashboard/project/_/observability/database) section in your project dashboard. There are three key reports:
+You can track connection usage in the [Observability](/dashboard/project/_/observability/database) section of the Supabase Dashboard. There are three reports:
-- **Database Connections:** shows total active connections by role (this includes direct and pooled connections).
-- **Dedicated Pooler Client Connections:** shows the number of active client connections to PgBouncer.
-- **Shared Pooler (Supavisor) Client Connections:** shows the number of active client connections to Supavisor.
+- **Database Connections:** total active connections by role, including direct and pooled connections.
+- **Dedicated Pooler Client Connections:** active client connections to PgBouncer.
+- **Shared Pooler (Supavisor) Client Connections:** active client connections to Supavisor.
-Keep in mind that the Roles page is not real-time, it shows the connection count from the last refresh. If you need up-to-the-second data, set up Grafana or run the query against `pg_stat_activity` directly in SQL Editor. We have a few helpful queries for checking connections.
+These reports are not real-time. They show the connection count from the last refresh. For up-to-the-second data, set up Grafana or query `pg_stat_activity` directly in the SQL Editor. The following queries report on current connections.
```sql
-- Count connections by application and user name
@@ -255,64 +266,63 @@ group by usename, application_name;
```sql
-- View all connections
- SELECT
- pg_stat_activity.pid,
- ssl AS ssl_connection,
- datname AS database,
- usename AS connected_role,
- application_name,
- client_addr,
- query,
- query_start,
- state,
- backend_start
-FROM pg_stat_ssl
-JOIN pg_stat_activity
- ON pg_stat_ssl.pid = pg_stat_activity.pid;
+select
+ pg_stat_activity.pid,
+ ssl as ssl_connection,
+ datname as database,
+ usename as connected_role,
+ application_name,
+ client_addr,
+ query,
+ query_start,
+ state,
+ backend_start
+from pg_stat_ssl
+join pg_stat_activity on pg_stat_ssl.pid = pg_stat_activity.pid;
```
### Why are there active connections when the app is idle?
-Even if your application isn’t making queries, some Supabase services keep persistent connections to your database. For example, Storage, PostgREST, and our health checker all maintain long-lived connections. You usually see a small baseline of active connections from these services.
+Even when your application isn't making queries, some Supabase services keep persistent connections to your database. Storage, PostgREST, and the health checker all maintain long-lived connections. You usually see a small baseline of active connections from these services.
### Why do connection strings have different ports?
Different modes use different ports:
-- Direct connection: `5432` (Postgres on your project instance)
-- Dedicated pooler, transaction mode: `6543` (PgBouncer on your project instance)
-- Shared pooler, transaction mode: `6543` (Supavisor, multi-tenant)
-- Shared pooler, session mode: `5432` (Supavisor, multi-tenant)
+- Direct connection: `5432`, for Postgres on your project instance
+- Dedicated pooler, transaction mode: `6543`, for PgBouncer on your project instance
+- Shared pooler, transaction mode: `6543`, for Supavisor
+- Shared pooler, session mode: `5432`, for Supavisor
-The port helps route the connection to the right pooler/mode.
+The port routes the connection to the right pooler and mode.
### Does connection pooling affect latency?
-Because the dedicated pooler is hosted on the same machine as your database, it connects with lower latency than the shared pooler, which is hosted on a separate server. Direct connections have no pooler overhead but require IPv6 unless you have the IPv4 add-on.
+The dedicated pooler runs on the same machine as your database, so it connects with lower latency than the shared pooler, which runs on a separate server. Direct connections have no pooler overhead, but they require IPv6 unless you have the IPv4 add-on.
### How to choose the right connection method?
**Direct connection:**
-- Best for: persistent backend services
-- Use for migrations, pg_dump, backup and management tools
-- Network: reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+- Best for persistent backends
+- Use for migrations, `pg_dump`, and backup and management tools
+- Reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address)
-**Shared pooler (Supavisor):**
+**Shared pooler, Supavisor:**
-- Best for: connections from IPv4 networks (IPv4-only on every tier)
- - Supavisor session mode → persistent backend on IPv4 networks
- - Supavisor transaction mode → serverless functions or short-lived tasks
-- Use for application runtime traffic (queries, writes)
+- Best for connections from IPv4 networks. It is IPv4-only on every plan
+ - Session mode for a persistent backend on an IPv4 network
+ - Transaction mode for serverless functions and other short-lived tasks
+- Use for application runtime traffic, such as queries and writes
-**Dedicated pooler (PgBouncer, paid tier):**
+**Dedicated pooler, PgBouncer, on paid plans:**
-- Best for: high-performance apps that need dedicated resources
-- Use for application runtime traffic (queries, writes)
-- Transaction mode only - use the Shared Pooler if you need session mode
-- Network: reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address)
+- Best for high-performance apps that need dedicated resources
+- Use for application runtime traffic, such as queries and writes
+- Transaction mode only. Use the shared pooler if you need session mode
+- Reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address)
-See the [connection method matrix](#how-to-connect-to-your-postgres-databases) at the top of this page for a quick reference, or follow the decision flow in the diagram below to choose the right option for your environment.
+See the [table of connection modes](#how-to-connect-to-your-postgres-databases) at the top of this page for a quick reference, or follow the decision flow in the diagram below to choose the right option for your environment.
```mermaid
flowchart TD
@@ -328,4 +338,4 @@ flowchart TD
I --> K[Use Supavisor Transaction Mode]
```
-The decision depends on where you connect from. For a **persistent backend**, use a direct connection if you can reach the database over IPv6 (or have the IPv4 add-on); otherwise use Supavisor in session mode. For **serverless or edge** environments, use the dedicated pooler (PgBouncer, Pro plan) when IPv6 or the IPv4 add-on is available, or Supavisor in transaction mode when you need IPv4.
+The decision depends on where your code runs. For a persistent backend, use a direct connection if you can reach the database over IPv6 or have the IPv4 add-on. Otherwise, use the shared pooler in session mode. For serverless and edge environments, use the dedicated pooler on paid plans when IPv6 or the IPv4 add-on is available, or the shared pooler in transaction mode when you need IPv4.
diff --git a/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx b/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
index 192b4ef7d8b..45a51a39244 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
@@ -1,21 +1,21 @@
---
id: 'serverless-drivers'
-title: 'Serverless Drivers'
-description: 'Connecting to your Postgres database in serverless environments.'
-subtitle: 'Connecting to your Postgres database in serverless environments.'
+title: 'Serverless drivers'
+description: 'Connect to your Postgres database from a serverless environment'
+subtitle: 'Choose a driver for connecting to your Postgres database from a serverless environment.'
---
-Supabase provides several options for connecting to your Postgres database from serverless environments.
+Learn how to connect to your Postgres database from a serverless environment. The driver you use depends on which runtime your code runs in.
-[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api) and therefore works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres#poolers) and can serve thousands of simultaneous requests, and therefore is ideal for Serverless workloads.
+[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api), so it works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres#poolers) and can serve thousands of simultaneous requests, which suits serverless workloads.
## Vercel Edge Functions
-Vercel's [Edge runtime](https://vercel.com/docs/functions/runtimes/edge-runtime) is built on top of the [V8 engine](https://v8.dev/), that provides a limited set of Web Standard APIs.
+Vercel's [Edge runtime](https://vercel.com/docs/functions/runtimes/edge-runtime) runs on the [V8 engine](https://v8.dev/) and exposes a limited set of Web Standard APIs.
### Quickstart
-Choose one of these Vercel Deploy Templates which use our [Vercel Deploy Integration](https://vercel.com/integrations/supabase) to automatically configure your connection strings as environment variables on your Vercel project!
+Choose one of these Vercel Deploy Templates. They use the [Vercel Deploy Integration](https://vercel.com/integrations/supabase) to configure your connection strings as environment variables on your Vercel project.
@@ -43,10 +43,13 @@ Choose one of these Vercel Deploy Templates which use our [Vercel Deploy Integra
### Manual configuration
-In your [`Database Settings`](/dashboard/project/_?showConnect=true&method=transaction) and copy the URI from the `Transaction pooler` section and save it as the `POSTGRES_URL` environment variable. Remember to replace the password placeholder with your actual database password and add the following suffix `?workaround=supabase-pooler.vercel`.
+1. In the Supabase Dashboard, click [Connect](/dashboard/project/_?showConnect=true&method=transaction) and copy the URI from the **Transaction pooler** section.
+2. Replace the password placeholder with your database password.
+3. Add the suffix `?workaround=supabase-pooler.vercel` to the URI.
+4. Save the result as the `POSTGRES_URL` environment variable.
```txt .env.local
-POSTGRES_URL="postgres://postgres.cfcxynqnhdybqtbhjemm:[YOUR-PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres?workaround=supabase-pooler.vercel"
+POSTGRES_URL="postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgres?workaround=supabase-pooler.vercel"
```
@@ -120,7 +123,7 @@ export { sql } from 'kysely'
## Cloudflare Workers
-Cloudflare's Workers runtime also uses the [V8 engine](https://v8.dev/) but provides polyfills for a subset of Node.js APIs and [TCP Sockets API](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/), giving you a couple of options:
+Cloudflare's Workers runtime also uses the [V8 engine](https://v8.dev/), but it provides polyfills for a subset of Node.js APIs and the [TCP Sockets API](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/). That gives you three options:
- [supabase-js](https://developers.cloudflare.com/workers/databases/native-integrations/supabase/)
- [Postgres.js](https://github.com/porsager/postgres?tab=readme-ov-file#cloudflare-workers-support)
@@ -128,7 +131,7 @@ Cloudflare's Workers runtime also uses the [V8 engine](https://v8.dev/) but prov
## Supabase Edge Functions
-Supabase Edge Functions uses the [Deno runtime](https://deno.com/) which has native support for TCP connections allowing you to choose your favorite client:
+Supabase Edge Functions use the [Deno runtime](https://deno.com/), which has native support for TCP connections. You can choose any of these clients:
- [supabase-js](/docs/guides/functions/connect-to-postgres#using-supabase-js)
- [Deno Postgres driver](/docs/guides/functions/connect-to-postgres#using-a-postgres-client)
From 976e7338bc16f3f81358d31ef01acc773676ad44 Mon Sep 17 00:00:00 2001
From: Miranda Limonczenko
Date: Wed, 9 Sep 2026 16:21:41 -0700
Subject: [PATCH 004/614] docs: restructure the connecting to Postgres guide by
information type (#49869)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Docs update. Restructure, mostly moved lines, plus inbound anchor fixes.
## What is the current behavior?
The page states the same routing decision four times and never states an
answer:
- Intro bullets
- A matrix table
- A "How to choose the right connection method?" section
- A Mermaid flowchart
An agent asked "I'm deploying to Vercel serverless functions, set up the
database connection" has to synthesize an answer from four partial,
inconsistent restatements. Context, procedure, and reference material
are interleaved throughout, so background reading interrupts the action
path.
The page is also too long at 2,883 words, and grouping alone doesn't fix
that. Explainer and reference material need their own page, and the
troubleshooting group belongs in troubleshooting entries.
16 of the 19 inbound anchor links to this page are already broken on
`master`, before any restructure: `#direct-connections`,
`#shared-pooler`, `#connection-pooler`, `#how-connection-pooling-works`,
`#quick-summary`, `#connection-pool`, and `#connecting-with-drizzle`.
Groundwork for [DOCS-1312](https://linear.app/supabase/issue/DOCS-1312).
The issue stays open until the paired eval is re-run.
## What is the new behavior?
Group the guide into a decision, a procedure, context, reference, and
troubleshooting, per CONTRIBUTING § Guides on mixed information types.
Review with `git diff --color-moved=zebra`.
- Lead with "Which connection method do you use?", a decision table
keyed on where your code runs. Section navigation sits directly below
the intro.
- Collect every connection string under "Get your connection string",
with the shared Connect dialog steps stated once as a procedure.
- Move the endpoint, port, pool size, and connection limit material into
"Connection reference". These were FAQ questions.
- Split the page. The guide keeps the decision, the connection strings,
and the quickstarts, at 1,180 words and three paths. A new child page,
Connection pooling and limits, carries how pooling works, pool size,
connection limits, and monitoring.
- Move the endpoint and IP version table up beside the connection
strings it explains.
- Replace the troubleshooting group with two new troubleshooting
entries, `tenant-or-user-not-found` and
`fatal-password-authentication-failed`, plus links to the existing
entries. The existing connection-refused entry is stronger than what was
here: it names the IP ban and gives the unban procedure.
- Cut the pool size worked example. It said a pool size of 30 is a
shared ceiling across session and transaction mode, while the Supavisor
FAQ and the terminology entry both say pool size is per user, database,
and mode combination. That text came from `master`, so the contradiction
is pre-existing. Link the FAQ as the authority rather than picking a
side.
- Drop the duplicate `pg_stat_ssl` query, which already exists in
`connection-management.mdx` and
`monitor-supavisor-postgres-connections.mdx`, both with column tables
this page lacked.
- Add the subsection to the navigation, which also adopts
`connecting-to-postgres/serverless-drivers`. That page existed on disk
and was referenced nowhere in the navigation constants.
- Delete the decision flowchart. It was the fourth restatement of the
decision table, and its logic was broken: `Persistent Backend` had two
unconditional edges into decision nodes that each had one unlabeled
output, so neither node decided anything.
- Fix every broken inbound anchor, and pin stable anchors on the
headings they target. This now includes six files in `apps/www` that no
earlier pass in this stack checked, most of which were already broken on
`master`.
- Repoint the Studio Connect sheet's Drizzle link at the Drizzle guide.
It pointed at a heading this page hasn't had for some time.
- Serverless drivers: state the guide's intent, give the three runtimes
parallel structure, and link the transaction mode prepared statements
constraint. That page never mentioned the constraint that most affects
serverless connections.
## Additional context
PR 2 of 2. Base is #49868, rebased on its review feedback commit.
Three of the 13 files are in `apps/studio`, so this runs the Studio unit
tests, build, and lint ratchet. They are link string changes only. The
ESLint warning count is unchanged at 1 on the touched files, so the
ratchet holds.
## Manual testing
1. Open [Connect to your
database](https://docs-git-docs-connecting-to-postgres-structure-supabase.vercel.app/docs/guides/database/connecting-to-postgres)
on the deploy preview.
2. Check the table of contents. The top level reads: Which connection
method do you use?, Get your connection string, Quickstarts, Related.
The intro lists three paths.
3. Open
[Reports](https://docs-git-docs-connecting-to-postgres-structure-supabase.vercel.app/docs/guides/monitoring-and-debugging/reports)
and follow "Implement connection pooling" under Disk IO. It lands on the
decision table.
4. Open [Serverless
drivers](https://docs-git-docs-connecting-to-postgres-structure-supabase.vercel.app/docs/guides/database/connecting-to-postgres/serverless-drivers).
The intro links the transaction mode prepared statements constraint.
5. Check the sidebar. Connecting to your database expands to Connection
pooling and limits and Serverless drivers.
6. Open [Connection pooling and
limits](https://docs-git-docs-connecting-to-postgres-structure-supabase.vercel.app/docs/guides/database/connecting-to-postgres/pooling-and-limits).
Pool size states the setting and links the Supavisor FAQ, with no worked
example.
7. Open [Tenant or user not
found](https://docs-git-docs-connecting-to-postgres-structure-supabase.vercel.app/docs/guides/troubleshooting/tenant-or-user-not-found).
## Summary by CodeRabbit
* **New Features**
* Added a dedicated guide covering connection pooling, limits,
configuration, and monitoring.
* Added troubleshooting guides for password authentication failures and
shared pooler tenant or user errors.
* Expanded connection guidance with method selection, endpoints, IP
versions, and serverless driver configuration.
* **Documentation**
* Reorganized database connection documentation and navigation.
* Updated related links throughout the documentation to current
connection and pooling guidance.
* Improved guidance for pooler modes, connection strings, and supported
deployment environments.
---
.../NavigationMenu.constants.ts | 14 +
.../database/connecting-to-postgres.mdx | 423 ++++++------------
.../pooling-and-limits.mdx | 108 +++++
.../serverless-drivers.mdx | 10 +-
.../dropping-all-tables-in-schema.mdx | 2 +-
.../database/postgres/first-row-in-group.mdx | 2 +-
.../guides/database/postgres/indexes.mdx | 2 +-
.../guides/database/postgres/timeouts.mdx | 2 +-
.../postgres/which-version-of-postgres.mdx | 2 +-
apps/docs/content/guides/database/tables.mdx | 2 +-
.../content/guides/observability/reports.mdx | 42 +-
.../platform/manage-your-usage/egress.mdx | 2 +-
.../content/guides/platform/performance.mdx | 2 +-
...plication-superuser-connections-3V3nIb.mdx | 2 +-
.../fatal-password-authentication-failed.mdx | 31 ++
.../troubleshooting/http-api-issues.mdx | 2 +-
.../tenant-or-user-not-found.mdx | 34 ++
17 files changed, 361 insertions(+), 321 deletions(-)
create mode 100644 apps/docs/content/guides/database/connecting-to-postgres/pooling-and-limits.mdx
create mode 100644 apps/docs/content/troubleshooting/fatal-password-authentication-failed.mdx
create mode 100644 apps/docs/content/troubleshooting/tenant-or-user-not-found.mdx
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 7be23d9e1a3..6a97bf6aa02 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -1034,6 +1034,20 @@ export const database: NavMenuConstant = {
{
name: 'Connecting to your database',
url: '/guides/database/connecting-to-postgres' as `/${string}`,
+ items: [
+ {
+ name: 'Connecting to your database',
+ url: '/guides/database/connecting-to-postgres' as `/${string}`,
+ },
+ {
+ name: 'Connection pooling and limits',
+ url: '/guides/database/connecting-to-postgres/pooling-and-limits' as `/${string}`,
+ },
+ {
+ name: 'Serverless drivers',
+ url: '/guides/database/connecting-to-postgres/serverless-drivers' as `/${string}`,
+ },
+ ],
},
{ name: 'Importing data', url: '/guides/database/import-data' },
{ name: 'Securing your data', url: '/guides/database/secure-data' },
diff --git a/apps/docs/content/guides/database/connecting-to-postgres.mdx b/apps/docs/content/guides/database/connecting-to-postgres.mdx
index 9b1a234e2c2..b06d0b05a35 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres.mdx
@@ -4,24 +4,26 @@ description: 'Connect to Postgres from your frontend, backend, or serverless env
subtitle: 'Supabase provides several ways to connect to your Postgres database, whether your code runs in the frontend, in a persistent backend, or in a serverless function.'
---
-## How to connect to your Postgres databases
+Learn how to pick a connection method and where to find the connection string for it.
-How you connect to your database depends on where your code runs:
+- [Which connection method do you use?](#choose-a-connection-method) picks a method based on where your code runs.
+- [Get your connection string](#get-your-connection-string) shows where each string comes from.
+- [Quickstarts](#quickstarts) connect a specific ORM or database GUI.
-- For frontend applications, use the [Data API](#data-apis-and-client-libraries).
-- For Postgres clients, use a connection string:
- - Use the [direct connection string](#direct-connection) for single sessions and Postgres native commands. This covers database GUIs, client applications such as [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html), [migrations](/docs/guides/deployment/database-migrations), [backup and restore](/docs/guides/platform/migrating-within-supabase/backup-restore), and [replication](/docs/guides/database/postgres/setup-replication-external). The direct endpoint is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
- - Use [pooler session mode](#pooler-session-mode) for application traffic from persistent backends on IPv4-only networks.
- - Use [pooler transaction mode](#pooler-transaction-mode) for application traffic from short-lived clients, such as serverless and edge functions.
+For how pooling works and the limits that apply to your connections, see [Connection pooling and limits](/docs/guides/database/connecting-to-postgres/pooling-and-limits).
-The following table summarizes each mode, its host and port, the IP version it supports on each plan, and what it's best used for:
+## Which connection method do you use? [#choose-a-connection-method]
-| Mode | Host:Port | Free | Paid | Paid + IPv4 add-on | Best for |
-| ---------------------------------- | ----------------------------------------------- | ---- | ---- | ------------------ | ------------------------------------------ |
-| Direct connection | `db.[PROJECT-REF].supabase.co:5432` | IPv6 | IPv6 | IPv4 | Migrations, `pg_dump`, persistent backends |
-| Shared pooler, session mode | `aws-[INDEX]-[REGION].pooler.supabase.com:5432` | IPv4 | IPv4 | IPv4 | Persistent backends on IPv4-only networks |
-| Shared pooler, transaction mode | `aws-[INDEX]-[REGION].pooler.supabase.com:6543` | IPv4 | IPv4 | IPv4 | Serverless and edge functions |
-| Dedicated pooler, transaction mode | `db.[PROJECT-REF].supabase.co:6543` | - | IPv6 | IPv4 | High-performance app traffic on paid plans |
+How you connect to your database depends on where your code runs. Find your case in the table, then get the matching connection string.
+
+| Where your code runs | Use | Why |
+| --------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
+| A frontend application | [Data API](#data-apis-and-client-libraries) | Works over REST or GraphQL, so you don't need a Postgres client. Requires Row Level Security. |
+| A serverless or edge function | [Shared pooler, transaction mode](#pooler-transaction-mode) | These environments open many short-lived connections. |
+| A persistent backend on IPv6, or with the IPv4 add-on | [Direct connection](#direct-connection) | No pooler in the path. |
+| A persistent backend on an IPv4-only network | [Shared pooler, session mode](#pooler-session-mode) | The shared pooler is IPv4-only on every plan. |
+| A high-performance application on a paid plan | [Dedicated pooler](#dedicated-pooler) | Runs on the same machine as your database, so lower latency than the shared pooler. |
+| Migrations, `pg_dump`, backup and restore, or replication | [Direct connection](#direct-connection) | These are single sessions and Postgres native commands. |
@@ -29,7 +31,115 @@ The IPv4 add-on is not dual-stack: enabling it swaps the project's IPv6 (AAAA) D
-## Quickstarts
+For the host, port, and IP version of each mode, see [Endpoints and IP versions](#endpoints-and-ip-versions).
+
+## Get your connection string [#get-your-connection-string]
+
+Connecting from a frontend application? You don't need a connection string. Skip to [Data APIs and client libraries](#data-apis-and-client-libraries), which uses your project URL and an API key instead.
+
+For every Postgres connection mode, the string comes from the same place:
+
+1. Open your project in the [Supabase Dashboard](/dashboard/project/_).
+2. Click **Connect** at the top of the page.
+3. Choose the connection method you picked above.
+4. Copy the string and replace `[YOUR-PASSWORD]` with your database password. [Percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) any reserved characters it contains, such as `&`, `#`, `?`, or a space.
+
+The sections below show what each string looks like and when to use it.
+
+### Direct connection [#direct-connection]
+
+The direct connection string connects directly to your Postgres instance. Use it for persistent backends, such as virtual machines (VMs) and long-running containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets.
+
+
+
+Direct connections are on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address). If your network is IPv4-only and you don't have the add-on, use [session mode](#pooler-session-mode) instead.
+
+
+
+```txt
+postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
+```
+
+Get this string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true).
+
+### Shared pooler, session mode [#pooler-session-mode]
+
+The session mode connection string connects to your Postgres instance through the shared pooler. Use it as an alternative to a direct connection when you connect from an IPv4-only network.
+
+```txt
+postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:5432/postgres
+```
+
+Get this string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=session) and choosing **Session pooler**.
+
+### Shared pooler, transaction mode [#pooler-transaction-mode]
+
+The transaction mode connection string connects to your Postgres instance through the shared pooler in transaction-pooling mode. Use it for serverless and edge functions, which open many short-lived connections.
+
+
+
+Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your connection library.
+
+
+
+```txt
+postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgres
+```
+
+Get this string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction) and choosing **Transaction pooler**.
+
+### Dedicated pooler [#dedicated-pooler]
+
+On paid plans, Supabase provisions a dedicated pooler that runs alongside your Postgres database. The dedicated pooler runs in transaction mode only. For session mode, use the [shared pooler](#pooler-session-mode). It is reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+
+```txt
+postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres
+```
+
+Get this string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
+
+### Data APIs and client libraries [#data-apis-and-client-libraries]
+
+The Data APIs let you interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as your tables have [Row Level Security](/docs/guides/database/postgres/row-level-security) (RLS) enabled and policies that allow the access. RLS with no policies denies every request.
+
+- [REST](/docs/guides/api)
+- [GraphQL](/docs/guides/graphql/api)
+
+For convenience, you can also use the [Supabase client libraries](/docs/reference), which wrap the Data APIs with a developer-friendly interface and handle authentication for you:
+
+- [JavaScript](/docs/reference/javascript/introduction)
+- [Flutter](/docs/reference/dart/introduction)
+- [Swift](/docs/reference/swift)
+- [Python](/docs/reference/python/introduction)
+- [C#](/docs/reference/csharp/introduction)
+- [Kotlin](/docs/reference/kotlin/introduction)
+
+### Connect with SSL [#connecting-with-ssl]
+
+Connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
+
+Download your server root certificate from [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard. The same section has a toggle that rejects non-SSL connections to your database.
+
+
+
+### Endpoints and IP versions [#endpoints-and-ip-versions]
+
+Each mode has its own host, port, and IP version support. IP version support depends on your plan and on whether the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+
+| Mode | Host:Port | Free | Paid | Paid + IPv4 add-on |
+| ---------------------------------- | ----------------------------------------------- | ---- | ---- | ------------------ |
+| Direct connection | `db.[PROJECT-REF].supabase.co:5432` | IPv6 | IPv6 | IPv4 |
+| Shared pooler, session mode | `aws-[INDEX]-[REGION].pooler.supabase.com:5432` | IPv4 | IPv4 | IPv4 |
+| Shared pooler, transaction mode | `aws-[INDEX]-[REGION].pooler.supabase.com:6543` | IPv4 | IPv4 | IPv4 |
+| Dedicated pooler, transaction mode | `db.[PROJECT-REF].supabase.co:6543` | - | IPv6 | IPv4 |
+
+The port routes the connection to the right pooler and mode. Port `5432` reaches Postgres for a direct connection and Supavisor for session mode. Port `6543` reaches PgBouncer for the dedicated pooler and Supavisor for shared transaction mode.
+
+To connect over IPv4, you have two options. The shared pooler is IPv4-only on every plan, in both session and transaction mode. Alternatively, add the [IPv4 add-on](/docs/guides/platform/ipv4-address) to your project, which makes the direct connection and the dedicated pooler reachable over IPv4 instead of IPv6.
+
+## Quickstarts [#quickstarts]
+
+Each quickstart connects one ORM or database GUI to your Supabase database.
@@ -60,282 +170,17 @@ The IPv4 add-on is not dual-stack: enabling it swaps the project's IPv6 (AAAA) D
-## Data APIs and client libraries
+## Related
-The Data APIs let you interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as your tables have [Row Level Security](/docs/guides/database/postgres/row-level-security) (RLS) enabled and policies that allow the access. RLS with no policies denies every request.
-
-- [REST](/docs/guides/api)
-- [GraphQL](/docs/guides/graphql/api)
-
-For convenience, you can also use the [Supabase client libraries](/docs/reference), which wrap the Data APIs with a developer-friendly interface and handle authentication for you:
-
-- [JavaScript](/docs/reference/javascript/introduction)
-- [Flutter](/docs/reference/dart/introduction)
-- [Swift](/docs/reference/swift)
-- [Python](/docs/reference/python/introduction)
-- [C#](/docs/reference/csharp/introduction)
-- [Kotlin](/docs/reference/kotlin/introduction)
-
-## Direct connection
-
-The direct connection string connects directly to your Postgres instance. Use it for persistent backends, such as virtual machines (VMs) and long-running containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets.
-
-
-
-Direct connections are on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address). If your network is IPv4-only and you don't have the add-on, use [pooler session mode](#pooler-session-mode) instead.
-
-
-
-The connection string looks like this:
-
-```txt
-postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
-```
-
-Get your project's direct connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true).
-
-## Poolers
-
-Supabase offers two poolers. The shared pooler, [Supavisor](https://github.com/supabase/supavisor), is multi-tenant, available on every project, and IPv4-only. The dedicated pooler, [PgBouncer](https://www.pgbouncer.org/), is available on paid plans and runs alongside your Postgres instance. Like the direct connection, it is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
-
-### Pooler session mode
-
-The session mode connection string connects to your Postgres instance through the shared pooler. Use it as an alternative to a direct connection when you connect from an IPv4-only network.
-
-The connection string looks like this:
-
-```txt
-postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:5432/postgres
-```
-
-Get your project's session mode connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=session) and choosing **Session pooler**.
-
-### Pooler transaction mode
-
-The transaction mode connection string connects to your Postgres instance through the shared pooler in transaction-pooling mode. Use it for serverless and edge functions, which open many short-lived connections.
-
-
-
-Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your connection library.
-
-
-
-The connection string looks like this:
-
-```txt
-postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgres
-```
-
-Get your project's transaction mode connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction) and choosing **Transaction pooler**.
-
-## Dedicated pooler
-
-On paid plans, Supabase provisions a dedicated pooler, [PgBouncer](https://www.pgbouncer.org/), that runs alongside your Postgres database. The dedicated pooler runs in transaction mode only. For session mode, use the [shared pooler](#pooler-session-mode). It is reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
-
-The connection string looks like this:
-
-```txt
-postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres
-```
-
-The dedicated pooler runs on the same machine as your database, so it connects with lower latency than the shared pooler. It also uses more of your project's compute resources. If your network supports IPv6, or you have the IPv4 add-on, use the dedicated pooler instead of the shared pooler.
-
-Get your project's dedicated pooler connection string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
-
-## More about connection pooling
-
-Connection pooling improves database performance by reusing existing connections between queries. This reduces the overhead of establishing connections and improves scalability.
-
-You can use an application-side pooler or a server-side pooler, depending on whether your backend is persistent or serverless. Supabase provides a server-side pooler called Supavisor.
-
-### Application-side poolers
-
-Application-side poolers are built into connection libraries and API servers, such as Prisma, SQLAlchemy, and PostgREST. They maintain several active connections with Postgres or a server-side pooler, which reduces the overhead of establishing connections between queries. When you deploy to a persistent backend, such as a long-running container or VM, an application-side pooler is enough on its own.
-
-### Server-side poolers
-
-A Postgres connection is a long-lived session. Once established, it stays open until the client disconnects, or until the server or the network closes it. A server might make a single 10 ms query but hold its database connection for seconds or longer.
-
-Server-side poolers, such as Supabase's [Supavisor](https://github.com/supabase/supavisor) in transaction mode, sit between clients and the database. Think of them as load balancers for Postgres connections.
-
-
-
-Server-side poolers maintain hot connections with the database and share them with clients only when needed, which maximizes the number of queries a single connection can serve. Use them for queries from auto-scaling systems, such as edge and serverless functions.
-
-## Connecting with SSL
-
-Connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
-
-Download your server root certificate from [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard. The same section has a toggle that rejects non-SSL connections to your database.
-
-
-
-## Resources
-
-- [Connection management](/docs/guides/database/connection-management)
+- [Connection pooling and limits](/docs/guides/database/connecting-to-postgres/pooling-and-limits) explains poolers, pool size, and connection limits.
+- [Serverless drivers](/docs/guides/database/connecting-to-postgres/serverless-drivers) covers Vercel, Cloudflare Workers, and Supabase Edge Functions.
+- [Managing connections](/docs/guides/database/connection-management) covers monitoring and diagnosing stuck queries.
- [Connecting with psql](/docs/guides/database/psql)
- [Importing data into Supabase](/docs/guides/database/import-data)
-## Troubleshooting and Postgres connection string FAQs
+### Troubleshooting
-The following answers cover common connection problems and questions.
-
-### What is a `connection refused` error?
-
-A `connection refused` error means your database isn't reachable. Check that your Supabase project is running, confirm your database's connection string, check your firewall settings, and validate your network permissions.
-
-### What is the `FATAL: Password authentication failed` error?
-
-This error means your credentials are incorrect. Check your username and password in the Supabase Dashboard. If the problem persists, reset your database password in the project settings.
-
-### How do you connect using IPv4?
-
-You have two options. The shared pooler is IPv4-only on every plan, in both session and transaction mode. Alternatively, add the [IPv4 add-on](/docs/guides/platform/ipv4-address) to your project, which makes the direct connection and the dedicated pooler reachable over IPv4 instead of IPv6.
-
-### Where is the Postgres connection string in Supabase?
-
-Your connection string is in the Supabase Dashboard. Click [Connect](/dashboard/project/_?showConnect=true) at the top of the page.
-
-### Can you use Supavisor and PgBouncer together?
-
-You can use both, but don't do it unless you're trying to increase the total number of concurrent client connections. In most cases, choose either PgBouncer or Supavisor for pooled or transaction-based traffic. Direct connections remain the best choice for long-lived sessions, and shared pooler session mode is the alternative when those sessions need IPv4. Running both poolers at once increases the risk of hitting your database's maximum connection limit on smaller compute sizes.
-
-### How does the default pool size work?
-
-Supavisor and PgBouncer work independently, but both reference the same pool size setting. You can adjust it in [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard.
-
-Say you set the pool size to 30. Supavisor can then open up to 30 server-side connections to Postgres. Those 30 are shared between the session mode port, `5432`, and the transaction mode port, `6543`. Each mode can use all 30 on its own, or the two can split them, but the total across both modes cannot exceed 30.
-
-PgBouncer can open up to 30 connections under the same limit. If both poolers reach their limits at the same time, you could have as many as 60 backend connections hitting your database, in addition to any direct connections.
-
-### What is the difference between client connections and backend connections?
-
-There are two limits to understand when working with poolers.
-
-| Limit | What it counts | What sets it |
-| ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
-| Client connections | How many clients can connect to a pooler at the same time | Your [compute size's max pooler clients limit](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections) |
-| Backend connections | How many active connections a pooler opens to Postgres | The pool size for that pooler |
-
-Both limits apply independently to Supavisor and PgBouncer.
-
-```txt
-Total backend load on Postgres =
- Direct connections +
- Supavisor backend connections (≤ supavisor_pool_size) +
- PgBouncer backend connections (≤ pgbouncer_pool_size)
-≤ Postgres max connections for your compute instance
-```
-
-### What is the max pooler clients limit?
-
-The max pooler clients limit for your compute size applies separately to Supavisor and PgBouncer. One pooler reaching its client limit doesn't affect the other. When a pooler reaches this limit, it stops accepting new client connections until existing ones close. The other pooler is unaffected. You can check your connection limits in the [compute and disk documentation](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections).
-
-### Where can you see current connection usage?
-
-You can track connection usage in the [Observability](/dashboard/project/_/observability/database) section of the Supabase Dashboard. There are three reports:
-
-- **Database Connections:** total active connections by role, including direct and pooled connections.
-- **Dedicated Pooler Client Connections:** active client connections to PgBouncer.
-- **Shared Pooler (Supavisor) Client Connections:** active client connections to Supavisor.
-
-These reports are not real-time. They show the connection count from the last refresh. For up-to-the-second data, set up Grafana or query `pg_stat_activity` directly in the SQL Editor. The following queries report on current connections.
-
-```sql
--- Count connections by application and user name
-select
- count(usename),
- count(application_name),
- application_name,
- usename
-from
- pg_stat_ssl
- join pg_stat_activity on pg_stat_ssl.pid = pg_stat_activity.pid
-group by usename, application_name;
-```
-
-```sql
--- View all connections
-select
- pg_stat_activity.pid,
- ssl as ssl_connection,
- datname as database,
- usename as connected_role,
- application_name,
- client_addr,
- query,
- query_start,
- state,
- backend_start
-from pg_stat_ssl
-join pg_stat_activity on pg_stat_ssl.pid = pg_stat_activity.pid;
-```
-
-### Why are there active connections when the app is idle?
-
-Even when your application isn't making queries, some Supabase services keep persistent connections to your database. Storage, PostgREST, and the health checker all maintain long-lived connections. You usually see a small baseline of active connections from these services.
-
-### Why do connection strings have different ports?
-
-Different modes use different ports:
-
-- Direct connection: `5432`, for Postgres on your project instance
-- Dedicated pooler, transaction mode: `6543`, for PgBouncer on your project instance
-- Shared pooler, transaction mode: `6543`, for Supavisor
-- Shared pooler, session mode: `5432`, for Supavisor
-
-The port routes the connection to the right pooler and mode.
-
-### Does connection pooling affect latency?
-
-The dedicated pooler runs on the same machine as your database, so it connects with lower latency than the shared pooler, which runs on a separate server. Direct connections have no pooler overhead, but they require IPv6 unless you have the IPv4 add-on.
-
-### How to choose the right connection method?
-
-**Direct connection:**
-
-- Best for persistent backends
-- Use for migrations, `pg_dump`, and backup and management tools
-- Reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address)
-
-**Shared pooler, Supavisor:**
-
-- Best for connections from IPv4 networks. It is IPv4-only on every plan
- - Session mode for a persistent backend on an IPv4 network
- - Transaction mode for serverless functions and other short-lived tasks
-- Use for application runtime traffic, such as queries and writes
-
-**Dedicated pooler, PgBouncer, on paid plans:**
-
-- Best for high-performance apps that need dedicated resources
-- Use for application runtime traffic, such as queries and writes
-- Transaction mode only. Use the shared pooler if you need session mode
-- Reachable over IPv6, or over IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address)
-
-See the [table of connection modes](#how-to-connect-to-your-postgres-databases) at the top of this page for a quick reference, or follow the decision flow in the diagram below to choose the right option for your environment.
-
-```mermaid
-flowchart TD
- A[Where are you connecting from?] --> B[Persistent Backend]
- A --> C[Serverless / Edge]
- B --> D{IPv6 Supported? IPv4 Add-on?}
- B --> E{IPv4 Needed?}
- C --> H{IPv6 Supported? IPv4 Add-on?}
- C --> I{IPv4 Needed?}
- D --> F[Use Direct Connection]
- E --> G[Use Supavisor Session Mode]
- H --> J[Use Dedicated Pooler PgBouncer Pro]
- I --> K[Use Supavisor Transaction Mode]
-```
-
-The decision depends on where your code runs. For a persistent backend, use a direct connection if you can reach the database over IPv6 or have the IPv4 add-on. Otherwise, use the shared pooler in session mode. For serverless and edge environments, use the dedicated pooler on paid plans when IPv6 or the IPv4 add-on is available, or the shared pooler in transaction mode when you need IPv4.
+- [Tenant or user not found](/docs/guides/troubleshooting/tenant-or-user-not-found), when the pooler can't match your host and username to a project.
+- [FATAL: Password authentication failed](/docs/guides/troubleshooting/fatal-password-authentication-failed)
+- [Connection refused](/docs/guides/troubleshooting/error-connection-refused-when-trying-to-connect-to-supabase-database-hwG0Dr), which is usually an IP ban rather than an outage.
+- [Too many connections](/docs/guides/troubleshooting/too-many-connections-for-database-postgres)
diff --git a/apps/docs/content/guides/database/connecting-to-postgres/pooling-and-limits.mdx b/apps/docs/content/guides/database/connecting-to-postgres/pooling-and-limits.mdx
new file mode 100644
index 00000000000..2bb4fe24b57
--- /dev/null
+++ b/apps/docs/content/guides/database/connecting-to-postgres/pooling-and-limits.mdx
@@ -0,0 +1,108 @@
+---
+title: 'Connection pooling and limits'
+description: 'How connection pooling works, and the limits that apply to your connections'
+subtitle: 'How Supabase pools database connections, and the limits that apply to them.'
+---
+
+Learn how to choose between connection modes, size a pool, and work out why you're running out of connections. To get connected, see [Connect to your database](/docs/guides/database/connecting-to-postgres).
+
+## How connection pooling works [#how-connection-pooling-works]
+
+Connection pooling improves database performance by reusing existing connections between queries. This reduces the overhead of establishing connections and improves scalability.
+
+A Postgres connection is a long-lived session. Once established, it stays open until the client disconnects, or until the server or the network closes it. A server might make a single 10 ms query but hold its database connection for seconds or longer.
+
+A pooler sits between clients and the database and shares a small set of database connections across many clients, so a connection isn't tied up while a client sits idle.
+
+
+
+### Application-side and server-side poolers [#application-side-poolers]
+
+There are two kinds, and they work together.
+
+**Application-side poolers** are built into connection libraries and API servers, such as Prisma, SQLAlchemy, and Postgres.js. They keep a few connections open and reuse them. On a persistent backend, such as a long-running container or VM, an application-side pooler is enough on its own.
+
+**Server-side poolers**, such as Supavisor in transaction mode, run in front of the database and serve many clients. Use one when connections come from serverless or edge functions, or from anything that scales horizontally. These environments open many short-lived connections, which is the case an application-side pooler can't cover.
+
+For more on when each is needed and how to size them, see the [Supavisor FAQ](/docs/guides/troubleshooting/supavisor-faq-YyP5tI).
+
+### Shared and dedicated poolers [#shared-pooler]
+
+Supabase offers two poolers. The shared pooler, [Supavisor](https://github.com/supabase/supavisor), is multi-tenant, available on every project, and IPv4-only. The dedicated pooler, [PgBouncer](https://www.pgbouncer.org/), is available on paid plans and runs alongside your Postgres instance. Like the direct connection, it is on IPv6, or on IPv4 if the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
+
+The dedicated pooler runs on the same machine as your database, so it connects with lower latency than the shared pooler, which runs on a separate server. It also uses more of your project's compute resources. If your network supports IPv6, or you have the IPv4 add-on, use the dedicated pooler instead of the shared pooler. Direct connections have no pooler overhead, but they require IPv6 unless you have the IPv4 add-on.
+
+In most cases, choose either PgBouncer or Supavisor for pooled or transaction-based traffic. Direct connections remain the best choice for long-lived sessions, and shared pooler session mode is the alternative when those sessions need IPv4. Run both poolers at once only when you need to raise the total number of concurrent client connections, and expect a higher risk of hitting your database's maximum connection limit on smaller compute sizes.
+
+## Connection limits
+
+### Pool size [#pooler-pool-size]
+
+Pool size sets how many connections a pooler is allowed to open to Postgres. You can adjust it in [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard.
+
+Supavisor and PgBouncer read the same setting but apply it independently, so raising it raises the ceiling for both.
+
+For what the setting counts, and how it interacts with the user, database, and mode combinations connecting to your project, see [the Supavisor FAQ](/docs/guides/troubleshooting/supavisor-faq-YyP5tI).
+
+### Client connections and backend connections [#client-connections-and-backend-connections]
+
+There are two limits to understand when working with poolers.
+
+| Limit | What it counts | What sets it |
+| ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| Client connections | How many clients can connect to a pooler at the same time | Your [compute size's max pooler clients limit](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections) |
+| Backend connections | How many active connections a pooler opens to Postgres | The pool size for that pooler |
+
+Both limits apply independently to Supavisor and PgBouncer. One pooler reaching its client limit doesn't affect the other. When a pooler reaches this limit, it stops accepting new client connections until existing ones close.
+
+```txt
+Direct connections +
+ Supavisor backend connections +
+ PgBouncer backend connections
+< Postgres max connections for your compute instance
+```
+
+Stay below the maximum rather than at it. Supabase services hold their own connections, including Auth, Storage, PostgREST, and the health checker, and those come out of the same total. [Managing connections](/docs/guides/database/connection-management) covers how much headroom to leave.
+
+For the connection counts that come with each compute size, see [compute and disk](/docs/guides/platform/compute-and-disk#postgres-replication-slots-wal-senders-and-connections). For the terminology, see [Supavisor and connection terminology explained](/docs/guides/troubleshooting/supavisor-and-connection-terminology-explained-9pr_ZO).
+
+### Monitor connection usage [#monitor-connection-usage]
+
+Track connection usage in the [Observability](/dashboard/project/_/observability/database) section of the Supabase Dashboard. There are three reports:
+
+- **Database Connections:** total active connections by role, including direct and pooled connections.
+- **Dedicated Pooler Client Connections:** active client connections to PgBouncer.
+- **Shared Pooler (Supavisor) Client Connections:** active client connections to Supavisor.
+
+These reports are not real-time. They show the connection count from the last refresh. For up-to-the-second data, query `pg_stat_activity` in the SQL Editor:
+
+```sql
+-- Count connections by application and user name
+select
+ count(usename),
+ count(application_name),
+ application_name,
+ usename
+from
+ pg_stat_ssl
+ join pg_stat_activity on pg_stat_ssl.pid = pg_stat_activity.pid
+group by usename, application_name;
+```
+
+To list every connection with its state and query, and to read which Supabase service each role belongs to, see [Managing connections](/docs/guides/database/connection-management#observing-live-connections).
+
+## Related
+
+- [Connect to your database](/docs/guides/database/connecting-to-postgres)
+- [Managing connections](/docs/guides/database/connection-management)
+- [Supavisor FAQ](/docs/guides/troubleshooting/supavisor-faq-YyP5tI)
diff --git a/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx b/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
index 45a51a39244..36642ee20a3 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
@@ -7,12 +7,16 @@ subtitle: 'Choose a driver for connecting to your Postgres database from a serve
Learn how to connect to your Postgres database from a serverless environment. The driver you use depends on which runtime your code runs in.
-[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api), so it works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres#poolers) and can serve thousands of simultaneous requests, which suits serverless workloads.
+[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api), so it works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works) and can serve thousands of simultaneous requests, which suits serverless workloads.
+
+If you connect with a Postgres client instead, use the [shared pooler in transaction mode](/docs/guides/database/connecting-to-postgres#pooler-transaction-mode). Transaction mode doesn't support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html), so [turn them off](https://github.com/orgs/supabase/discussions/28239) in your connection library.
## Vercel Edge Functions
Vercel's [Edge runtime](https://vercel.com/docs/functions/runtimes/edge-runtime) runs on the [V8 engine](https://v8.dev/) and exposes a limited set of Web Standard APIs.
+Start from a deploy template, or configure the connection yourself.
+
### Quickstart
Choose one of these Vercel Deploy Templates. They use the [Vercel Deploy Integration](https://vercel.com/integrations/supabase) to configure your connection strings as environment variables on your Vercel project.
@@ -125,6 +129,8 @@ export { sql } from 'kysely'
Cloudflare's Workers runtime also uses the [V8 engine](https://v8.dev/), but it provides polyfills for a subset of Node.js APIs and the [TCP Sockets API](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/). That gives you three options:
+### Drivers [#cloudflare-workers-drivers]
+
- [supabase-js](https://developers.cloudflare.com/workers/databases/native-integrations/supabase/)
- [Postgres.js](https://github.com/porsager/postgres?tab=readme-ov-file#cloudflare-workers-support)
- [node-postgres](https://developers.cloudflare.com/workers/tutorials/postgres/)
@@ -133,6 +139,8 @@ Cloudflare's Workers runtime also uses the [V8 engine](https://v8.dev/), but it
Supabase Edge Functions use the [Deno runtime](https://deno.com/), which has native support for TCP connections. You can choose any of these clients:
+### Drivers [#supabase-edge-functions-drivers]
+
- [supabase-js](/docs/guides/functions/connect-to-postgres#using-supabase-js)
- [Deno Postgres driver](/docs/guides/functions/connect-to-postgres#using-a-postgres-client)
- [Postgres.js](https://github.com/porsager/postgres)
diff --git a/apps/docs/content/guides/database/postgres/dropping-all-tables-in-schema.mdx b/apps/docs/content/guides/database/postgres/dropping-all-tables-in-schema.mdx
index 9af8ef0795c..bfacb3be120 100644
--- a/apps/docs/content/guides/database/postgres/dropping-all-tables-in-schema.mdx
+++ b/apps/docs/content/guides/database/postgres/dropping-all-tables-in-schema.mdx
@@ -25,4 +25,4 @@ end $$;
This query works by listing out all the tables in the given schema and then executing a `drop table` for each (hence the `for... loop`).
-You can run this query using the [SQL Editor](/dashboard/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections).
+You can run this query using the [SQL Editor](/dashboard/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connection).
diff --git a/apps/docs/content/guides/database/postgres/first-row-in-group.mdx b/apps/docs/content/guides/database/postgres/first-row-in-group.mdx
index b20a3eaa8b8..12333740ef6 100644
--- a/apps/docs/content/guides/database/postgres/first-row-in-group.mdx
+++ b/apps/docs/content/guides/database/postgres/first-row-in-group.mdx
@@ -42,4 +42,4 @@ The important bits here are:
- The `desc` keyword to order the `points` from highest to lowest.
- The `distinct` keyword that tells Postgres to only return a single row per team.
-This query can also be executed via `psql` or any other query editor if you prefer to [connect directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections).
+This query can also be executed via `psql` or any other query editor if you prefer to [connect directly to the database](/docs/guides/database/connecting-to-postgres#direct-connection).
diff --git a/apps/docs/content/guides/database/postgres/indexes.mdx b/apps/docs/content/guides/database/postgres/indexes.mdx
index 4982a26e37d..5cd249b2ba7 100644
--- a/apps/docs/content/guides/database/postgres/indexes.mdx
+++ b/apps/docs/content/guides/database/postgres/indexes.mdx
@@ -28,7 +28,7 @@ create table persons (
-All the queries in this guide can be run using the [SQL Editor](/dashboard/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections).
+All the queries in this guide can be run using the [SQL Editor](/dashboard/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connection).
diff --git a/apps/docs/content/guides/database/postgres/timeouts.mdx b/apps/docs/content/guides/database/postgres/timeouts.mdx
index 3415ac23e93..c831515f0fe 100644
--- a/apps/docs/content/guides/database/postgres/timeouts.mdx
+++ b/apps/docs/content/guides/database/postgres/timeouts.mdx
@@ -5,7 +5,7 @@ subtitle: Extend database timeouts to execute longer transactions
-Dashboard and [Client](/docs/guides/api/rest/client-libs) queries have a max-configurable timeout of 60 seconds. For longer transactions, use [Supavisor or direct connections](/docs/guides/database/connecting-to-postgres#quick-summary).
+Dashboard and [Client](/docs/guides/api/rest/client-libs) queries have a max-configurable timeout of 60 seconds. For longer transactions, use [Supavisor or direct connections](/docs/guides/database/connecting-to-postgres#choose-a-connection-method).
diff --git a/apps/docs/content/guides/database/postgres/which-version-of-postgres.mdx b/apps/docs/content/guides/database/postgres/which-version-of-postgres.mdx
index 5d51d77f916..f6516547146 100644
--- a/apps/docs/content/guides/database/postgres/which-version-of-postgres.mdx
+++ b/apps/docs/content/guides/database/postgres/which-version-of-postgres.mdx
@@ -19,4 +19,4 @@ Which should return something like:
PostgreSQL 15.1 on aarch64-unknown-linux-gnu, compiled by gcc (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0, 64-bit
```
-This query can also be executed via `psql` or any other query editor if you prefer to [connect directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections).
+This query can also be executed via `psql` or any other query editor if you prefer to [connect directly to the database](/docs/guides/database/connecting-to-postgres#direct-connection).
diff --git a/apps/docs/content/guides/database/tables.mdx b/apps/docs/content/guides/database/tables.mdx
index a2f32f4a4aa..22b348dbfc7 100644
--- a/apps/docs/content/guides/database/tables.mdx
+++ b/apps/docs/content/guides/database/tables.mdx
@@ -361,7 +361,7 @@ For example, if you wanted to load a CSV file into your movies table:
"Return of the Jedi", "After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star."
```
-You would [connect](../../guides/database/connecting-to-postgres#direct-connections) to your database directly and load the file with the COPY command:
+You would [connect](../../guides/database/connecting-to-postgres#direct-connection) to your database directly and load the file with the COPY command:
```bash
psql -h DATABASE_URL -p 5432 -d postgres -U postgres \
diff --git a/apps/docs/content/guides/observability/reports.mdx b/apps/docs/content/guides/observability/reports.mdx
index 3daa92daeaf..ab6e6c63b5a 100644
--- a/apps/docs/content/guides/observability/reports.mdx
+++ b/apps/docs/content/guides/observability/reports.mdx
@@ -169,12 +169,12 @@ Why this matters for Postgres:
Actions you can take:
-| Action | Description |
-| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
-| [Use connection pooling](/docs/guides/database/connecting-to-postgres#how-connection-pooling-works) | Route clients through Supavisor or PgBouncer to cap fork-driven commit pressure. |
-| Lower `max_connections` or per-pool sizes | Reduce the upper bound on concurrent backends so spikes cannot exceed the Commit limit. |
-| [Tune `work_mem`](https://pgtune.leopard.in.ua) | Reduce per-operation memory allocations on workloads with many concurrent queries. |
-| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Raise both physical RAM and the Commit limit so the workload fits with headroom. |
+| Action | Description |
+| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
+| [Use connection pooling](/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works) | Route clients through Supavisor or PgBouncer to cap fork-driven commit pressure. |
+| Lower `max_connections` or per-pool sizes | Reduce the upper bound on concurrent backends so spikes cannot exceed the Commit limit. |
+| [Tune `work_mem`](https://pgtune.leopard.in.ua) | Reduce per-operation memory allocations on workloads with many concurrent queries. |
+| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Raise both physical RAM and the Commit limit so the workload fits with headroom. |
### CPU usage
@@ -345,11 +345,11 @@ How it helps debug issues:
Actions you can take:
-| Action | Description |
-| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
-| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Increase maximum connection limits |
-| Implement [connection pooling](/docs/guides/database/connecting-to-postgres#shared-pooler) | Optimize connection management for high direct connection usage |
-| Review application code | Ensure proper connection handling and cleanup |
+| Action | Description |
+| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
+| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Increase maximum connection limits |
+| Implement [connection pooling](/docs/guides/database/connecting-to-postgres#choose-a-connection-method) | Optimize connection management for high direct connection usage |
+| Review application code | Ensure proper connection handling and cleanup |
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
@@ -369,11 +369,11 @@ How it helps debug issues:
Actions you can take:
-| Action | Description |
-| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
-| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Increase maximum connection limits |
-| Implement [connection pooling](/docs/guides/database/connecting-to-postgres#shared-pooler) | Optimize connection management for high direct connection usage |
-| Review application code | Ensure proper connection handling and cleanup |
+| Action | Description |
+| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
+| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Increase maximum connection limits |
+| Implement [connection pooling](/docs/guides/database/connecting-to-postgres#choose-a-connection-method) | Optimize connection management for high direct connection usage |
+| Review application code | Ensure proper connection handling and cleanup |
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
@@ -393,11 +393,11 @@ How it helps debug issues:
Actions you can take:
-| Action | Description |
-| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
-| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Increase maximum connection limits |
-| Implement [connection pooling](/docs/guides/database/connecting-to-postgres#shared-pooler) | Optimize connection management for high direct connection usage |
-| Review application code | Ensure proper connection handling and cleanup |
+| Action | Description |
+| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
+| [Upgrade compute size](/docs/guides/platform/compute-and-disk#compute-size) | Increase maximum connection limits |
+| Implement [connection pooling](/docs/guides/database/connecting-to-postgres#choose-a-connection-method) | Optimize connection management for high direct connection usage |
+| Review application code | Ensure proper connection handling and cleanup |
### Disk Usage
diff --git a/apps/docs/content/guides/platform/manage-your-usage/egress.mdx b/apps/docs/content/guides/platform/manage-your-usage/egress.mdx
index b05dbb00359..09e49705282 100644
--- a/apps/docs/content/guides/platform/manage-your-usage/egress.mdx
+++ b/apps/docs/content/guides/platform/manage-your-usage/egress.mdx
@@ -47,7 +47,7 @@ Data pushed to clients via Supabase Realtime for subscribed events.
Data sent to the client when using the shared connection pooler (Supavisor) to access your database. When using the shared connection pooler, we do not count database egress, as this would otherwise count double (Database -> Shared Pooler + Shared Pooler -> Client).
-**Example:** You are using our [shared connection pooler](/docs/guides/database/connecting-to-postgres#shared-pooler) and you query a list of invoices in your backend. The data returned from that query is contributing to Shared Pooler Egress.
+**Example:** You are using our [shared connection pooler](/docs/guides/database/connecting-to-postgres/pooling-and-limits#shared-pooler) and you query a list of invoices in your backend. The data returned from that query is contributing to Shared Pooler Egress.
### Log Drain Egress
diff --git a/apps/docs/content/guides/platform/performance.mdx b/apps/docs/content/guides/platform/performance.mdx
index 246ad18e91d..d0ff40d8d37 100644
--- a/apps/docs/content/guides/platform/performance.mdx
+++ b/apps/docs/content/guides/platform/performance.mdx
@@ -31,7 +31,7 @@ In such a scenario, you can consider:
You can use the [pg_stat_activity](https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW) view to debug which clients are holding open connections on your DB. `pg_stat_activity` only exposes information on direct connections to the database. Information on the number of connections to Supavisor is available [via the metrics endpoint](../telemetry/metrics).
-Depending on the clients involved, you might be able to configure them to work with fewer connections (e.g. by imposing a limit on the maximum number of connections they're allowed to use), or shift specific workloads to connect via [Supavisor](/docs/guides/database/connecting-to-postgres#poolers) instead. Transient workflows, which can scale up and down rapidly in response to traffic (e.g. serverless functions), can especially benefit from using a connection pooler rather than connecting to the DB directly.
+Depending on the clients involved, you might be able to configure them to work with fewer connections (e.g. by imposing a limit on the maximum number of connections they're allowed to use), or shift specific workloads to connect via [Supavisor](/docs/guides/database/connecting-to-postgres/pooling-and-limits#shared-pooler) instead. Transient workflows, which can scale up and down rapidly in response to traffic (e.g. serverless functions), can especially benefit from using a connection pooler rather than connecting to the DB directly.
### Allowing higher number of connections
diff --git a/apps/docs/content/troubleshooting/database-error-remaining-connection-slots-are-reserved-for-non-replication-superuser-connections-3V3nIb.mdx b/apps/docs/content/troubleshooting/database-error-remaining-connection-slots-are-reserved-for-non-replication-superuser-connections-3V3nIb.mdx
index 6a200efb796..ac619a13b85 100644
--- a/apps/docs/content/troubleshooting/database-error-remaining-connection-slots-are-reserved-for-non-replication-superuser-connections-3V3nIb.mdx
+++ b/apps/docs/content/troubleshooting/database-error-remaining-connection-slots-are-reserved-for-non-replication-superuser-connections-3V3nIb.mdx
@@ -15,6 +15,6 @@ This error usually occurs when the database reaches the maximum number of connec
To overcome this, the connections need to be optimized as mentioned here: https://supabase.com/docs/guides/platform/performance#optimizing-the-number-of-connections
Additionally, you can try using the connection pool to help solve this issue:
-https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler
+https://supabase.com/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works
If you're already using connection pooling and still hitting the maximum connections, then it is suggested to upgrade your compute add-on that allows more connections: https://supabase.com/docs/guides/platform/compute-and-disk
diff --git a/apps/docs/content/troubleshooting/fatal-password-authentication-failed.mdx b/apps/docs/content/troubleshooting/fatal-password-authentication-failed.mdx
new file mode 100644
index 00000000000..5dbb9e869d9
--- /dev/null
+++ b/apps/docs/content/troubleshooting/fatal-password-authentication-failed.mdx
@@ -0,0 +1,31 @@
+---
+title = "FATAL: Password authentication failed"
+date_created = "2026-09-02T00:00:00+00:00"
+topics = [ "database", "supavisor" ]
+keywords = [ "password", "authentication", "credentials", "fail2ban" ]
+[[errors]]
+message = "FATAL: password authentication failed for user"
+---
+
+A `FATAL: Password authentication failed` error means Postgres rejected your credentials. Check the username before the password, because the username is the more common mistake.
+
+## Check the username first
+
+The username depends on how you connect:
+
+- Direct connections and the dedicated pooler use `postgres`.
+- Shared pooler connections use `postgres.[PROJECT-REF]`.
+
+Supplying the wrong one usually produces [Tenant or user not found](/docs/guides/troubleshooting/tenant-or-user-not-found) rather than this error, but a valid username with the wrong password lands here.
+
+## Check the password
+
+Reserved characters in a password have to be [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding) when they appear in a connection string. This covers `&`, `#`, `?`, and spaces, among others. A password that works in a GUI field can fail in a URI for this reason alone.
+
+If you don't have the password, [reset it](/docs/guides/troubleshooting/how-do-i-reset-my-supabase-database-password-oTs5sB) in [Database settings](/dashboard/project/_/database/settings).
+
+## Repeated failures cause a different error
+
+Repeated authentication failures from the same address get that address banned. Once banned, connections stop failing with this error and start failing with [connection refused](/docs/guides/troubleshooting/error-connection-refused-when-trying-to-connect-to-supabase-database-hwG0Dr) instead.
+
+If your error changes from authentication failed to connection refused while you're testing credentials, you're banned rather than locked out. That page has the unban procedure.
diff --git a/apps/docs/content/troubleshooting/http-api-issues.mdx b/apps/docs/content/troubleshooting/http-api-issues.mdx
index de8836ef69d..3ac17e74db1 100644
--- a/apps/docs/content/troubleshooting/http-api-issues.mdx
+++ b/apps/docs/content/troubleshooting/http-api-issues.mdx
@@ -47,7 +47,7 @@ Errors about too many open connections can be _temporarily_ resolved by [restart
- If you're receiving a `No more connections allowed (max_client_conn)` error:
- Configure your applications and services to [use fewer connections](../platform/performance#configuring-clients-to-use-fewer-connections).
- [Upgrade](/dashboard/project/_/settings/infrastructure) to a [larger compute add-on](../platform/compute-and-disk) to increase the number of available connections.
-- If you're receiving a `sorry, too many clients already` or `remaining connection slots are reserved for non-replication superuser connections` error message in addition to the above suggestions, switch to using the [connection pooler](/docs/guides/database/connecting-to-postgres#connection-pool) instead.
+- If you're receiving a `sorry, too many clients already` or `remaining connection slots are reserved for non-replication superuser connections` error message in addition to the above suggestions, switch to using the [connection pooler](/docs/guides/database/connecting-to-postgres#choose-a-connection-method) instead.
### Connection refused
diff --git a/apps/docs/content/troubleshooting/tenant-or-user-not-found.mdx b/apps/docs/content/troubleshooting/tenant-or-user-not-found.mdx
new file mode 100644
index 00000000000..f3982d98c27
--- /dev/null
+++ b/apps/docs/content/troubleshooting/tenant-or-user-not-found.mdx
@@ -0,0 +1,34 @@
+---
+title = "Tenant or user not found when connecting through the shared pooler"
+date_created = "2026-09-02T00:00:00+00:00"
+topics = [ "database", "supavisor" ]
+keywords = [ "pooler", "supavisor", "tenant", "hostname", "username" ]
+[[errors]]
+message = "Tenant or user not found"
+[[errors]]
+message = "FATAL: (ENOTFOUND) tenant/user postgres. not found"
+---
+
+A `Tenant or user not found` error means the shared pooler couldn't match your host and username to a project. Some drivers report it as `FATAL: (ENOTFOUND) tenant/user postgres. not found`.
+
+The error reads like a credentials problem, so it often sends people to reset a password that was never wrong. The cause is almost always the host or the username, not the password.
+
+## The host was typed instead of copied
+
+Shared pooler hosts look like `aws-1-us-east-2.pooler.supabase.com`. The number is a pooler cluster index, not part of the region name, and a region can have more than one. `aws-0` is not a safe default, and you can't work the host out from your region.
+
+Copy the host from the [Connect](/dashboard/project/_?showConnect=true) dialog rather than composing it.
+
+## The username is missing the project ref
+
+Shared pooler connections use `postgres.[PROJECT-REF]`, not `postgres`. Direct connections and the dedicated pooler use `postgres`.
+
+If you connect as a custom role through the shared pooler, the username is `[ROLE].[PROJECT-REF]`. Supplying only the role name, with no project ref, produces this error rather than an authentication failure.
+
+## How to fix it
+
+1. Open the [Connect](/dashboard/project/_?showConnect=true) dialog in the Supabase Dashboard.
+2. Choose **Session pooler** or **Transaction pooler**.
+3. Copy the whole string, and replace only the password placeholder.
+
+For the full set of hosts, ports, and usernames, see [Connect to your database](/docs/guides/database/connecting-to-postgres).
From 165582b08e43b0d962a8316913eb3529443bdd0c Mon Sep 17 00:00:00 2001
From: Palash Awasthi <159032387+PalashAwasthi05@users.noreply.github.com>
Date: Wed, 9 Sep 2026 16:35:24 -0700
Subject: [PATCH 005/614] docs: add Reflex framework quickstart (#45441)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Docs update — adds a new framework quickstart.
## What is the current behavior?
The Framework Quickstarts section under Getting Started covers Next.js,
Nuxt, React, Flask, and others, but doesn't include Reflex
(https://reflex.dev), an open-source Python web framework that compiles
to React. Python developers building full-stack apps with Reflex have to
piece together the Supabase setup from the general Python reference
rather than following a quickstart.
## What is the new behavior?
Adds a Reflex framework quickstart at
`apps/docs/content/guides/getting-started/quickstarts/reflex.mdx`,
mirroring the structure of the existing Flask quickstart: same DB setup
partial (`quickstart_db_setup.mdx`), same env var step using
`ProjectConfigVariables`, same six-step `StepHikeCompact` shape. A nav
entry is added directly after Flask in `NavigationMenu.constants.ts`,
gated on `!jsOnly` to match Flask's pattern.
Verified end-to-end against a fresh Supabase project: ran every command
in the docs literally, including the full SQL from
`quickstart_db_setup.mdx` (with the `grant select on public.instruments
to anon` line). The rendered Reflex app shows the Instruments heading
with all three seeded rows. No console errors, no event-loop warnings.
## Additional context
Conventions used in the quickstart:
- Uses `uv` (`uv init`, `uv add`, `uv run`) rather than pip + venv. This
follows the broader docs pattern of each quickstart using its
framework's idiomatic tooling (Next.js → npx, RedwoodJS → yarn, Laravel
→ composer, Flutter → pubspec.yaml). It also matches what Reflex's own
`reflex init` post-install message recommends. `uv add` produces a
`pyproject.toml` and `uv.lock` so users can rebuild deterministically
with `uv sync`.
- The Supabase client is constructed via `acreate_client` with a
lazy-init pattern, and the event handler is `async def`. This avoids
blocking Reflex's event loop on the HTTP request.
In touch with the Supabase team on this — happy to iterate on copy or
scope based on review.
Pre-flight: ran pnpm run format locally, which passed cleanly. Did not
run pnpm run build locally — hit a Windows/CRLF-related TOML parsing
failure in an unrelated troubleshooting frontmatter file
(apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx)
during page data collection. Relying on Vercel preview to validate the
build.
## Summary by CodeRabbit
* **New Features**
* Added Reflex (Python) and Spring Boot to the framework quickstarts.
* Reorganized quickstart listings for clearer framework navigation and
grouping.
* **Documentation**
* Added a Reflex quickstart guide covering Supabase setup, environment
variables, asynchronous data loading, error handling, and running the
app.
* Added Reflex-specific AI guidance for configuring Supabase projects.
---------
Co-authored-by: Nik Richers
---
.../components/ContentListings/iconChip.tsx | 13 +-
apps/docs/components/FrameworkQuickstarts.tsx | 11 ++
.../NavigationMenu.constants.ts | 65 +++++----
.../getting-started/quickstarts/reflex.mdx | 130 ++++++++++++++++++
apps/docs/data/ai-prompts.data.ts | 14 ++
.../content-listings/getting-started.data.ts | 120 +++++++++-------
.../public/img/icons/reflex-icon-light.svg | 8 ++
apps/docs/public/img/icons/reflex-icon.svg | 8 ++
.../public/img/icons/spring-boot-icon.svg | 6 +
supa-mdx-lint/Rule001HeadingCase.toml | 1 +
10 files changed, 292 insertions(+), 84 deletions(-)
create mode 100644 apps/docs/content/guides/getting-started/quickstarts/reflex.mdx
create mode 100644 apps/docs/public/img/icons/reflex-icon-light.svg
create mode 100644 apps/docs/public/img/icons/reflex-icon.svg
create mode 100644 apps/docs/public/img/icons/spring-boot-icon.svg
diff --git a/apps/docs/components/ContentListings/iconChip.tsx b/apps/docs/components/ContentListings/iconChip.tsx
index 8fe440b18bf..e3af1bed8e9 100644
--- a/apps/docs/components/ContentListings/iconChip.tsx
+++ b/apps/docs/components/ContentListings/iconChip.tsx
@@ -1,11 +1,10 @@
'use client'
+import type { ContentListingIcon } from '~/lib/content-listings.schema'
import { Axiom, Datadog, Grafana, Last9, Otlp, Sentry } from 'icons'
import { Braces, Cloud, Server } from 'lucide-react'
import type { ReactNode } from 'react'
-import type { ContentListingIcon } from '~/lib/content-listings.schema'
-
type IconKind = Extract['kind']
const ICON_KIND_COMPONENTS: Record = {
@@ -33,5 +32,15 @@ export function resolveContentListingIcon(
)
}
+ // GlassPanel picks -light via next-themes; class-based dark: is more reliable for
+ // Reflex (near-black light mark vanishes on dark cards if theme resolution lags).
+ if (typeof icon === 'string' && icon.endsWith('/reflex-icon')) {
+ return (
+ <>
+
+
+ >
+ )
+ }
return icon
}
diff --git a/apps/docs/components/FrameworkQuickstarts.tsx b/apps/docs/components/FrameworkQuickstarts.tsx
index 398b099f5de..66760243aab 100644
--- a/apps/docs/components/FrameworkQuickstarts.tsx
+++ b/apps/docs/components/FrameworkQuickstarts.tsx
@@ -96,6 +96,12 @@ const frameworks = [
icon: '/docs/img/icons/python-icon',
href: '/guides/getting-started/quickstarts/flask',
},
+ {
+ name: 'Reflex',
+ icon: '/docs/img/icons/reflex-icon',
+ href: '/guides/getting-started/quickstarts/reflex',
+ hasLightIcon: true,
+ },
{
name: 'Laravel',
icon: '/docs/img/icons/laravel-icon',
@@ -106,6 +112,11 @@ const frameworks = [
icon: '/docs/img/icons/rails-icon',
href: '/guides/getting-started/quickstarts/ruby-on-rails',
},
+ {
+ name: 'Spring Boot',
+ icon: '/docs/img/icons/spring-boot-icon',
+ href: '/guides/getting-started/quickstarts/spring-boot',
+ },
]
export function FrameworkQuickstarts({ labelledBy }: { labelledBy?: string }) {
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 6a97bf6aa02..613c942a50f 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -366,38 +366,50 @@ export const gettingstarted: NavMenuConstant = {
name: 'Framework Quickstarts',
enabled: frameworkQuickstartsEnabled,
items: [
+ {
+ name: 'React',
+ url: '/guides/getting-started/quickstarts/reactjs',
+ },
{
name: 'Next.js',
url: '/guides/getting-started/quickstarts/nextjs',
},
{
- name: 'React',
- url: '/guides/getting-started/quickstarts/reactjs',
+ name: 'TanStack Start',
+ url: '/guides/getting-started/quickstarts/tanstack' as `/${string}`,
},
{
name: 'Astro',
url: '/guides/getting-started/quickstarts/astrojs',
},
- {
- name: 'Nuxt',
- url: '/guides/getting-started/quickstarts/nuxtjs',
- },
{
name: 'Vue',
url: '/guides/getting-started/quickstarts/vue',
},
+ {
+ name: 'Nuxt',
+ url: '/guides/getting-started/quickstarts/nuxtjs',
+ },
+ {
+ name: 'SvelteKit',
+ url: '/guides/getting-started/quickstarts/sveltekit' as `/${string}`,
+ },
+ {
+ name: 'SolidJS',
+ url: '/guides/getting-started/quickstarts/solidjs',
+ },
+ {
+ name: 'RedwoodJS',
+ url: '/guides/getting-started/quickstarts/redwoodjs' as `/${string}`,
+ },
+ {
+ name: 'Refine',
+ url: '/guides/getting-started/quickstarts/refine',
+ },
{
name: 'Hono',
url: '/guides/getting-started/quickstarts/hono',
},
- {
- name: 'Expo React Native',
- url: '/guides/getting-started/quickstarts/expo-react-native',
- },
- {
- name: 'Flutter',
- url: '/guides/getting-started/quickstarts/flutter',
- },
{
name: 'iOS SwiftUI',
url: '/guides/getting-started/quickstarts/ios-swiftui',
@@ -407,8 +419,12 @@ export const gettingstarted: NavMenuConstant = {
url: '/guides/getting-started/quickstarts/kotlin' as `/${string}`,
},
{
- name: 'SvelteKit',
- url: '/guides/getting-started/quickstarts/sveltekit' as `/${string}`,
+ name: 'Expo React Native',
+ url: '/guides/getting-started/quickstarts/expo-react-native',
+ },
+ {
+ name: 'Flutter',
+ url: '/guides/getting-started/quickstarts/flutter',
},
{
name: 'Flask (Python)',
@@ -416,8 +432,9 @@ export const gettingstarted: NavMenuConstant = {
enabled: !jsOnly,
},
{
- name: 'TanStack Start',
- url: '/guides/getting-started/quickstarts/tanstack' as `/${string}`,
+ name: 'Reflex (Python)',
+ url: '/guides/getting-started/quickstarts/reflex' as `/${string}`,
+ enabled: !jsOnly,
},
{
name: 'Laravel PHP',
@@ -434,18 +451,6 @@ export const gettingstarted: NavMenuConstant = {
url: '/guides/getting-started/quickstarts/spring-boot' as `/${string}`,
enabled: !jsOnly,
},
- {
- name: 'SolidJS',
- url: '/guides/getting-started/quickstarts/solidjs',
- },
- {
- name: 'RedwoodJS',
- url: '/guides/getting-started/quickstarts/redwoodjs' as `/${string}`,
- },
- {
- name: 'Refine',
- url: '/guides/getting-started/quickstarts/refine',
- },
],
},
{
diff --git a/apps/docs/content/guides/getting-started/quickstarts/reflex.mdx b/apps/docs/content/guides/getting-started/quickstarts/reflex.mdx
new file mode 100644
index 00000000000..efbf157d086
--- /dev/null
+++ b/apps/docs/content/guides/getting-started/quickstarts/reflex.mdx
@@ -0,0 +1,130 @@
+---
+title: 'Use Supabase with Reflex'
+subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Reflex app.'
+breadcrumb: 'Framework Quickstarts'
+---
+
+
+
+<$Partial path="quickstart_db_setup.mdx" />
+
+## 3. Create a Reflex app
+
+Create a new directory for your Reflex app, initialize a project with `uv`, add Reflex as a dependency, and scaffold a blank app with `--template blank`.
+
+```bash
+mkdir my_app && cd my_app
+uv init
+uv add reflex
+uv run reflex init --template blank
+```
+
+## 4. Set up AI tooling (optional)
+
+<$Partial path="quickstart_ai_tooling.mdx" />
+
+## 5. Install the Supabase client library
+
+The fastest way to get started is to use the `supabase-py` client library, which provides a convenient interface for working with Supabase from a Reflex app.
+
+Install `supabase-py` and `python-dotenv` to load environment variables.
+
+```bash
+uv add supabase python-dotenv
+```
+
+## 6. Declare Supabase environment variables
+
+Create a `.env` file in your project root and populate it with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&connectTab=frameworks):
+
+
+
+```text name=.env
+SUPABASE_URL=
+SUPABASE_PUBLISHABLE_KEY=
+```
+
+<$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "" }} />
+
+## 7. Create the Supabase client
+
+Open `my_app/my_app.py` and add the following code to create an async Supabase client from your environment variables. Reflex's event handlers run inside an async event loop, so the client is created once with `acreate_client` and reused across requests, rather than recreated per page load.
+
+```python name=my_app/my_app.py
+import os
+import reflex as rx
+from supabase import acreate_client, AsyncClient
+from dotenv import load_dotenv
+
+load_dotenv()
+
+_supabase: AsyncClient | None = None
+
+
+async def get_supabase() -> AsyncClient:
+ global _supabase
+ if _supabase is None:
+ _supabase = await acreate_client(
+ os.environ.get("SUPABASE_URL"),
+ os.environ.get("SUPABASE_PUBLISHABLE_KEY"),
+ )
+ return _supabase
+```
+
+## 8. Query data from the app
+
+Add a `State` class that holds the query result, an async event handler that fetches data from your `instruments` table, and a page that renders the result. `on_load` triggers the handler when the page is opened.
+
+```python name=my_app/my_app.py
+from postgrest import APIError
+
+
+class State(rx.State):
+ instruments: list[dict] = []
+ error: str = ""
+
+ async def load_instruments(self):
+ supabase = await get_supabase()
+ try:
+ response = await supabase.table("instruments").select("*").execute()
+ except APIError as error:
+ self.error = f"Error loading instruments: {error.message}"
+ return
+ self.instruments = response.data
+
+
+def index() -> rx.Component:
+ return rx.container(
+ rx.heading("Instruments"),
+ rx.cond(
+ State.error,
+ rx.text(State.error),
+ rx.foreach(
+ State.instruments,
+ lambda instrument: rx.text(instrument["name"]),
+ ),
+ ),
+ )
+
+
+app = rx.App()
+app.add_page(index, on_load=State.load_instruments)
+```
+
+## 9. Start the app
+
+Run the Reflex development server, go to http://localhost:3000 in a browser, and you should see the list of instruments.
+
+```bash
+uv run reflex run
+```
+
+<$Partial path="quickstart_going_to_production.mdx" />
+
+## Next steps
+
+- Set up [Auth](/docs/guides/auth) for your app
+- [Insert more data](/docs/guides/database/import-data) into your database
+- Upload and serve static files using [Storage](/docs/guides/storage)
diff --git a/apps/docs/data/ai-prompts.data.ts b/apps/docs/data/ai-prompts.data.ts
index 66081d5bcb5..6700d988420 100644
--- a/apps/docs/data/ai-prompts.data.ts
+++ b/apps/docs/data/ai-prompts.data.ts
@@ -179,6 +179,20 @@ database.new and run the instruments table SQL. Then:
REFERENCE
https://supabase.com/docs/guides/getting-started/quickstarts/refine.md`,
+ reflex: `Help me add Supabase to my Reflex project. Create a Supabase project at
+database.new and run the instruments table SQL. Then:
+1. Run \`uv init\` and \`uv add reflex\`, then \`uv run reflex init --template blank\`
+ to scaffold the app.
+2. Run \`uv add supabase python-dotenv\`.
+3. Create \`.env\` and set \`SUPABASE_URL\` and \`SUPABASE_PUBLISHABLE_KEY\`.
+4. In \`my_app/my_app.py\`, create a single async Supabase client with
+ \`acreate_client\` (one client per process, not recreated per request) and an
+ \`rx.State\` event handler that queries and renders the instruments table,
+ handling \`postgrest.APIError\`.
+5. Run \`uv run reflex run\` and open http://localhost:3000.
+
+REFERENCE
+https://supabase.com/docs/guides/getting-started/quickstarts/reflex.md`,
'ruby-on-rails': `Help me add Supabase to my Ruby on Rails project. Create a Supabase project at
database.new. Then:
1. Run \`rails new blog -d=postgresql\` to scaffold a new Rails project.
diff --git a/apps/docs/data/content-listings/getting-started.data.ts b/apps/docs/data/content-listings/getting-started.data.ts
index 07e7320a1df..a450b2186b7 100644
--- a/apps/docs/data/content-listings/getting-started.data.ts
+++ b/apps/docs/data/content-listings/getting-started.data.ts
@@ -76,12 +76,11 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Full-stack React with server rendering, wired to Supabase Postgres and cookie-based auth.',
},
{
- title: 'Nuxt',
- href: '/guides/getting-started/quickstarts/nuxtjs',
- icon: '/docs/img/icons/nuxt-icon',
- hasLightIcon: false,
- description:
- 'Full-stack Vue with server rendering, reading Postgres through a Supabase composable.',
+ title: 'TanStack Start',
+ href: '/guides/getting-started/quickstarts/tanstack',
+ icon: '/docs/img/icons/tanstack-icon',
+ hasLightIcon: true,
+ description: 'Type-safe full-stack React that queries Supabase Postgres in server functions.',
},
{
title: 'Astro',
@@ -92,12 +91,35 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Content-driven sites that render on the server and pull Supabase Postgres data per request.',
},
{
- title: 'Hono',
- href: '/guides/getting-started/quickstarts/hono',
- icon: '/docs/img/icons/hono-icon',
+ title: 'Vue',
+ href: '/guides/getting-started/quickstarts/vue',
+ icon: '/docs/img/icons/vuejs-icon',
hasLightIcon: false,
description:
- 'Lightweight web APIs with Supabase Auth anonymous sign-in and RLS-protected reads.',
+ 'Build single-page apps with the Vue composition API, backed by Supabase Postgres.',
+ },
+ {
+ title: 'Nuxt',
+ href: '/guides/getting-started/quickstarts/nuxtjs',
+ icon: '/docs/img/icons/nuxt-icon',
+ hasLightIcon: false,
+ description:
+ 'Full-stack Vue with server rendering, reading Postgres through a Supabase composable.',
+ },
+ {
+ title: 'SvelteKit',
+ href: '/guides/getting-started/quickstarts/sveltekit',
+ icon: '/docs/img/icons/svelte-icon',
+ hasLightIcon: false,
+ description: 'Full-stack Svelte that loads Supabase Postgres data in server load functions.',
+ },
+ {
+ title: 'SolidJS',
+ href: '/guides/getting-started/quickstarts/solidjs',
+ icon: '/docs/img/icons/solidjs-icon',
+ hasLightIcon: false,
+ description:
+ 'Fine-grained reactive UIs that load Supabase Postgres data with Solid resources.',
},
{
title: 'RedwoodJS',
@@ -108,20 +130,20 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Full-stack React and GraphQL, with Prisma migrations against your Supabase Postgres database.',
},
{
- title: 'Expo React Native',
- href: '/guides/getting-started/quickstarts/expo-react-native',
- icon: '/docs/img/icons/expo-icon',
- hasLightIcon: true,
+ title: 'Refine',
+ href: '/guides/getting-started/quickstarts/refine',
+ icon: '/docs/img/icons/refine-icon',
+ hasLightIcon: false,
description:
- 'Ship iOS and Android from one React Native codebase, backed by Supabase Postgres.',
+ 'Scaffold CRUD dashboards and admin panels straight from your Supabase Postgres tables.',
},
{
- title: 'Flutter',
- href: '/guides/getting-started/quickstarts/flutter',
- icon: '/docs/img/icons/flutter-icon',
+ title: 'Hono',
+ href: '/guides/getting-started/quickstarts/hono',
+ icon: '/docs/img/icons/hono-icon',
hasLightIcon: false,
- feature: 'sdk:dart',
- description: 'Ship iOS and Android from one Dart codebase, backed by Supabase Postgres.',
+ description:
+ 'Lightweight web APIs with Supabase Auth anonymous sign-in and RLS-protected reads.',
},
{
title: 'iOS SwiftUI',
@@ -141,42 +163,20 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Native Android apps in Kotlin and Jetpack Compose, using the Supabase Kotlin SDK.',
},
{
- title: 'SvelteKit',
- href: '/guides/getting-started/quickstarts/sveltekit',
- icon: '/docs/img/icons/svelte-icon',
- hasLightIcon: false,
- description: 'Full-stack Svelte that loads Supabase Postgres data in server load functions.',
- },
- {
- title: 'SolidJS',
- href: '/guides/getting-started/quickstarts/solidjs',
- icon: '/docs/img/icons/solidjs-icon',
- hasLightIcon: false,
- description:
- 'Fine-grained reactive UIs that load Supabase Postgres data with Solid resources.',
- },
- {
- title: 'Vue',
- href: '/guides/getting-started/quickstarts/vue',
- icon: '/docs/img/icons/vuejs-icon',
- hasLightIcon: false,
- description:
- 'Build single-page apps with the Vue composition API, backed by Supabase Postgres.',
- },
- {
- title: 'TanStack Start',
- href: '/guides/getting-started/quickstarts/tanstack',
- icon: '/docs/img/icons/tanstack-icon',
+ title: 'Expo React Native',
+ href: '/guides/getting-started/quickstarts/expo-react-native',
+ icon: '/docs/img/icons/expo-icon',
hasLightIcon: true,
- description: 'Type-safe full-stack React that queries Supabase Postgres in server functions.',
+ description:
+ 'Ship iOS and Android from one React Native codebase, backed by Supabase Postgres.',
},
{
- title: 'Refine',
- href: '/guides/getting-started/quickstarts/refine',
- icon: '/docs/img/icons/refine-icon',
+ title: 'Flutter',
+ href: '/guides/getting-started/quickstarts/flutter',
+ icon: '/docs/img/icons/flutter-icon',
hasLightIcon: false,
- description:
- 'Scaffold CRUD dashboards and admin panels straight from your Supabase Postgres tables.',
+ feature: 'sdk:dart',
+ description: 'Ship iOS and Android from one Dart codebase, backed by Supabase Postgres.',
},
{
title: 'Python',
@@ -185,6 +185,14 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
hasLightIcon: false,
description: 'Serve Flask web apps that query Postgres with the Supabase Python client.',
},
+ {
+ title: 'Reflex',
+ href: '/guides/getting-started/quickstarts/reflex',
+ icon: '/docs/img/icons/reflex-icon',
+ hasLightIcon: true,
+ description:
+ 'Full-stack Python web apps that query Supabase Postgres with the async Python client.',
+ },
{
title: 'Laravel',
href: '/guides/getting-started/quickstarts/laravel',
@@ -201,6 +209,14 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
description:
'Convention-driven Ruby apps with Active Record connected directly to your Supabase Postgres database.',
},
+ {
+ title: 'Spring Boot',
+ href: '/guides/getting-started/quickstarts/spring-boot',
+ icon: '/docs/img/icons/spring-boot-icon',
+ hasLightIcon: false,
+ description:
+ 'Java APIs with Spring Data JPA connected directly to your Supabase Postgres database.',
+ },
],
}
diff --git a/apps/docs/public/img/icons/reflex-icon-light.svg b/apps/docs/public/img/icons/reflex-icon-light.svg
new file mode 100644
index 00000000000..545d4780962
--- /dev/null
+++ b/apps/docs/public/img/icons/reflex-icon-light.svg
@@ -0,0 +1,8 @@
+
diff --git a/apps/docs/public/img/icons/reflex-icon.svg b/apps/docs/public/img/icons/reflex-icon.svg
new file mode 100644
index 00000000000..89ce248b543
--- /dev/null
+++ b/apps/docs/public/img/icons/reflex-icon.svg
@@ -0,0 +1,8 @@
+
diff --git a/apps/docs/public/img/icons/spring-boot-icon.svg b/apps/docs/public/img/icons/spring-boot-icon.svg
new file mode 100644
index 00000000000..a87092cb99b
--- /dev/null
+++ b/apps/docs/public/img/icons/spring-boot-icon.svg
@@ -0,0 +1,6 @@
+
diff --git a/supa-mdx-lint/Rule001HeadingCase.toml b/supa-mdx-lint/Rule001HeadingCase.toml
index 34bbda84489..189b854f1fd 100644
--- a/supa-mdx-lint/Rule001HeadingCase.toml
+++ b/supa-mdx-lint/Rule001HeadingCase.toml
@@ -197,6 +197,7 @@ may_uppercase = [
"Query Performance",
"Rails",
"React",
+ "Reflex",
"Rollup",
"React Email",
"React Native",
From 15484a0e7544dad2d5309f4bf993d406bd6883c4 Mon Sep 17 00:00:00 2001
From: Miranda Limonczenko
Date: Wed, 9 Sep 2026 16:39:09 -0700
Subject: [PATCH 006/614] docs: add application-side pool sizing to the
connecting to Postgres guide (#49927)
Closes DOCS-1312
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Docs update. One technical addition, isolated so the eval can attribute
a score change to it.
I re-ran the preview link on a scratch Eval branch and found that this
PR will resolve the Eval.
## What is the current behavior?
The eval baseline for `build-docs-004-postgres-connection` fails one
check, 3 of 6 runs: the application-side pool cap for a serverless
invocation.
- Two failing runs left `max` unset, which is 10 on the Postgres.js
default.
- One set `max: 5`.
The page says nothing about the application-side pool, so there was
nothing for an agent to read. Every other check passes 6/6, including
the connection string, port, username, and prepared statements. The mode
choice already transmits from the page.
Baseline notes are on
[DOCS-1312](https://linear.app/supabase/issue/DOCS-1312).
## What is the new behavior?
Add a **Configure your client** section to the procedure group. Pool
sizing is its only subject.
- Set the application-side pool to 1 connection per serverless
invocation, and raise it only on evidence.
- Name the trap concretely. Library defaults assume a persistent
backend, and 10 connections is 10 per warm instance, with the instance
count outside your control.
- One Postgres.js sample setting `max` and `prepare`, created at module
scope.
- Cite the [Supavisor
FAQ](https://supabase.com/docs/guides/troubleshooting/supavisor-faq-YyP5tI)
and [Prisma
troubleshooting](https://supabase.com/docs/guides/database/prisma/prisma-troubleshooting),
which already carries the equivalent `connection_limit` guidance for one
ORM. The gap is that the connection guide didn't carry it for readers
not using Prisma.
`prepare: false` is in the sample because a transaction mode sample is
wrong without it, and the page already instructs it. It isn't new
guidance. `ssl: 'require'` is, so it waits for #49928.
## Additional context
PR 3 of 4. Base is #49869.
This ships alone on purpose. It's the only change with baseline evidence
behind it, so a score change after this PR is attributable to one edit.
#49928 carries the rest of the eval feedback and is not expected to move
the score.
**Run the eval against this preview before #49928 lands.**
## Manual testing
1. Open [Connect to your
database](https://docs-git-docs-connecting-to-postgres-pool-size-supabase.vercel.app/docs/guides/database/connecting-to-postgres)
on the deploy preview.
2. Check the table of contents. "Configure your client" appears under
Get your connection string.
3. Read the section. It states 1 connection per invocation and names the
Postgres.js default of 10.
4. Read the sample. It sets `max: 1` and `prepare: false`, and says the
client is created once at module scope.
## Summary by CodeRabbit
- **Documentation**
- Added guidance for configuring application-side Postgres clients when
connecting through Supabase poolers.
- Documented recommended serverless settings, including creating the
client once, limiting connections per invocation, and disabling prepared
statements in transaction mode.
- Added a Postgres.js configuration example and links to relevant
Supavisor FAQ and Prisma troubleshooting resources.
---
.../database/connecting-to-postgres.mdx | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/apps/docs/content/guides/database/connecting-to-postgres.mdx b/apps/docs/content/guides/database/connecting-to-postgres.mdx
index b06d0b05a35..61e94e3ad14 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres.mdx
@@ -98,6 +98,31 @@ postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres
Get this string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
+### Configure your client
+
+Your connection library keeps its own pool of connections, separate from the poolers Supabase runs. Configure it for the environment your code runs in.
+
+In a serverless function:
+
+1. Create the client once at module scope, not per request.
+2. Set the pool to 1 connection. The client is shared by every invocation on that warm instance, so this caps the instance, not the request.
+3. Turn off prepared statements, which [transaction mode](#pooler-transaction-mode) doesn't support.
+
+```ts lib/db.ts
+import postgres from 'postgres'
+
+export const sql = postgres(process.env.DATABASE_URL, {
+ max: 1,
+ prepare: false,
+})
+```
+
+Library defaults assume a persistent backend, so they are too high for serverless. [Postgres.js](https://github.com/porsager/postgres) defaults to 10 connections. That is 10 connections for every warm instance of your function, and the number of warm instances isn't something you control. A few dozen instances is enough to exhaust the pool.
+
+Raise the pool above 1 only when you have evidence that concurrent invocations on one instance are queuing for the connection.
+
+For more on sizing an application-side pool, see the [Supavisor FAQ](/docs/guides/troubleshooting/supavisor-faq-YyP5tI). If you use Prisma, [Prisma troubleshooting](/docs/guides/database/prisma/prisma-troubleshooting) covers the equivalent `connection_limit` setting.
+
### Data APIs and client libraries [#data-apis-and-client-libraries]
The Data APIs let you interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as your tables have [Row Level Security](/docs/guides/database/postgres/row-level-security) (RLS) enabled and policies that allow the access. RLS with no policies denies every request.
From bc102876bbfb428582b57cacb7099d2ab8d764e9 Mon Sep 17 00:00:00 2001
From: Miranda Limonczenko
Date: Wed, 9 Sep 2026 16:49:42 -0700
Subject: [PATCH 007/614] docs: apply the rest of the connecting to Postgres
feedback (#49928)
Closes FDBKIN-31335
Closes FDBKIN-13040
Closes FDBKIN-8653
Closes FDBKIN-19912
Closes DOCS-740
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Docs update. While we are revising this document, this PR gathers docs
feedback via AI magic and applies that feedback.
## What is the current behavior?
These findings stand on feedback intake rather than on the baseline.
Worth doing, and the eval won't show a score change for any of them.
- **Nothing explains the pooler host.** #49868 switched the strings to
`[POOLER-HOST]`, but the page never says why you can't compose the host,
and agents that recite `aws-0` get `Tenant or user not found`.
agent-skills#92.
- **The page gives the instruction to turn prepared statements off, but
not the flag.** It also links the GitHub discussion rather than the
troubleshooting entry that mirrors it. FDBKIN-8248, FDBKIN-7883.
- **SSL goes undiscussed.** Four of six eval runs set `ssl: 'require'`
unprompted.
- **The pooled username format only appears inside example strings**,
never as a rule. DOCS-740, FDBKIN-19912.
- **Third-party tools have no answer.** Session mode is the right one,
and the decision table had no row for a BI client or database GUI at
all. FDBKIN-8653.
- **Only one of transaction mode's three limitations is documented.**
FDBKIN-13040 names prepared statements, cursors, and session-level
settings. The page covered prepared statements.
## What is the new behavior?
- Tell the reader to copy the host, port, and username rather than
typing the placeholders, and explain the pooler cluster index next to
the reference table. The placeholders themselves changed in #49868.
- State the username rule: direct connections and the dedicated pooler
use `postgres`, shared pooler connections use `postgres.`.
- Add a per-driver prepared statements table for Postgres.js, Drizzle,
Prisma, asyncpg, and JDBC, and link [Disabling prepared
statements](https://supabase.com/docs/guides/troubleshooting/disabling-prepared-statements-qL8lEL)
for the rest. Add JDBC's `prepareThreshold=0` to that entry too, so the
two pages agree.
- Document SSL: `require` rather than the `prefer` default, which falls
back to plaintext.
- Link the `CONNECT_TIMEOUT` entry for stale sockets in frozen
serverless runtimes.
- Add a decision table row for a third-party tool, and point at
Quickstarts for named tools.
- Cover all three transaction mode limitations. Cursors work inside a
single transaction only, and session-level state is lost between
transactions: `set` and `reset`, session-level advisory locks, `listen`
and `notify`, and temporary tables. Renamed the section from "Prepared
statements", since it now covers the cause rather than one symptom.
- Promote Configure your client to an H2 and fold the SSL certificate
section into it. The table of contents only renders H2 and H3, so the
client settings were invisible as H4s.
## Manual testing
1. Open [Connect to your
database](https://docs-git-docs-connecting-to-postgres-technical-supabase.vercel.app/docs/guides/database/connecting-to-postgres)
on the deploy preview.
2. Read the Get your connection string lead-in. It tells you to copy the
host, port, and username rather than typing the placeholders.
3. Check the table of contents. Configure your client is an H2 with
Application-side pool size, Prepared statements, SSL, and Stale
connections under it.
4. Follow the prepared statements link. It lands on the in-docs
troubleshooting entry, not GitHub.
5. Open the [endpoint
reference](https://docs-git-docs-connecting-to-postgres-technical-supabase.vercel.app/docs/guides/database/connecting-to-postgres#endpoints-and-ip-versions).
The table shows `aws-[INDEX]-[REGION]`, and the prose below explains the
index and the username rule.
6. Read the decision table. It has a row for a third-party BI client or
database GUI, pointing at session mode.
7. Read [Transaction mode
limitations](https://docs-git-docs-connecting-to-postgres-technical-supabase.vercel.app/docs/guides/database/connecting-to-postgres#transaction-mode-limitations).
It covers prepared statements, cursors, and session-level state.
## Summary by CodeRabbit
## Documentation
- Expanded the Postgres connection guide with clearer client
configuration guidance, including pool sizing, SSL, stale connections,
and transaction mode limitations.
- Added recommendations for BI tools and database GUIs using the shared
pooler.
- Clarified connection strings, pooler hosts, usernames, ports, and IP
version behavior.
- Updated serverless driver guidance for transaction mode configuration.
- Added JDBC troubleshooting instructions for disabling prepared
statements with `prepareThreshold=0`.
---
.../database/connecting-to-postgres.mdx | 111 ++++++++++++------
.../serverless-drivers.mdx | 2 +-
.../disabling-prepared-statements-qL8lEL.mdx | 8 ++
3 files changed, 84 insertions(+), 37 deletions(-)
diff --git a/apps/docs/content/guides/database/connecting-to-postgres.mdx b/apps/docs/content/guides/database/connecting-to-postgres.mdx
index 61e94e3ad14..df6115b4621 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres.mdx
@@ -8,6 +8,7 @@ Learn how to pick a connection method and where to find the connection string fo
- [Which connection method do you use?](#choose-a-connection-method) picks a method based on where your code runs.
- [Get your connection string](#get-your-connection-string) shows where each string comes from.
+- [Configure your client](#configure-your-client) sets pool size, prepared statements, and SSL.
- [Quickstarts](#quickstarts) connect a specific ORM or database GUI.
For how pooling works and the limits that apply to your connections, see [Connection pooling and limits](/docs/guides/database/connecting-to-postgres/pooling-and-limits).
@@ -22,6 +23,7 @@ How you connect to your database depends on where your code runs. Find your case
| A serverless or edge function | [Shared pooler, transaction mode](#pooler-transaction-mode) | These environments open many short-lived connections. |
| A persistent backend on IPv6, or with the IPv4 add-on | [Direct connection](#direct-connection) | No pooler in the path. |
| A persistent backend on an IPv4-only network | [Shared pooler, session mode](#pooler-session-mode) | The shared pooler is IPv4-only on every plan. |
+| A third-party tool, such as a BI client or database GUI | [Shared pooler, session mode](#pooler-session-mode) | Reachable over IPv4 from networks you don't control, and it supports prepared statements. |
| A high-performance application on a paid plan | [Dedicated pooler](#dedicated-pooler) | Runs on the same machine as your database, so lower latency than the shared pooler. |
| Migrations, `pg_dump`, backup and restore, or replication | [Direct connection](#direct-connection) | These are single sessions and Postgres native commands. |
@@ -31,7 +33,7 @@ The IPv4 add-on is not dual-stack: enabling it swaps the project's IPv6 (AAAA) D
-For the host, port, and IP version of each mode, see [Endpoints and IP versions](#endpoints-and-ip-versions).
+For the host, port, and IP version of each mode, see [Endpoints and IP versions](#endpoints-and-ip-versions). For a named ORM or database GUI, see [Quickstarts](#quickstarts).
## Get your connection string [#get-your-connection-string]
@@ -44,7 +46,7 @@ For every Postgres connection mode, the string comes from the same place:
3. Choose the connection method you picked above.
4. Copy the string and replace `[YOUR-PASSWORD]` with your database password. [Percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) any reserved characters it contains, such as `&`, `#`, `?`, or a space.
-The sections below show what each string looks like and when to use it.
+The sections below show what each string looks like and when to use it. Take the host, port, and username from the string you copied rather than typing the bracketed placeholders literally. The pooler host in particular can't be composed from your region, and pooled connections use a different username from direct connections. Both are covered under [Endpoints and IP versions](#endpoints-and-ip-versions).
### Direct connection [#direct-connection]
@@ -78,7 +80,7 @@ The transaction mode connection string connects to your Postgres instance throug
-Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your connection library.
+Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, turn them off in your connection library. See [Transaction mode limitations](#transaction-mode-limitations) for the setting your driver uses and for the other session-state features this affects.
@@ -98,31 +100,6 @@ postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres
Get this string from the Supabase Dashboard by clicking [Connect](/dashboard/project/_?showConnect=true&method=transaction).
-### Configure your client
-
-Your connection library keeps its own pool of connections, separate from the poolers Supabase runs. Configure it for the environment your code runs in.
-
-In a serverless function:
-
-1. Create the client once at module scope, not per request.
-2. Set the pool to 1 connection. The client is shared by every invocation on that warm instance, so this caps the instance, not the request.
-3. Turn off prepared statements, which [transaction mode](#pooler-transaction-mode) doesn't support.
-
-```ts lib/db.ts
-import postgres from 'postgres'
-
-export const sql = postgres(process.env.DATABASE_URL, {
- max: 1,
- prepare: false,
-})
-```
-
-Library defaults assume a persistent backend, so they are too high for serverless. [Postgres.js](https://github.com/porsager/postgres) defaults to 10 connections. That is 10 connections for every warm instance of your function, and the number of warm instances isn't something you control. A few dozen instances is enough to exhaust the pool.
-
-Raise the pool above 1 only when you have evidence that concurrent invocations on one instance are queuing for the connection.
-
-For more on sizing an application-side pool, see the [Supavisor FAQ](/docs/guides/troubleshooting/supavisor-faq-YyP5tI). If you use Prisma, [Prisma troubleshooting](/docs/guides/database/prisma/prisma-troubleshooting) covers the equivalent `connection_limit` setting.
-
### Data APIs and client libraries [#data-apis-and-client-libraries]
The Data APIs let you interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as your tables have [Row Level Security](/docs/guides/database/postgres/row-level-security) (RLS) enabled and policies that allow the access. RLS with no policies denies every request.
@@ -139,14 +116,6 @@ For convenience, you can also use the [Supabase client libraries](/docs/referenc
- [C#](/docs/reference/csharp/introduction)
- [Kotlin](/docs/reference/kotlin/introduction)
-### Connect with SSL [#connecting-with-ssl]
-
-Connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
-
-Download your server root certificate from [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard. The same section has a toggle that rejects non-SSL connections to your database.
-
-
-
### Endpoints and IP versions [#endpoints-and-ip-versions]
Each mode has its own host, port, and IP version support. IP version support depends on your plan and on whether the project has the [IPv4 add-on](/docs/guides/platform/ipv4-address).
@@ -158,10 +127,80 @@ Each mode has its own host, port, and IP version support. IP version support dep
| Shared pooler, transaction mode | `aws-[INDEX]-[REGION].pooler.supabase.com:6543` | IPv4 | IPv4 | IPv4 |
| Dedicated pooler, transaction mode | `db.[PROJECT-REF].supabase.co:6543` | - | IPv6 | IPv4 |
+`[INDEX]` in the shared pooler host is a pooler cluster index, not part of the region name. A region can have more than one, so you can't work out your host from your region. Copy the host from the Connect dialog.
+
+The username differs by connection type. Direct connections and the dedicated pooler use `postgres`. Shared pooler connections use `postgres.[PROJECT-REF]`. If you connect as a custom role through the shared pooler, the username is `[ROLE].[PROJECT-REF]`.
+
The port routes the connection to the right pooler and mode. Port `5432` reaches Postgres for a direct connection and Supavisor for session mode. Port `6543` reaches PgBouncer for the dedicated pooler and Supavisor for shared transaction mode.
To connect over IPv4, you have two options. The shared pooler is IPv4-only on every plan, in both session and transaction mode. Alternatively, add the [IPv4 add-on](/docs/guides/platform/ipv4-address) to your project, which makes the direct connection and the dedicated pooler reachable over IPv4 instead of IPv6.
+## Configure your client
+
+A connection string on its own isn't enough. Your connection library keeps its own pool of connections, separate from the poolers Supabase runs, and its defaults assume a persistent backend.
+
+In a serverless function:
+
+1. Create the client once at module scope, not per request.
+2. Set the pool to 1 connection. The client is shared by every invocation on that warm instance, so this caps the instance, not the request.
+3. Turn off prepared statements, which [transaction mode](#pooler-transaction-mode) doesn't support.
+4. Set SSL to `require`, so the driver refuses to connect without encryption.
+
+```ts lib/db.ts
+import postgres from 'postgres'
+
+export const sql = postgres(process.env.DATABASE_URL, {
+ max: 1,
+ prepare: false,
+ ssl: 'require',
+})
+```
+
+The rest of this section explains each setting, and what changes for a driver other than Postgres.js.
+
+### Application-side pool size
+
+Library defaults are too high for serverless. [Postgres.js](https://github.com/porsager/postgres) defaults to 10 connections. That is 10 connections for every warm instance of your function, and the number of warm instances isn't something you control. A few dozen instances is enough to exhaust the pool.
+
+Raise the pool above 1 only when you have evidence that concurrent invocations on one instance are queuing for the connection.
+
+For more on sizing an application-side pool, see the [Supavisor FAQ](/docs/guides/troubleshooting/supavisor-faq-YyP5tI). If you use Prisma, [Prisma troubleshooting](/docs/guides/database/prisma/prisma-troubleshooting) covers the equivalent `connection_limit` setting.
+
+### Transaction mode limitations
+
+[Transaction mode](#pooler-transaction-mode) returns your connection to the pool after each transaction, so anything that depends on session state doesn't survive between transactions. Three things are affected.
+
+**Prepared statements** aren't supported, so turn them off. Each driver does this differently:
+
+| Driver | Setting |
+| -------------------- | ----------------------------------------- |
+| Postgres.js, Drizzle | `prepare: false` |
+| Prisma | `pgbouncer=true` on the connection string |
+| asyncpg | `statement_cache_size=0` |
+| JDBC | `prepareThreshold=0` |
+
+For node-postgres, Psycopg, and Rust drivers, see [Disabling prepared statements](/docs/guides/troubleshooting/disabling-prepared-statements-qL8lEL).
+
+**Cursors** work inside a single transaction only. A `with hold` cursor is meant to outlive its transaction, and it doesn't survive the connection returning to the pool.
+
+**Session-level state** is lost between transactions. This covers `set` and `reset`, session-level advisory locks, `listen` and `notify`, and temporary tables. Run them inside the transaction that needs them, or use session mode or a direct connection instead.
+
+Direct connections and session mode support all three, so none of this applies to either.
+
+### SSL [#connecting-with-ssl]
+
+Connect using SSL wherever possible, to prevent snooping and man-in-the-middle attacks.
+
+Set SSL to `require` so the driver refuses to connect without encryption. Most drivers default to `prefer`, which falls back to sending your data in plaintext if the encrypted attempt fails. On a connection string, this is `sslmode=require`.
+
+`require` encrypts the connection but doesn't verify the server, so it doesn't stop a man-in-the-middle attack. To verify as well as encrypt, download your server root certificate from [Database settings](/dashboard/project/_/database/settings) in the Supabase Dashboard and point your driver at it. Downloading the certificate on its own changes nothing: the driver has to be told to use it, with `sslmode=verify-full` and `sslrootcert` on a connection string, or the equivalent option in your library. The same section has a toggle that rejects non-SSL connections to your database.
+
+
+
+### Stale connections
+
+Serverless runtimes freeze a function between requests, which can leave a pooled TCP socket stale. If you see `CONNECT_TIMEOUT` errors or queries that hang until the execution limit, see [Troubleshooting `CONNECT_TIMEOUT` or hanging queries in Serverless Functions](/docs/guides/troubleshooting/troubleshooting-connect_timeout-or-hanging-queries-in-vercel-serverless-functions-775f92).
+
## Quickstarts [#quickstarts]
Each quickstart connects one ORM or database GUI to your Supabase database.
diff --git a/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx b/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
index 36642ee20a3..7ddc03b5775 100644
--- a/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
+++ b/apps/docs/content/guides/database/connecting-to-postgres/serverless-drivers.mdx
@@ -9,7 +9,7 @@ Learn how to connect to your Postgres database from a serverless environment. Th
[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api), so it works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works) and can serve thousands of simultaneous requests, which suits serverless workloads.
-If you connect with a Postgres client instead, use the [shared pooler in transaction mode](/docs/guides/database/connecting-to-postgres#pooler-transaction-mode). Transaction mode doesn't support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html), so [turn them off](https://github.com/orgs/supabase/discussions/28239) in your connection library.
+If you connect with a Postgres client instead, use the [shared pooler in transaction mode](/docs/guides/database/connecting-to-postgres#pooler-transaction-mode), then [configure your client](/docs/guides/database/connecting-to-postgres#configure-your-client) for pool size, prepared statements, and SSL. Those three settings are what most serverless connection problems come down to.
## Vercel Edge Functions
diff --git a/apps/docs/content/troubleshooting/disabling-prepared-statements-qL8lEL.mdx b/apps/docs/content/troubleshooting/disabling-prepared-statements-qL8lEL.mdx
index 7d0c3ab413d..f563224808d 100644
--- a/apps/docs/content/troubleshooting/disabling-prepared-statements-qL8lEL.mdx
+++ b/apps/docs/content/troubleshooting/disabling-prepared-statements-qL8lEL.mdx
@@ -51,6 +51,14 @@ Follow the recommendation in the [asyncpg docs](https://magicstack.github.io/asy
> disable automatic use of prepared statements by passing `statement_cache_size=0` to [asyncpg.connect()](https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.connect) and [asyncpg.create_pool()](https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.pool.create_pool) (and, obviously, avoid the use of [Connection.prepare()](https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.prepare));
+## JDBC
+
+Set [`prepareThreshold`](https://jdbc.postgresql.org/documentation/use/#connection-parameters) to `0`:
+
+```
+jdbc:postgresql://[POOLER-HOST]:6543/postgres?prepareThreshold=0
+```
+
## Rust's Deadpool or `tokio-postgres`:
- Check [GitHub Discussion](https://github.com/bikeshedder/deadpool/issues/340#event-13642472475)
From 23a5bd4707487135c5d767361d08452ae7c63242 Mon Sep 17 00:00:00 2001
From: Miranda Limonczenko
Date: Wed, 9 Sep 2026 17:10:55 -0700
Subject: [PATCH 008/614] fix: update inbound links to the pooling guide
(#50187)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Bug fix. Link string changes only, no content changes.
## What is the current behavior?
Nine inbound links in `apps/www` and `apps/studio` point at anchors on
the connecting to Postgres guide that don't exist. All nine are already
broken on production today: `#connection-pooler`, `#connection-pool`,
`#how-connection-pooling-works`, `#serverside-poolers`, and
`#connecting-with-drizzle` are all missing from the live page.
#49869 moves the pooling content to a child page, so these links need
current destinations either way.
## What is the new behavior?
Point each link at the page that holds the content now.
- **Studio, 3 links.** The Connect sheet's Drizzle link goes to the
Drizzle guide. The connection pooling and pooling modes links go to
`pooling-and-limits#how-connection-pooling-works`.
- **www, 6 links.** Three blog posts, the Heroku comparison page, and
the Dedicated poolers feature entry go to `pooling-and-limits`. The
feature entry uses `#shared-pooler`.
## Additional context
Split out of #49869. These paths belong to `@supabase/marketing` and
`@supabase/Dashboard` in CODEOWNERS, and pulling both teams into a
docs-only restructure for nine link strings isn't a good trade.
Merge after #49928. The destinations don't exist on production until the
docs pages land.
## Manual testing
1. Open [Supavisor: Scaling Postgres to 1 Million
Connections](https://zone-www-dot-com-git-fix-pooler-docs-links-supabase.vercel.app/blog/supavisor-1-million).
The "connection pooling" link in the opening paragraph resolves to
`connecting-to-postgres/pooling-and-limits#how-connection-pooling-works`.
2. Open [Dedicated
poolers](https://zone-www-dot-com-git-fix-pooler-docs-links-supabase.vercel.app/features/dedicated-poolers).
The docs link resolves to `pooling-and-limits#shared-pooler`.
3. Open [Supabase vs Heroku
Postgres](https://zone-www-dot-com-git-fix-pooler-docs-links-supabase.vercel.app/alternatives/supabase-vs-heroku-postgres).
Both connection pooling links resolve to
`pooling-and-limits#how-connection-pooling-works`.
4. Open [Connection pooling and
limits](https://docs-git-docs-connecting-to-postgres-technical-supabase.vercel.app/docs/guides/database/connecting-to-postgres/pooling-and-limits)
on the #49928 docs preview. The `how-connection-pooling-works` and
`shared-pooler` headings both render with those IDs.
5. Open the Connect dialog on any project. Under Drizzle, the docs link
opens the Drizzle guide.
6. Open Database settings, then Connection pooling. The pooler link
opens Connection pooling and limits.
## Summary by CodeRabbit
* **Documentation**
* Updated connection pooling links across Studio, product pages, blogs,
and comparison content to point to the relevant pooling guidance.
* Refined links for Drizzle ORM, dedicated poolers, direct connections,
and shared poolers.
* Improved navigation to specific documentation sections explaining
connection pooling modes and behavior.
---
.../components/interfaces/ConnectSheet/Connect.constants.ts | 2 +-
.../Settings/Database/ConnectionPooling/ConnectionPooling.tsx | 2 +-
.../interfaces/Settings/Database/PoolingModesModal.tsx | 2 +-
apps/www/_alternatives/supabase-vs-heroku-postgres.mdx | 4 ++--
apps/www/_blog/2023-08-11-supavisor-1-million.mdx | 2 +-
apps/www/_blog/2024-01-16-ipv6.mdx | 2 +-
apps/www/_blog/2025-03-07-dedicated-poolers.mdx | 2 +-
apps/www/data/features.tsx | 3 ++-
8 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts b/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts
index a588f3320dd..0339d07cd8c 100644
--- a/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts
+++ b/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts
@@ -369,7 +369,7 @@ export const ORMS: ConnectionType[] = [
key: 'drizzle',
label: 'Drizzle',
icon: 'drizzle',
- guideLink: `${DOCS_URL}/guides/database/connecting-to-postgres#connecting-with-drizzle`,
+ guideLink: `${DOCS_URL}/guides/database/drizzle`,
children: [],
},
]
diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx
index d45a87d62f5..638f720e9f6 100644
--- a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx
@@ -165,7 +165,7 @@ export const ConnectionPooling = () => {
diff --git a/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx b/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx
index 3e16bc7b8cb..d7019bf0920 100644
--- a/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx
@@ -51,7 +51,7 @@ export const PoolingModesModal = () => {
Which pooling mode should I use?
diff --git a/apps/www/_alternatives/supabase-vs-heroku-postgres.mdx b/apps/www/_alternatives/supabase-vs-heroku-postgres.mdx
index fafac289596..ac1b3fee7b2 100644
--- a/apps/www/_alternatives/supabase-vs-heroku-postgres.mdx
+++ b/apps/www/_alternatives/supabase-vs-heroku-postgres.mdx
@@ -21,7 +21,7 @@ Supabase also offers managed Postgres, the main difference is that with each dep
- Auth - [users can log in and out of your application](https://supabase.com/auth)
- Functions - [deploy custom logic to the edge](https://supabase.com/edge-functions)
- Storage - [serve large files and folders](https://supabase.com/storage)
-- Supavisor - [connection pooling useful for serverless computing](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler)
+- Supavisor - [connection pooling useful for serverless computing](https://supabase.com/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works)
## How are they similar?
@@ -42,7 +42,7 @@ Heroku Postgres and Supabase both offer:
These are some of the key differences between Heroku Postgres and Supabase in terms of features:
- Supabase is more than just the raw database, it also comes with:
- - [Connection pooling](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool) so that you won’t run out of connections in a serverless environment.
+ - [Connection pooling](https://supabase.com/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works) so that you won’t run out of connections in a serverless environment.
- [Auto-generated APIs](https://supabase.com/docs/guides/api#rest-api-overview) based on your schema, so you can communicate with your database directly from the client.
- [Realtime API](https://supabase.com/docs/reference/dart/subscribe) is useful for when you want to subscribe to changes to your database over websockets.
- [Auth API](https://supabase.com/auth) can be used to leverage Postgres’s Row Level Security model, and control access to sensitive data on a per user, or per group level.
diff --git a/apps/www/_blog/2023-08-11-supavisor-1-million.mdx b/apps/www/_blog/2023-08-11-supavisor-1-million.mdx
index ff2fc6038fc..e07f971e073 100644
--- a/apps/www/_blog/2023-08-11-supavisor-1-million.mdx
+++ b/apps/www/_blog/2023-08-11-supavisor-1-million.mdx
@@ -17,7 +17,7 @@ imgThumb: launch-week-8/day-5/supavisor-thumb.jpg
One of the most [widely-discussed shortcomings](https://news.ycombinator.com/item?id=24735012) of Postgres is it's connection system. Every Postgres connection has a reasonably high memory footprint, and determining the maximum number of connections your database can handle is a [bit of an art](https://momjian.us/main/blogs/pgblog/2020.html#April_22_2020).
-A common solution is [connection pooling](https://supabase.com/docs/guides/database/connecting-to-postgres#how-connection-pooling-works). Supabase currently offers [pgbouncer](http://www.pgbouncer.org/) which is single-threaded, making it difficult to scale. We've seen some [novel ways](https://twitter.com/viggy28/status/1677674197664038912?s=12&t=_WCn3v_QJ7tkQLvOvkZkqg) to scale pgbouncer, but we have a [few other goals](https://github.com/supabase/supavisor#motivation) in mind for our platform.
+A common solution is [connection pooling](https://supabase.com/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works). Supabase currently offers [pgbouncer](http://www.pgbouncer.org/) which is single-threaded, making it difficult to scale. We've seen some [novel ways](https://twitter.com/viggy28/status/1677674197664038912?s=12&t=_WCn3v_QJ7tkQLvOvkZkqg) to scale pgbouncer, but we have a [few other goals](https://github.com/supabase/supavisor#motivation) in mind for our platform.
And so we've built [Supavisor](https://github.com/supabase/supavisor), a Postgres connection pooler that can handle millions of connections.
diff --git a/apps/www/_blog/2024-01-16-ipv6.mdx b/apps/www/_blog/2024-01-16-ipv6.mdx
index 876abe24092..138376b3b34 100644
--- a/apps/www/_blog/2024-01-16-ipv6.mdx
+++ b/apps/www/_blog/2024-01-16-ipv6.mdx
@@ -54,7 +54,7 @@ After your ISP receives this address, it is responsible for routing all traffic
Here are some of the ways that you will be affected when domains/servers start resolving to IPv6 instead of IPv4, if your ISP doesn't support IPv6:
- Do you have a web server set up in AWS? You won't be able to SSH into it.
-- Are you connected to a Supabase database from your local machine using the [direct connection](https://supabase.com/docs/guides/database/connecting-to-postgres#direct-connections)? You need to use the [connection pooler](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler) which will resolve as IPv4 instead (we will pay for IPv4 addresses on these).
+- Are you connected to a Supabase database from your local machine using the [direct connection](https://supabase.com/docs/guides/database/connecting-to-postgres#direct-connection)? You need to use the [connection pooler](https://supabase.com/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works) which will resolve as IPv4 instead (we will pay for IPv4 addresses on these).
- Are you connecting to any AWS server from Vercel? That will start [failing](https://github.com/orgs/vercel/discussions/47) soon if you don't set up an IPv4 address for that server.
## Tooling support
diff --git a/apps/www/_blog/2025-03-07-dedicated-poolers.mdx b/apps/www/_blog/2025-03-07-dedicated-poolers.mdx
index b2024ec7c5a..4ef3dd66fb3 100644
--- a/apps/www/_blog/2025-03-07-dedicated-poolers.mdx
+++ b/apps/www/_blog/2025-03-07-dedicated-poolers.mdx
@@ -17,7 +17,7 @@ Today we're announcing **Dedicated Poolers** - a Postgres connection pooler that
-Don't know what a Pooler is? Check out our [docs](/docs/guides/database/connecting-to-postgres#serverside-poolers).
+Don't know what a Pooler is? Check out our [docs](/docs/guides/database/connecting-to-postgres/pooling-and-limits#how-connection-pooling-works).
diff --git a/apps/www/data/features.tsx b/apps/www/data/features.tsx
index 39f392e0050..c9168c78b0e 100644
--- a/apps/www/data/features.tsx
+++ b/apps/www/data/features.tsx
@@ -600,7 +600,8 @@ Dedicated Poolers provide an alternative to Supavisor for specific use cases, gi
icon: Database,
products: [PRODUCT_SHORTNAMES.DATABASE],
heroImage: '',
- docsUrl: 'https://supabase.com/docs/guides/database/connecting-to-postgres#serverside-poolers',
+ docsUrl:
+ 'https://supabase.com/docs/guides/database/connecting-to-postgres/pooling-and-limits#shared-pooler',
slug: 'dedicated-poolers',
status: {
stage: PRODUCT_STAGES.GA,
From 30ab816ff4ef0e09b8cc6d33821f649eeef3e928 Mon Sep 17 00:00:00 2001
From: Danny White <3104761+dnywh@users.noreply.github.com>
Date: Thu, 10 Sep 2026 10:35:05 +1000
Subject: [PATCH 009/614] feat(studio): make the Connect framework and client
selectors searchable (#50072)
## What kind of change does this PR introduce?
Feature.
## What is the current behavior?
The Framework and Client selectors in the Connect sheet are plain
selects. Neither is scannable at its current length, and Client is the
worse of the two at 19 options.
## What is the new behavior?
Both are searchable comboboxes. Each keeps its selection, filters as you
type, matches on the underlying key as well as the label so `nextjs`
finds `Next.js`, and announces its empty state to screen readers.
| Before | After |
| --- | --- |
| | |
Placeholder, search and empty-state copy now sit on the field definition
in the schema, next to the label, so one combobox component serves both
fields without guessing at plurals.
Client keeps its icons hidden, matching what the select did. The comment
about MCP images being unoptimized still stands, so this is not the PR
to turn them on.
Radix Select brings its own scroll lock, so replacing it with a popover
would have regressed touch scrolling in the sheet. #50103 moved that
guard into `CommandList` and has merged, so this branch now carries the
feature only.
## To test
- Open the Connect sheet on the deploy preview.
- Open the Framework selector, search for `native`, confirm only React
Native remains, select it, and confirm the generated connection
instructions update.
- Search `nextjs` and confirm Next.js matches on its key.
- Switch to the MCP tab and open Client. Search `cur` and confirm Cursor
matches.
- Confirm both lists cap their height and scroll, and that the sheet
behind stays put.
## Summary by CodeRabbit
* **New Features**
* Framework selection now uses a searchable combobox for easier
navigation of long lists.
* Search results clear automatically when the combobox closes.
* Long framework lists appear in a contained, scrollable area.
* **Accessibility**
* Screen readers announce when no frameworks match the search.
* Improved combobox and listbox relationships support assistive
technologies.
* The dropdown opens as a modal layer to keep focus within the selection
experience.
---------
Co-authored-by: Claude Opus 5
---
.../interfaces/ConnectSheet/Connect.types.ts | 8 +-
.../ConnectConfigSection.test.tsx | 141 +++++++++++++++++
.../ConnectSheet/ConnectConfigSection.tsx | 143 ++++++++++++++++++
.../ConnectConfigSection.utils.ts | 15 ++
.../__tests__/connect.schema.test.ts | 6 +-
.../interfaces/ConnectSheet/connect.schema.ts | 14 +-
.../ConnectConfigSection.utils.test.ts | 29 ++++
7 files changed, 350 insertions(+), 6 deletions(-)
create mode 100644 apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.test.tsx
create mode 100644 apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.utils.ts
create mode 100644 apps/studio/tests/components/interfaces/ConnectSheet/ConnectConfigSection.utils.test.ts
diff --git a/apps/studio/components/interfaces/ConnectSheet/Connect.types.ts b/apps/studio/components/interfaces/ConnectSheet/Connect.types.ts
index fc5bf7712ec..851aca8eeac 100644
--- a/apps/studio/components/interfaces/ConnectSheet/Connect.types.ts
+++ b/apps/studio/components/interfaces/ConnectSheet/Connect.types.ts
@@ -73,7 +73,7 @@ export interface ModeDefinition {
// Schema Types - Fields
// ============================================================================
-type FieldType = 'select' | 'radio-grid' | 'radio-list' | 'switch' | 'multi-select'
+type FieldType = 'select' | 'combobox' | 'radio-grid' | 'radio-list' | 'switch' | 'multi-select'
export interface FieldOption {
value: string
@@ -87,6 +87,12 @@ interface FieldDefinition {
type: FieldType
label: string
description?: string
+ // Copy for `combobox` fields, which need their own trigger, search and empty-state text
+ combobox?: {
+ placeholder: string
+ searchPlaceholder: string
+ emptyMessage: string
+ }
// Options can be static, or reference a data source, or be conditional
options?: FieldOption[] | { source: string } | ConditionalValue
// Only show this field when these state conditions are met
diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.test.tsx b/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.test.tsx
new file mode 100644
index 00000000000..bad879d0812
--- /dev/null
+++ b/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.test.tsx
@@ -0,0 +1,141 @@
+import { screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { mockAnimationsApi } from 'jsdom-testing-mocks'
+import { describe, expect, test, vi } from 'vitest'
+
+import type { ResolvedField } from './Connect.types'
+import { ConnectConfigSection } from './ConnectConfigSection'
+import { customRender } from '@/tests/lib/custom-render'
+
+mockAnimationsApi()
+
+const frameworkField: ResolvedField = {
+ id: 'framework',
+ type: 'combobox',
+ label: 'Framework',
+ combobox: {
+ placeholder: 'Select framework',
+ searchPlaceholder: 'Search frameworks...',
+ emptyMessage: 'No frameworks found',
+ },
+ resolvedOptions: [],
+}
+
+const frameworkOptions = [
+ { value: 'nextjs', label: 'Next.js' },
+ { value: 'react', label: 'React' },
+ { value: 'react-native', label: 'React Native' },
+]
+
+const manyFrameworkOptions = Array.from({ length: 20 }, (_, index) => ({
+ value: `framework-${index}`,
+ label: `Framework ${index}`,
+}))
+
+describe('ConnectConfigSection', () => {
+ test('filters frameworks and selects the matching option', async () => {
+ const user = userEvent.setup()
+ const onFieldChange = vi.fn()
+
+ customRender(
+ frameworkOptions}
+ />
+ )
+
+ await user.click(screen.getByRole('combobox'))
+ await user.type(screen.getByPlaceholderText('Search frameworks...'), 'native')
+
+ expect(screen.getByRole('option', { name: 'React Native' })).toBeInTheDocument()
+ expect(screen.queryByRole('option', { name: 'Next.js' })).not.toBeInTheDocument()
+
+ await user.click(screen.getByRole('option', { name: 'React Native' }))
+
+ expect(onFieldChange).toHaveBeenCalledWith('framework', 'react-native')
+ })
+
+ test('matches frameworks by key without announcing the empty state', async () => {
+ const user = userEvent.setup()
+
+ customRender(
+ frameworkOptions}
+ />
+ )
+
+ await user.click(screen.getByRole('combobox'))
+ await user.type(screen.getByPlaceholderText('Search frameworks...'), 'nextjs')
+
+ expect(screen.getByRole('option', { name: 'Next.js' })).toBeInTheDocument()
+ expect(screen.getByRole('status')).toBeEmptyDOMElement()
+ })
+
+ test('announces when no frameworks match the search', async () => {
+ const user = userEvent.setup()
+
+ customRender(
+ frameworkOptions}
+ />
+ )
+
+ await user.click(screen.getByRole('combobox'))
+ await user.type(screen.getByPlaceholderText('Search frameworks...'), 'missing')
+
+ expect(screen.getByRole('status')).toHaveTextContent('No frameworks found')
+ })
+
+ test('clears the search when the combobox closes after selecting an option', async () => {
+ const user = userEvent.setup()
+
+ customRender(
+ frameworkOptions}
+ />
+ )
+
+ await user.click(screen.getByRole('combobox'))
+ const searchInput = screen.getByPlaceholderText('Search frameworks...')
+ await user.type(searchInput, 'native')
+ await user.click(screen.getByRole('option', { name: 'React Native' }))
+ await user.click(screen.getByRole('combobox'))
+
+ expect(screen.getByPlaceholderText('Search frameworks...')).toHaveValue('')
+ })
+
+ test('uses a bounded scroll area for long framework lists', async () => {
+ const user = userEvent.setup()
+
+ customRender(
+ manyFrameworkOptions}
+ />
+ )
+
+ const combobox = document.getElementById('connect-framework')
+ expect(combobox).toBeTruthy()
+
+ await user.click(combobox!)
+
+ const listbox = screen.getByRole('listbox')
+ expect(combobox!.getAttribute('aria-controls')).toBe(listbox.id)
+
+ expect(listbox).toHaveClass('max-h-72', 'overscroll-contain')
+ expect(screen.getAllByRole('option')).toHaveLength(20)
+ })
+})
diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.tsx b/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.tsx
index 9f96b39af66..740f39e6e9f 100644
--- a/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.tsx
+++ b/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.tsx
@@ -1,5 +1,17 @@
+import { Check, ChevronsUpDown } from 'lucide-react'
+import { useState } from 'react'
import {
+ Button,
cn,
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
RadioGroupStacked,
RadioGroupStackedItem,
Select,
@@ -19,6 +31,7 @@ import {
} from 'ui-patterns/multi-select'
import type { ConnectMode, FieldOption, ResolvedField } from './Connect.types'
+import { getOptionMatchScore } from './ConnectConfigSection.utils'
import { ConnectionIcon } from './ConnectionIcon'
import {
ConnectModeButton,
@@ -55,6 +68,33 @@ export function ConnectConfigSection({
}
switch (field.type) {
+ case 'combobox':
+ return (
+
+ onFieldChange(field.id, v)}
+ placeholder={field.combobox?.placeholder ?? 'Select option'}
+ searchPlaceholder={field.combobox?.searchPlaceholder ?? 'Search...'}
+ emptyMessage={field.combobox?.emptyMessage ?? 'No results found'}
+ /*
+ [Joshen] Omitting MCP icons for now as the images are not optimized (large)
+ and is causing noticeably latency issues on the browser (even with the existing Connect UI)
+ */
+ showIcons={field.id === 'framework'}
+ />
+
+ )
+
case 'radio-grid':
return (
void
+ placeholder: string
+ searchPlaceholder: string
+ emptyMessage: string
+ showIcons: boolean
+}
+
+function ConnectCombobox({
+ id,
+ options,
+ value,
+ onValueChange,
+ placeholder,
+ searchPlaceholder,
+ emptyMessage,
+ showIcons,
+}: ConnectComboboxProps) {
+ const [isOpen, setIsOpen] = useState(false)
+ const [search, setSearch] = useState('')
+ const [listboxElementId, setListboxElementId] = useState()
+ const selectedOption = options.find((option) => option.value === value)
+ const showEmptyStatus =
+ search.trim().length > 0 &&
+ !options.some((option) => getOptionMatchScore(option.label, search, [option.value]) > 0)
+ const handleOpenChange = (open: boolean) => {
+ setIsOpen(open)
+ if (!open) setSearch('')
+ }
+
+ return (
+
+
+ }
+ >
+
+ {showIcons && selectedOption?.icon && (
+
+
+
+ )}
+ {selectedOption?.label ?? placeholder}
+
+
+
+
+
+
+
+ {showEmptyStatus ? emptyMessage : ''}
+
+ {
+ if (node?.id) setListboxElementId(node.id)
+ }}
+ >
+ {emptyMessage}
+
+ {options.map((option) => (
+ {
+ onValueChange(option.value)
+ handleOpenChange(false)
+ }}
+ className="gap-x-2"
+ >
+
+ {showIcons && option.icon && (
+
+
+
+ )}
+ {option.label}
+
+ ))}
+
+
+
+
+
+ )
+}
+
interface ModeSelectorProps {
modes: Array<{ id: ConnectMode; label: string; description: string }>
selected: ConnectMode
diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.utils.ts b/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.utils.ts
new file mode 100644
index 00000000000..7c20d3f6aa9
--- /dev/null
+++ b/apps/studio/components/interfaces/ConnectSheet/ConnectConfigSection.utils.ts
@@ -0,0 +1,15 @@
+/**
+ * cmdk filter for the Connect comboboxes, matching a plain substring against the option label and
+ * its key (passed through as a cmdk keyword) so `nextjs` finds `Next.js`.
+ *
+ * Also used to decide whether a combobox announces its empty state, so the announcement cannot
+ * disagree with what the list actually filtered out.
+ */
+export function getOptionMatchScore(value: string, search: string, keywords?: string[]) {
+ const normalizedSearch = search.trim().toLowerCase()
+ if (normalizedSearch.length === 0) return 1
+
+ const searchableValues = [value, ...(keywords ?? [])]
+
+ return searchableValues.some((item) => item.toLowerCase().includes(normalizedSearch)) ? 1 : 0
+}
diff --git a/apps/studio/components/interfaces/ConnectSheet/__tests__/connect.schema.test.ts b/apps/studio/components/interfaces/ConnectSheet/__tests__/connect.schema.test.ts
index f3b08063f56..3582a1a4d44 100644
--- a/apps/studio/components/interfaces/ConnectSheet/__tests__/connect.schema.test.ts
+++ b/apps/studio/components/interfaces/ConnectSheet/__tests__/connect.schema.test.ts
@@ -73,7 +73,7 @@ describe('connect.schema:structure', () => {
describe('connect.schema:fields', () => {
test('framework field should have correct type', () => {
const field = connectSchema.fields.framework
- expect(field.type).toBe('select')
+ expect(field.type).toBe('combobox')
expect(field.options).toEqual({ source: 'frameworks' })
expect(field.defaultValue).toBe('nextjs')
})
@@ -112,9 +112,9 @@ describe('connect.schema:fields', () => {
expect(field.defaultValue).toBe('prisma')
})
- test('mcpClient field should have select type', () => {
+ test('mcpClient field should have combobox type', () => {
const field = connectSchema.fields.mcpClient
- expect(field.type).toBe('select')
+ expect(field.type).toBe('combobox')
expect(field.options).toEqual({ source: 'mcpClients' })
expect(field.defaultValue).toBe('claude-code')
})
diff --git a/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts b/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
index fb141fe0c8f..07ae4cee27c 100644
--- a/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
+++ b/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
@@ -303,8 +303,13 @@ export const connectSchema: ConnectSchema = {
// Framework fields
framework: {
id: 'framework',
- type: 'select',
+ type: 'combobox',
label: 'Framework',
+ combobox: {
+ placeholder: 'Select framework',
+ searchPlaceholder: 'Search frameworks...',
+ emptyMessage: 'No frameworks found',
+ },
options: { source: 'frameworks' },
defaultValue: 'nextjs',
},
@@ -376,9 +381,14 @@ export const connectSchema: ConnectSchema = {
// MCP fields
mcpClient: {
id: 'mcpClient',
- type: 'select',
+ type: 'combobox',
label: 'Client',
description: 'The MCP client you are using.',
+ combobox: {
+ placeholder: 'Select client',
+ searchPlaceholder: 'Search clients...',
+ emptyMessage: 'No clients found',
+ },
options: { source: 'mcpClients' },
defaultValue: 'claude-code',
},
diff --git a/apps/studio/tests/components/interfaces/ConnectSheet/ConnectConfigSection.utils.test.ts b/apps/studio/tests/components/interfaces/ConnectSheet/ConnectConfigSection.utils.test.ts
new file mode 100644
index 00000000000..30202d96e8a
--- /dev/null
+++ b/apps/studio/tests/components/interfaces/ConnectSheet/ConnectConfigSection.utils.test.ts
@@ -0,0 +1,29 @@
+import { describe, expect, test } from 'vitest'
+
+import { getOptionMatchScore } from '@/components/interfaces/ConnectSheet/ConnectConfigSection.utils'
+
+describe('ConnectConfigSection utils: getOptionMatchScore', () => {
+ test.each(['', ' '])('matches a blank search (%j)', (search) => {
+ expect(getOptionMatchScore('Next.js', search, ['nextjs'])).toBe(1)
+ })
+
+ test('matches a substring of the option label', () => {
+ expect(getOptionMatchScore('Next.js', 'xt.j')).toBe(1)
+ })
+
+ test('matches a substring of an option keyword', () => {
+ expect(getOptionMatchScore('Next.js', 'nextjs', ['nextjs'])).toBe(1)
+ })
+
+ test('matches case-insensitively and ignores surrounding search whitespace', () => {
+ expect(getOptionMatchScore('React Native', ' NATIVE ')).toBe(1)
+ })
+
+ test('does not match when the search is absent from the label and keywords', () => {
+ expect(getOptionMatchScore('Next.js', 'flutter', ['nextjs'])).toBe(0)
+ })
+
+ test('does not require keywords', () => {
+ expect(getOptionMatchScore('Next.js', 'nextjs')).toBe(0)
+ })
+})
From 1131e3e2ce0bf6e7a7577fc4da46945697002ab7 Mon Sep 17 00:00:00 2001
From: Danny White <3104761+dnywh@users.noreply.github.com>
Date: Thu, 10 Sep 2026 11:23:17 +1000
Subject: [PATCH 010/614] fix(ui): default Button variant to default instead of
primary (#50160)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What kind of change does this PR introduce?
Bug fix / design-system alignment for the legacy `Button` from `ui`.
## What is the current behavior?
Omitting `variant` on the legacy `Button` falls back to brand-green
`primary`. That makes accidental greens easy, and it is hard to spot the
real main action on busy pages.
## What is the new behavior?
- Legacy `Button` now defaults to neutral `default`
- Intentional primary CTAs (create, save, submit, marketing CTAs, and
matching `ButtonTooltip` usages) now set `variant="primary"` so their
appearance is unchanged
- Neutral actions that previously relied on the old fallback (cancel,
close, back, dashboard nav, and similar) become grey/white
- Design-system docs updated; regression tests cover the new default
`Button_Shadcn_` is unchanged. It already uses its own CVA default.
This is PR 1 of 2 in a stack. PR 2 drops now-redundant
`variant="default"` props.
## To test
Studio (http://localhost:8082):
- `/sign-in`: Sign in stays green
- Open a project → Database → Tables: New table stays green
- Auth → Users → Invite: Invite user stays green; Cancel / dismiss
controls stay neutral
- Project Settings → General: edit a field so Cancel and Save appear.
Cancel is neutral, Save is green
Design system (http://localhost:3003):
- Components → Button: default demo is neutral; primary demo is green;
featured preview is the default variant
Marketing (optional):
- www header: Start your project stays green; logged-in Dashboard is
neutral
## Summary by CodeRabbit
- **Style**
- Buttons now default to a neutral style, while primary actions across
Studio, documentation, marketing pages, forms, dialogs, and error states
use prominent primary styling.
- Updated button examples and previews clarify the distinction between
default and primary variants.
- Event registration now includes a directional arrow icon.
- **Tests**
- Added coverage confirming default button styling and explicit primary
styling behave as expected.
- Updated related test fixtures to use primary styling where
appropriate.
---
.../content/docs/components/button.mdx | 19 +++++++------------
.../default/example/button-as-child.tsx | 2 +-
.../default/example/button-loading.tsx | 2 +-
.../default/example/button-with-icon.tsx | 6 +++++-
.../default/example/calendar-form.tsx | 4 +++-
.../example/calendar-react-hook-form.tsx | 4 +++-
.../example/checkbox-form-multiple.tsx | 4 +++-
.../default/example/checkbox-form-single.tsx | 4 +++-
.../default/example/date-picker-form.tsx | 4 +++-
.../default/example/dialog-centered-off.tsx | 4 +++-
.../registry/default/example/dialog-demo.tsx | 2 +-
.../registry/default/example/drawer-demo.tsx | 2 +-
.../default/example/drawer-dialog.tsx | 4 +++-
.../registry/default/example/field-demo.tsx | 4 +++-
.../default/example/field-responsive.tsx | 4 +++-
.../default/example/input-otp-form.tsx | 4 +++-
.../example/multi-select-in-dialog.tsx | 2 +-
.../default/example/radio-group-form.tsx | 4 +++-
.../example/sheet-confirm-on-close-demo.tsx | 2 +-
.../registry/default/example/tabs-demo.tsx | 4 ++--
.../default/example/textarea-with-button.tsx | 2 +-
.../AppleSecretGenerator.tsx | 1 +
.../components/Feedback/FeedbackModal.tsx | 8 +++++++-
.../NavigationMenu/GlobalMobileMenu.tsx | 2 +-
.../Navigation/NavigationMenu/TopNavBar.tsx | 2 +-
.../APIKeys/CreateNewAPIKeysButton.tsx | 4 +++-
.../APIKeys/CreatePublishableAPIKeyDialog.tsx | 2 +-
.../APIKeys/CreateSecretAPIKeyDialog.tsx | 2 +-
.../AccessTokens/Classic/NewTokenButton.tsx | 1 +
.../AccessTokens/Classic/NewTokenDialog.tsx | 2 +-
.../Scoped/Form/NewScopedTokenForm.tsx | 10 +++++++---
.../Scoped/Form/NewScopedTokenSuccess.tsx | 2 +-
.../Account/Preferences/AddPasswordRow.tsx | 2 +-
.../Preferences/ChangeEmailAddress.tsx | 2 +-
.../interfaces/Advisors/CreateRuleSheet.tsx | 2 +-
.../App/IndirectTaxDeclarationModal.tsx | 1 +
.../Auth/AuthProvidersForm/ProviderForm.tsx | 1 +
.../CreateOrUpdateCustomProviderSheet.tsx | 2 +-
.../interfaces/Auth/Hooks/CreateHookSheet.tsx | 1 +
.../OAuthApps/CreateOrUpdateOAuthAppSheet.tsx | 7 ++++++-
.../Auth/OAuthApps/OAuthAppsList.tsx | 1 +
.../Auth/RedirectUrls/AddNewURLModal.tsx | 1 +
.../Auth/RedirectUrls/RedirectUrlList.tsx | 1 +
.../ThirdPartyAuthForm/CreateAuth0Dialog.tsx | 8 +++++++-
.../CreateAwsCognitoAuthDialog.tsx | 8 +++++++-
.../CreateClerkAuthDialog.tsx | 8 +++++++-
.../CreateFirebaseAuthDialog.tsx | 8 +++++++-
.../ThirdPartyAuthForm/CreateWorkOSDialog.tsx | 8 +++++++-
.../interfaces/Auth/Users/CreateUserModal.tsx | 1 +
.../interfaces/Auth/Users/InviteUserModal.tsx | 1 +
.../Database/Backups/PITR/PITRStatus.tsx | 1 +
.../ConfirmRestoreDialog.tsx | 4 +++-
.../CreateNewProjectDialog.tsx | 2 +-
.../Extensions/EnableExtensionModal.tsx | 1 +
.../Functions/CreateFunction/index.tsx | 1 +
.../Database/Hooks/HTTPRequestConfig.tsx | 2 +-
.../Database/Hooks/HooksList/HooksList.tsx | 1 +
.../Policies/PolicyEditorPanel/index.tsx | 1 +
.../DestinationForm/DuckLake/Fields.tsx | 1 +
.../DestinationForm/index.tsx | 8 +++++++-
.../interfaces/Database/Tables/TableList.tsx | 8 +++++++-
.../Database/Triggers/TriggerSheet.tsx | 7 ++++++-
.../interfaces/Docs/Description.tsx | 2 +-
.../interfaces/Explorer/ExplorerQueryTab.tsx | 4 +++-
.../EdgeFunctionSecrets/EditSecretSheet.tsx | 8 +++++++-
.../CronJobsTab.EnableCleanupButton.tsx | 2 +-
.../CronJobs/CronJobsTab.Header.tsx | 4 +++-
.../CronJobs/EdgeFunctionSection.tsx | 2 +-
.../Integrations/Queues/QueuesTab.tsx | 4 +++-
.../Queues/SingleQueue/QueueDataGrid.tsx | 2 +-
.../Queues/UpgradeDatabaseAlert.tsx | 2 +-
.../Vault/Secrets/AddNewSecretModal.tsx | 1 +
.../Vault/Secrets/EditSecretModal.tsx | 2 +-
.../Integrations/Webhooks/OverviewTab.tsx | 1 +
.../create-key-dialog.tsx | 1 +
.../jwt-secret-keys-table/index.tsx | 1 +
.../rotate-key-dialog.tsx | 1 +
.../NewAwsMarketplaceOrgModal.tsx | 1 +
.../InvoicesSettings/InvoicePayButton.tsx | 2 +-
.../OAuthApps/PublishAppSidePanel/index.tsx | 7 ++++++-
.../Organization/ProjectClaim/benefits.tsx | 2 +-
.../Organization/ProjectClaim/confirm.tsx | 8 +++++++-
.../UpdateRolesPanel/UpdateRolesPanel.tsx | 1 +
.../PlatformWebhooksEndpointSheet.tsx | 2 +-
.../ProjectCreation/ProjectCreationFooter.tsx | 1 +
.../IndexAdvisor/IndexSuggestionIcon.tsx | 7 ++++++-
.../QueryPerformance/QueryDetail.tsx | 2 +-
.../QuerySources/LogsCustomRangeDialog.tsx | 2 +-
.../Inspector/RealtimeFilterPopover/index.tsx | 4 +++-
.../interfaces/Reports/CreateReportModal.tsx | 7 ++++++-
.../interfaces/Reports/UpdateModal.tsx | 7 ++++++-
.../interfaces/SQLEditor/RenameQueryModal.tsx | 7 ++++++-
.../SqlEditorManualSaveNoticeDialog.tsx | 4 +++-
.../AddRestrictionModal.tsx | 8 +++++++-
.../CustomDomainActivate.tsx | 1 +
.../CustomDomainConfig/CustomDomainVerify.tsx | 1 +
.../Infrastructure/ProjectUpgradeAlert.tsx | 7 ++++++-
.../TransferProjectButton.tsx | 1 +
.../ReadReplicas/ReadReplicaForm/index.tsx | 7 ++++++-
.../AWSPrivateLink/AWSPrivateLinkForm.tsx | 2 +-
.../Settings/Logs/Logs.DatePickers.tsx | 4 +++-
.../Logs/Logs.UpdateSavedQueryModal.tsx | 7 ++++++-
.../Settings/Logs/UpgradePrompt.tsx | 2 +-
.../SignIn/ForgotPasswordWizard.tsx | 10 +++++++++-
.../interfaces/SignIn/ResetPasswordForm.tsx | 1 +
.../interfaces/SignIn/SignInForm.tsx | 9 ++++++++-
.../interfaces/SignIn/SignInMfaForm.tsx | 1 +
.../interfaces/SignIn/SignInSSOForm.tsx | 9 ++++++++-
.../interfaces/SignIn/SignUpForm.tsx | 1 +
.../CreateTable/CreateTableSheet.tsx | 2 +-
.../CreateAnalyticsBucketForm.tsx | 1 +
.../BucketFilePickerPreviewPane.tsx | 1 +
.../interfaces/Storage/CreateBucketModal.tsx | 1 +
.../interfaces/Storage/EditBucketModal.tsx | 2 +-
.../StorageSettings/CreateCredentialModal.tsx | 2 +-
.../CreateVectorBucketDialog.tsx | 2 +-
.../VectorBuckets/CreateVectorTableSheet.tsx | 1 +
.../interfaces/Support/ProjectAndPlanInfo.tsx | 2 +-
.../interfaces/Support/SubmitButton.tsx | 1 +
.../FeedbackDropdown/FeedbackWidget.tsx | 1 +
.../layouts/ProjectLayout/RestoringState.tsx | 7 ++++++-
.../ProjectLayout/UpgradingState/index.tsx | 14 ++++++++++++--
.../components/ui/AutoEnableRLSNotice.tsx | 1 +
.../studio/components/ui/DatePicker/index.tsx | 4 +++-
.../ui/EditorPanel/SaveSnippetDialog.tsx | 2 +-
.../ui/RequestUpgradeToBillingOwners.tsx | 2 +-
apps/studio/hooks/use-check-latest-deploy.tsx | 1 +
apps/studio/pages/404.tsx | 2 +-
apps/studio/pages/500.tsx | 6 ++++--
.../[slug]/deploy-button/new-project.tsx | 4 +++-
.../[ref]/database/column-privileges.tsx | 2 +-
.../[ref]/functions/[functionSlug]/code.tsx | 1 +
.../pages/project/[ref]/functions/new.tsx | 1 +
apps/studio/tests/pages/sign-up.test.tsx | 3 ++-
.../www/app/(home)/_components/CTASection.tsx | 2 +-
apps/www/app/(home)/_components/Hero.tsx | 2 +-
.../partners/catalog/IntegrationsContent.tsx | 4 ++--
.../catalog/[slug]/PartnerCatalogDetail.tsx | 11 ++++++++---
.../StateOfStartups2026Content.tsx | 2 +-
.../register/RegisterContent.tsx | 7 ++++++-
apps/www/components/CTABanner/index.tsx | 2 +-
apps/www/components/CTASection.tsx | 2 +-
.../Contribute/SimilarSolvedThreads.tsx | 7 ++++++-
apps/www/components/Error404.tsx | 2 +-
.../www/components/Events/new/EventBanner.tsx | 7 ++++++-
.../components/Forms/ApplyToSupaSquadForm.tsx | 8 +++++++-
.../www/components/Forms/RequestADemoForm.tsx | 1 +
.../Forms/TalkToPartnershipTeamForm.tsx | 1 +
apps/www/components/Nav/MobileMenu.tsx | 2 +-
apps/www/components/Nav/index.tsx | 2 +-
apps/www/components/NewFeatureCard.tsx | 2 +-
apps/www/components/Pricing/UpgradePlan.tsx | 2 +-
.../www/components/Sections/EnterpriseCta.tsx | 4 ++--
.../www/components/Sections/ProductHeader.tsx | 2 +-
.../Sections/ProductHeaderCentered.tsx | 2 +-
.../Sections/ProductModulesHeader.tsx | 7 ++++---
apps/www/components/Solutions/CtaSection.tsx | 2 +-
apps/www/components/Supasquad/CtaSection.tsx | 2 +-
apps/www/pages/company.tsx | 4 +++-
apps/www/pages/opt-out/[ref].tsx | 7 ++++++-
apps/www/pages/partners/index.tsx | 2 +-
apps/www/pages/state-of-startups-2025.tsx | 2 +-
.../ui-patterns/src/PrivacySettings/index.tsx | 4 +++-
packages/ui-patterns/src/form/FormLayout2.tsx | 4 +++-
.../KeyValueFieldArray.test.tsx | 8 ++++++--
.../SingleValueFieldArray.test.tsx | 8 ++++++--
.../ui/src/components/Button/Button.test.tsx | 16 ++++++++++++++++
packages/ui/src/components/Button/Button.tsx | 2 +-
168 files changed, 454 insertions(+), 149 deletions(-)
diff --git a/apps/design-system/content/docs/components/button.mdx b/apps/design-system/content/docs/components/button.mdx
index fc7c1575839..6a89781e033 100644
--- a/apps/design-system/content/docs/components/button.mdx
+++ b/apps/design-system/content/docs/components/button.mdx
@@ -5,7 +5,7 @@ featured: true
component: true
---
-
+
## Usage
@@ -47,23 +47,18 @@ Use the `size` prop to determine the size of the button.
### Variants
-These are all the different `variant` variations.
+#### Default
+
+Used when no `variant` is specified. Prefer this unless another variant fits better, as below.
+
+
#### Primary
-Used for data insertion actions, confirming purchases, strong positive actions.
+Use sparingly for data insertion, confirming purchases, and other strong positive actions. Because it is so prominent, aim for at most one primary button in a viewport.
-#### Default
-
-Used for opening dialogs, navigating to pages, and other non CRUD actions.
-
-This `variant` will probably be the most used button variant.
-It will probably be changed to be the default variant in future.
-
-
-
#### Secondary
Can be used for signaling a data or config change, but not as serious as a primary button.
diff --git a/apps/design-system/registry/default/example/button-as-child.tsx b/apps/design-system/registry/default/example/button-as-child.tsx
index 8d8909cdb50..8504d95f394 100644
--- a/apps/design-system/registry/default/example/button-as-child.tsx
+++ b/apps/design-system/registry/default/example/button-as-child.tsx
@@ -3,7 +3,7 @@ import { Button } from 'ui'
export default function ButtonAsChild() {
return (
-
+ )
}
diff --git a/apps/design-system/registry/default/example/calendar-form.tsx b/apps/design-system/registry/default/example/calendar-form.tsx
index 99846d6f4b8..a4f5d66bf2d 100644
--- a/apps/design-system/registry/default/example/calendar-form.tsx
+++ b/apps/design-system/registry/default/example/calendar-form.tsx
@@ -84,7 +84,9 @@ export default function CalendarForm() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/calendar-react-hook-form.tsx b/apps/design-system/registry/default/example/calendar-react-hook-form.tsx
index 7cc4683d4ed..a9abdddb7ba 100644
--- a/apps/design-system/registry/default/example/calendar-react-hook-form.tsx
+++ b/apps/design-system/registry/default/example/calendar-react-hook-form.tsx
@@ -83,7 +83,9 @@ export default function CalendarForm() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/checkbox-form-multiple.tsx b/apps/design-system/registry/default/example/checkbox-form-multiple.tsx
index 006ad6bad2a..4b3df7eafed 100644
--- a/apps/design-system/registry/default/example/checkbox-form-multiple.tsx
+++ b/apps/design-system/registry/default/example/checkbox-form-multiple.tsx
@@ -112,7 +112,9 @@ export default function CheckboxReactHookFormMultiple() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/checkbox-form-single.tsx b/apps/design-system/registry/default/example/checkbox-form-single.tsx
index f18d4a172ef..8fed391e74e 100644
--- a/apps/design-system/registry/default/example/checkbox-form-single.tsx
+++ b/apps/design-system/registry/default/example/checkbox-form-single.tsx
@@ -59,7 +59,9 @@ export default function CheckboxReactHookFormSingle() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/date-picker-form.tsx b/apps/design-system/registry/default/example/date-picker-form.tsx
index 129fc91412f..422c302aafa 100644
--- a/apps/design-system/registry/default/example/date-picker-form.tsx
+++ b/apps/design-system/registry/default/example/date-picker-form.tsx
@@ -76,7 +76,9 @@ export default function DatePickerForm() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/dialog-centered-off.tsx b/apps/design-system/registry/default/example/dialog-centered-off.tsx
index ea35e2dd951..efaa47eea70 100644
--- a/apps/design-system/registry/default/example/dialog-centered-off.tsx
+++ b/apps/design-system/registry/default/example/dialog-centered-off.tsx
@@ -40,7 +40,9 @@ export default function DialogDemo() {
- Save changes
+
+ Save changes
+
diff --git a/apps/design-system/registry/default/example/dialog-demo.tsx b/apps/design-system/registry/default/example/dialog-demo.tsx
index 9d6972d7b63..9a06b6661fb 100644
--- a/apps/design-system/registry/default/example/dialog-demo.tsx
+++ b/apps/design-system/registry/default/example/dialog-demo.tsx
@@ -36,7 +36,7 @@ export default function DialogDemo() {
- Save changes
+ Save changes
diff --git a/apps/design-system/registry/default/example/drawer-demo.tsx b/apps/design-system/registry/default/example/drawer-demo.tsx
index 1208680a3d0..86cb0d8696b 100644
--- a/apps/design-system/registry/default/example/drawer-demo.tsx
+++ b/apps/design-system/registry/default/example/drawer-demo.tsx
@@ -121,7 +121,7 @@ export default function DrawerDemo() {
- Submit
+ SubmitCancel
diff --git a/apps/design-system/registry/default/example/drawer-dialog.tsx b/apps/design-system/registry/default/example/drawer-dialog.tsx
index b34a0df1ca9..b9473a1cba9 100644
--- a/apps/design-system/registry/default/example/drawer-dialog.tsx
+++ b/apps/design-system/registry/default/example/drawer-dialog.tsx
@@ -81,7 +81,9 @@ function ProfileForm({ className }: React.ComponentProps<'form'>) {
- Save changes
+
+ Save changes
+
)
}
diff --git a/apps/design-system/registry/default/example/field-demo.tsx b/apps/design-system/registry/default/example/field-demo.tsx
index 45cad3c1f18..b14770153c8 100644
--- a/apps/design-system/registry/default/example/field-demo.tsx
+++ b/apps/design-system/registry/default/example/field-demo.tsx
@@ -113,7 +113,9 @@ export default function FieldDemo() {
- Submit
+
+ Submit
+
Cancel
diff --git a/apps/design-system/registry/default/example/field-responsive.tsx b/apps/design-system/registry/default/example/field-responsive.tsx
index c59bbbeb254..82eee0cb3b2 100644
--- a/apps/design-system/registry/default/example/field-responsive.tsx
+++ b/apps/design-system/registry/default/example/field-responsive.tsx
@@ -45,7 +45,9 @@ export default function FieldResponsive() {
- Submit
+
+ Submit
+
Cancel
diff --git a/apps/design-system/registry/default/example/input-otp-form.tsx b/apps/design-system/registry/default/example/input-otp-form.tsx
index 41c6cb342b8..8e30f133cff 100644
--- a/apps/design-system/registry/default/example/input-otp-form.tsx
+++ b/apps/design-system/registry/default/example/input-otp-form.tsx
@@ -71,7 +71,9 @@ export default function InputOTPForm() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/multi-select-in-dialog.tsx b/apps/design-system/registry/default/example/multi-select-in-dialog.tsx
index 4e44a6ad574..9e742d41b24 100644
--- a/apps/design-system/registry/default/example/multi-select-in-dialog.tsx
+++ b/apps/design-system/registry/default/example/multi-select-in-dialog.tsx
@@ -63,7 +63,7 @@ export default function MultiSelectDemo() {
- Save changes
+ Save changes
diff --git a/apps/design-system/registry/default/example/radio-group-form.tsx b/apps/design-system/registry/default/example/radio-group-form.tsx
index 8893a529bdb..76beb6a4d15 100644
--- a/apps/design-system/registry/default/example/radio-group-form.tsx
+++ b/apps/design-system/registry/default/example/radio-group-form.tsx
@@ -76,7 +76,9 @@ export default function RadioGroupForm() {
)}
/>
- Submit
+
+ Submit
+
)
diff --git a/apps/design-system/registry/default/example/sheet-confirm-on-close-demo.tsx b/apps/design-system/registry/default/example/sheet-confirm-on-close-demo.tsx
index d7a4aa06e3c..02992722d69 100644
--- a/apps/design-system/registry/default/example/sheet-confirm-on-close-demo.tsx
+++ b/apps/design-system/registry/default/example/sheet-confirm-on-close-demo.tsx
@@ -213,7 +213,7 @@ export default function SheetConfirmOnCloseDemo() {
Cancel
-
+
Save changes
diff --git a/apps/design-system/registry/default/example/tabs-demo.tsx b/apps/design-system/registry/default/example/tabs-demo.tsx
index edf748e9718..63771d8116a 100644
--- a/apps/design-system/registry/default/example/tabs-demo.tsx
+++ b/apps/design-system/registry/default/example/tabs-demo.tsx
@@ -40,7 +40,7 @@ export default function TabsDemo() {
- Save changes
+ Save changes
@@ -63,7 +63,7 @@ export default function TabsDemo() {
- Save password
+ Save password
diff --git a/apps/design-system/registry/default/example/textarea-with-button.tsx b/apps/design-system/registry/default/example/textarea-with-button.tsx
index 05f3200e6c2..3ba8c27f845 100644
--- a/apps/design-system/registry/default/example/textarea-with-button.tsx
+++ b/apps/design-system/registry/default/example/textarea-with-button.tsx
@@ -4,7 +4,7 @@ export default function TextareaWithButton() {
return (
)
diff --git a/e2e/studio/features/assistant.spec.ts b/e2e/studio/features/assistant.spec.ts
index c549040e531..d510ca77c12 100644
--- a/e2e/studio/features/assistant.spec.ts
+++ b/e2e/studio/features/assistant.spec.ts
@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
+
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
@@ -16,10 +17,12 @@ test.describe('AI Assistant', async () => {
await page.locator('#assistant-trigger').click()
// Wait for the assistant panel to be visible
- await expect(page.getByRole('heading', { name: 'How can I assist you?' })).toBeVisible()
+ await expect(page.getByRole('heading', { name: 'Chat with your project' })).toBeVisible()
// Type "hello" in the chat input
- const chatInput = page.getByRole('textbox', { name: 'Chat to Postgres...' })
+ const chatInput = page.getByRole('textbox', {
+ name: 'Ask about your data, troubleshoot an issue, or explore your project...',
+ })
await chatInput.fill('hello')
const responsePromise = page.waitForResponse(
From c708e1128f3d34901548168e19df39c2f5045424 Mon Sep 17 00:00:00 2001
From: Anthony Lio
Date: Thu, 10 Sep 2026 09:14:42 +0300
Subject: [PATCH 012/614] fix(ui-patterns): reveal hover-only copy controls on
keyboard focus (#50083)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What kind of change does this PR introduce?
bug fix + a11y _ follow-up to the UI review on #50045
## What is the current behavior?
**`CodeBlock`**: the copy control lives in an `opacity-0
group-hover:opacity-100` wrapper with no focus rule, so it stays
invisible when a keyboard user tabs to it, button is focusable and
pressable, just not visible
**`DataInputs/Input`**: same wrapper, but the parent `InputGroup`
declares a *named* group (`group/input-group`), so the unnamed
`group-hover:` matched nothing. With `showCopyOnHover` the button was
invisible at all times, hover included. Only consumer today is the Edge
Functions "Download via CLI" popover.
## What is the new behavior?
├ adds `group-focus-within:opacity-100` to `CodeBlock`
| state | preview |
| -------|------|
| before | |
| after | |
├ retargets both variants at the named group: `group-hover/input-group:`
+ `group-focus-within/input-group:` in `Input`
| state | preview |
| -------|------|
| before | |
| after | |
## Testing
1. visits `/docs/guides/ai-tools/plugins#manual-installation`
2. tabs into the code block
## Summary by CodeRabbit
- **Accessibility Improvements**
- Copy buttons in code blocks and input fields are now revealed when the
component or its contents receive keyboard focus, in addition to
appearing on hover.
- Improved keyboard discoverability and access to copy actions.
---
packages/ui-patterns/src/CodeBlock/CodeBlock.tsx | 2 +-
packages/ui-patterns/src/DataInputs/Input.tsx | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/ui-patterns/src/CodeBlock/CodeBlock.tsx b/packages/ui-patterns/src/CodeBlock/CodeBlock.tsx
index fbd3345554e..8d41e769e0b 100644
--- a/packages/ui-patterns/src/CodeBlock/CodeBlock.tsx
+++ b/packages/ui-patterns/src/CodeBlock/CodeBlock.tsx
@@ -269,7 +269,7 @@ export const CodeBlock = ({
diff --git a/packages/ui-patterns/src/DataInputs/Input.tsx b/packages/ui-patterns/src/DataInputs/Input.tsx
index 98efedae67f..128eb19c963 100644
--- a/packages/ui-patterns/src/DataInputs/Input.tsx
+++ b/packages/ui-patterns/src/DataInputs/Input.tsx
@@ -91,7 +91,10 @@ const Input = forwardRef<
}
onClick={() => _onCopy(props.value)}
>
From 84db103ebb6dbe96ef14951265e2e54f3e1d66d4 Mon Sep 17 00:00:00 2001
From: Jordi Enric <37541088+jordienr@users.noreply.github.com>
Date: Thu, 10 Sep 2026 08:51:24 +0200
Subject: [PATCH 013/614] ci(api): verify generated types against production
(#49993)
---
.agents/skills/api-types/SKILL.md | 27 ++++
.github/workflows/label_prs.yml | 2 +-
.github/workflows/validate-pr.yml | 6 -
.../workflows/verify-production-api-types.yml | 50 ++++++++
package.json | 1 +
packages/api-types/package.json | 4 +-
.../scripts/verify-production-types.mjs | 121 ++++++++++++++++++
.../scripts/verify-production-types.test.mjs | 87 +++++++++++++
8 files changed, 290 insertions(+), 8 deletions(-)
create mode 100644 .agents/skills/api-types/SKILL.md
create mode 100644 .github/workflows/verify-production-api-types.yml
create mode 100644 packages/api-types/scripts/verify-production-types.mjs
create mode 100644 packages/api-types/scripts/verify-production-types.test.mjs
diff --git a/.agents/skills/api-types/SKILL.md b/.agents/skills/api-types/SKILL.md
new file mode 100644
index 00000000000..3d206497a2c
--- /dev/null
+++ b/.agents/skills/api-types/SKILL.md
@@ -0,0 +1,27 @@
+---
+name: api-types
+description: Maintain Supabase API types. Use when changing generated API type declarations, OpenAPI schemas, or investigating API type deployment drift.
+---
+
+# API types
+
+The generated API contract has three specs: API v1, API v2, and Platform. Their committed outputs are `packages/api-types/types/api-v1.d.ts`, `packages/api-types/types/api-v2.d.ts`, and `packages/api-types/types/platform.d.ts`.
+
+## Update types
+
+1. Make the API/schema change and ensure it is deployed to production before relying on a type PR. Production is the merge-gate source of truth.
+2. Run `pnpm api:codegen` against a running local API environment. It fetches all three local OpenAPI specs and updates the committed files.
+3. Inspect and commit only the intended generated type changes.
+4. Run `pnpm api:verify-types`. It fetches the three production OpenAPI specs, regenerates types with the repository tooling, and compares them with the committed files.
+
+Complete the update only when `pnpm api:verify-types` passes after the production deployment is available.
+
+## Interpret verification
+
+- A pass means the committed generated declarations match all three production specs at the time of the check.
+- A mismatch means production and the committed files differ. If the API is not deployed, deploy it and rerun the check. If production is correct, regenerate and review the changed files.
+- A fetch failure means the production schema endpoint could not be read; fix or retry the endpoint before treating the result as a type mismatch.
+
+## Pull requests
+
+The `Verify production API types` CI job runs when `packages/api-types/types/**` changes and performs the same production comparison. It is currently observational, not a required merge check. The `api-deploy-required` label is informational only. Still run the local verifier before requesting review and treat a failed CI verification as production drift that must be resolved.
diff --git a/.github/workflows/label_prs.yml b/.github/workflows/label_prs.yml
index 9e9ac01756e..50af84c9457 100644
--- a/.github/workflows/label_prs.yml
+++ b/.github/workflows/label_prs.yml
@@ -22,5 +22,5 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
- body: 'The `api-deploy-required` label was auto-applied to this PR because it updates the API types. Ensure that the new or updated API, if any, is deployed on production before **removing the label** and merging this PR.',
+ body: 'The `api-deploy-required` label was auto-applied to this PR because it updates the API types. The `Verify production API types` check reports whether the committed types match production; it is currently observational and does not block merging.',
})
diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml
index 2ee49d84c8e..1b10f526421 100644
--- a/.github/workflows/validate-pr.yml
+++ b/.github/workflows/validate-pr.yml
@@ -17,12 +17,6 @@ jobs:
echo "PR blocked: [tag: do not merge]"
exit 1
- - name: Tagged with 'api-deploy-required'
- if: contains( github.event.pull_request.labels.*.name, 'api-deploy-required')
- run: |
- echo "PR blocked: [tag: api-deploy-required] — confirm the API is deployed in production, then remove the label."
- exit 1
-
- name: All good
if: ${{ success() }}
run: |
diff --git a/.github/workflows/verify-production-api-types.yml b/.github/workflows/verify-production-api-types.yml
new file mode 100644
index 00000000000..bc99921ca89
--- /dev/null
+++ b/.github/workflows/verify-production-api-types.yml
@@ -0,0 +1,50 @@
+name: Verify production API types
+
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+
+permissions:
+ contents: read
+ pull-requests: read
+
+jobs:
+ verify-production-api-types:
+ runs-on: ubuntu-latest
+ steps:
+ - id: changes
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ if gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --paginate --jq '.[].filename' | grep -q '^packages/api-types/types/'; then
+ echo "api_types_changed=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "api_types_changed=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Check out pull request
+ if: steps.changes.outputs.api_types_changed == 'true'
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ with:
+ persist-credentials: false
+
+ - name: Install pnpm
+ if: steps.changes.outputs.api_types_changed == 'true'
+ uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
+ with:
+ run_install: false
+
+ - name: Set up Node.js
+ if: steps.changes.outputs.api_types_changed == 'true'
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version-file: '.nvmrc'
+ cache: pnpm
+
+ - name: Install API types dependencies
+ if: steps.changes.outputs.api_types_changed == 'true'
+ run: pnpm install --frozen-lockfile --filter=api-types...
+
+ - name: Verify production API types
+ if: steps.changes.outputs.api_types_changed == 'true'
+ run: pnpm --filter=api-types run verify-production-types
diff --git a/package.json b/package.json
index aa441729e00..e1f7d90f0d2 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"setup:cli": "supabase start -x studio && supabase status --output json > keys.json && node scripts/generateLocalEnv.js",
"generate:types": "supabase gen types typescript --local > ./supabase/functions/common/database-types.ts",
"api:codegen": "cd packages/api-types && pnpm run codegen",
+ "api:verify-types": "pnpm --filter=api-types run verify-production-types",
"knip": "knip",
"authorize-vercel-deploys": "tsx scripts/authorizeVercelDeploys.ts"
},
diff --git a/packages/api-types/package.json b/packages/api-types/package.json
index d975cab4de8..f230553959b 100644
--- a/packages/api-types/package.json
+++ b/packages/api-types/package.json
@@ -7,7 +7,9 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"clean": "rimraf .turbo tsconfig.tsbuildinfo",
- "codegen": "openapi-typescript --redocly ./redocly.yaml --alphabetize --default-non-nullable=false && prettier --cache --write types/*.d.ts"
+ "codegen": "openapi-typescript --redocly ./redocly.yaml --alphabetize --default-non-nullable=false && prettier --cache --write types/*.d.ts",
+ "verify-production-types": "node ./scripts/verify-production-types.mjs",
+ "test": "node --test ./scripts/*.test.mjs"
},
"author": "",
"license": "MIT",
diff --git a/packages/api-types/scripts/verify-production-types.mjs b/packages/api-types/scripts/verify-production-types.mjs
new file mode 100644
index 00000000000..308de036e7c
--- /dev/null
+++ b/packages/api-types/scripts/verify-production-types.mjs
@@ -0,0 +1,121 @@
+import { execFile } from 'node:child_process'
+import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { promisify } from 'node:util'
+
+const run = promisify(execFile)
+const packageDirectory = dirname(dirname(fileURLToPath(import.meta.url)))
+const typesDirectory = process.env.API_TYPES_DIRECTORY ?? join(packageDirectory, 'types')
+const platformApiUrl =
+ process.env.PLATFORM_API_OPENAPI_URL ?? 'https://api.supabase.com/api/platform-json'
+const fetchTimeout = 30_000
+
+const specifications = [
+ { name: 'api-v1', url: 'https://api.supabase.com/api/v1-json' },
+ { name: 'api-v2', url: 'https://api.supabase.com/api/v2-json' },
+ { name: 'platform', url: platformApiUrl },
+]
+
+export async function fetchOpenApiSpecifications(
+ specifications,
+ { fetchImpl = fetch, writeFileImpl = writeFile, temporaryDirectory, generatedTypesDirectory }
+) {
+ return Promise.all(
+ specifications.map(async ({ name, url }) => {
+ let response
+
+ try {
+ response = await fetchImpl(url, {
+ signal: AbortSignal.timeout(fetchTimeout),
+ })
+ } catch {
+ throw new Error(`Could not fetch ${name} OpenAPI specification from ${url}.`)
+ }
+
+ if (!response.ok) {
+ throw new Error(
+ `Could not fetch ${name} OpenAPI specification from ${url}: ${response.status}.`
+ )
+ }
+
+ await writeFileImpl(join(temporaryDirectory, `${name}.json`), await response.text())
+
+ return ` ${name}:\n root: ${join(temporaryDirectory, `${name}.json`)}\n x-openapi-ts:\n output: ${join(generatedTypesDirectory, `${name}.d.ts`)}`
+ })
+ )
+}
+
+export async function findMismatchedTypes(
+ specifications,
+ { readFileImpl = readFile, generatedTypesDirectory, typesDirectory }
+) {
+ const mismatches = await Promise.all(
+ specifications.map(async ({ name }) => {
+ const filename = `${name}.d.ts`
+ const [generated, committed] = await Promise.all([
+ readFileImpl(join(generatedTypesDirectory, filename), 'utf8'),
+ readFileImpl(join(typesDirectory, filename), 'utf8'),
+ ])
+
+ return generated === committed ? undefined : filename
+ })
+ )
+
+ return mismatches.filter((filename) => filename !== undefined)
+}
+
+export async function verifyProductionTypes() {
+ const temporaryDirectory = await mkdtemp(join(tmpdir(), 'api-types-'))
+ const generatedTypesDirectory = join(temporaryDirectory, 'types')
+
+ try {
+ await mkdir(generatedTypesDirectory)
+
+ const config = await fetchOpenApiSpecifications(specifications, {
+ temporaryDirectory,
+ generatedTypesDirectory,
+ })
+
+ await writeFile(join(temporaryDirectory, 'redocly.yaml'), `apis:\n${config.join('\n')}`)
+ await run(
+ 'pnpm',
+ [
+ 'exec',
+ 'openapi-typescript',
+ '--redocly',
+ join(temporaryDirectory, 'redocly.yaml'),
+ '--alphabetize',
+ '--default-non-nullable=false',
+ ],
+ { cwd: packageDirectory }
+ )
+
+ await run(
+ 'pnpm',
+ [
+ 'exec',
+ 'prettier',
+ '--write',
+ ...specifications.map(({ name }) => join(generatedTypesDirectory, `${name}.d.ts`)),
+ ],
+ { cwd: packageDirectory }
+ )
+
+ const changedTypes = await findMismatchedTypes(specifications, {
+ generatedTypesDirectory,
+ typesDirectory,
+ })
+
+ if (changedTypes.length > 0) {
+ throw new Error(`Committed API types do not match production: ${changedTypes.join(', ')}`)
+ }
+ } finally {
+ await rm(temporaryDirectory, { force: true, recursive: true })
+ }
+}
+
+if (process.argv[1] === fileURLToPath(import.meta.url)) {
+ await verifyProductionTypes()
+}
diff --git a/packages/api-types/scripts/verify-production-types.test.mjs b/packages/api-types/scripts/verify-production-types.test.mjs
new file mode 100644
index 00000000000..f2c67b2cecc
--- /dev/null
+++ b/packages/api-types/scripts/verify-production-types.test.mjs
@@ -0,0 +1,87 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+
+import { fetchOpenApiSpecifications, findMismatchedTypes } from './verify-production-types.mjs'
+
+const specifications = [
+ { name: 'api-v1', url: 'https://example.com/api/v1-json' },
+ { name: 'platform', url: 'https://example.com/api/platform-json' },
+]
+
+test('writes fetched specifications and returns their Redocly configuration', async () => {
+ const writes = []
+ const config = await fetchOpenApiSpecifications(specifications, {
+ temporaryDirectory: '/tmp/api-types',
+ generatedTypesDirectory: '/tmp/api-types/types',
+ fetchImpl: async (url) => ({
+ ok: true,
+ text: async () => `OpenAPI specification from ${url}`,
+ }),
+ writeFileImpl: async (path, content) => writes.push({ path, content }),
+ })
+
+ assert.deepEqual(writes, [
+ {
+ path: '/tmp/api-types/api-v1.json',
+ content: 'OpenAPI specification from https://example.com/api/v1-json',
+ },
+ {
+ path: '/tmp/api-types/platform.json',
+ content: 'OpenAPI specification from https://example.com/api/platform-json',
+ },
+ ])
+ assert.deepEqual(config, [
+ ' api-v1:\n root: /tmp/api-types/api-v1.json\n x-openapi-ts:\n output: /tmp/api-types/types/api-v1.d.ts',
+ ' platform:\n root: /tmp/api-types/platform.json\n x-openapi-ts:\n output: /tmp/api-types/types/platform.d.ts',
+ ])
+})
+
+test('returns no mismatches when generated types match committed types', async () => {
+ const mismatches = await findMismatchedTypes(specifications, {
+ generatedTypesDirectory: '/generated',
+ typesDirectory: '/committed',
+ readFileImpl: async (path) =>
+ path.includes('/generated/') ? 'generated type' : 'generated type',
+ })
+
+ assert.deepEqual(mismatches, [])
+})
+
+test('reports every generated type that differs from its committed counterpart', async () => {
+ const mismatches = await findMismatchedTypes(specifications, {
+ generatedTypesDirectory: '/generated',
+ typesDirectory: '/committed',
+ readFileImpl: async (path) => {
+ if (path.endsWith('api-v1.d.ts')) return 'matching type'
+ return path.includes('/generated/') ? 'new platform type' : 'committed platform type'
+ },
+ })
+
+ assert.deepEqual(mismatches, ['platform.d.ts'])
+})
+
+test('rejects unsuccessful OpenAPI responses', async () => {
+ await assert.rejects(
+ fetchOpenApiSpecifications([specifications[0]], {
+ temporaryDirectory: '/tmp/api-types',
+ generatedTypesDirectory: '/tmp/api-types/types',
+ fetchImpl: async () => ({ ok: false, status: 503 }),
+ writeFileImpl: async () => assert.fail('does not write an unsuccessful response'),
+ }),
+ /Could not fetch api-v1 OpenAPI specification from https:\/\/example\.com\/api\/v1-json: 503\./
+ )
+})
+
+test('rejects failed OpenAPI requests', async () => {
+ await assert.rejects(
+ fetchOpenApiSpecifications([specifications[0]], {
+ temporaryDirectory: '/tmp/api-types',
+ generatedTypesDirectory: '/tmp/api-types/types',
+ fetchImpl: async () => {
+ throw new Error('network unavailable')
+ },
+ writeFileImpl: async () => assert.fail('does not write a failed response'),
+ }),
+ /Could not fetch api-v1 OpenAPI specification from https:\/\/example\.com\/api\/v1-json\./
+ )
+})
From b8b92fe5661dffeb58b66910d6e63373d01e945a Mon Sep 17 00:00:00 2001
From: Anthony Lio
Date: Thu, 10 Sep 2026 10:15:30 +0300
Subject: [PATCH 014/614] fix(www): careers page error (#50185)
## What kind of change does this PR introduce?
bug fix careers page on anchor link click
## What is the current behavior?
on `/careers`, clicking "open positions", scrolling down and back up,
then clicking it again crashes the page
## What is the new behavior?
destructuring defaults on the page props, so an empty-props render is
harmless instead of fatal _ prefetch still returns `{}`, but the
sequence now renders normally instead of throwin
## Summary by CodeRabbit
* **Bug Fixes**
* Improved the careers page so it renders correctly when job listings,
placeholder job details, or contributor information are unavailable.
---
apps/www/pages/careers.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/apps/www/pages/careers.tsx b/apps/www/pages/careers.tsx
index b1788318a12..5ba598e4b93 100644
--- a/apps/www/pages/careers.tsx
+++ b/apps/www/pages/careers.tsx
@@ -96,7 +96,11 @@ interface CareersPageProps {
contributors: { login: string; avatar_url: string; html_url: string }[]
}
-const CareerPage = ({ jobs, placeholderJob, contributors }: CareersPageProps) => {
+const CareerPage = ({
+ jobs = {},
+ placeholderJob = null,
+ contributors = [],
+}: Partial) => {
const { basePath } = useRouter()
const { jobsCount } = staticContent
From d9742d707f51e998e60037175d7310c6f6ba60d6 Mon Sep 17 00:00:00 2001
From: Saxon Fletcher
Date: Thu, 10 Sep 2026 17:38:19 +1000
Subject: [PATCH 015/614] chore(studio): refine Explorer sidebar breadcrumbs
(#50188)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Moves Explorer’s back navigation and create actions into a reusable
sidebar breadcrumb header. Reduces product-menu headings globally to
`text-sm` and keeps breadcrumb links free of padding, borders, and
backgrounds.
### How to test
1. Open `/project//explorer` and confirm the header shows Explorer
and the SQL Editor switch action.
2. Open Notebooks and Chats. Confirm the header shows `Explorer >
Notebooks/Chats` and the corresponding create action works.
3. Return using the Explorer breadcrumb with a click or Tab + Enter.
Check that the label stays aligned and has no hover background.
4. Open another product, such as Database, and confirm its sidebar
heading uses the smaller font size.
5. At a mobile viewport, open the menu and repeat the notebook/chat
actions and back navigation without closing the sheet. Confirm the
header stays current and disappears when returning to the main menu or
opening another product.
Validation: 18 focused tests, typecheck, formatting, and lint ratchet
passed.
## Summary by CodeRabbit
- **New Features**
- Added a shared Explorer sidebar header with breadcrumbs and contextual
actions for creating notebooks and chats.
- Added keyboard-accessible navigation between the Explorer overview and
notebook or chat sections.
- Added support for customized product menu headers across project
layouts.
- **Improvements**
- Centralized Explorer navigation and actions in the shared sidebar
layout.
- Improved mobile menu updates when navigating between Explorer
resources.
- Refined Explorer home layout and drag-handle behavior across screen
sizes.
---------
Co-authored-by: Joshen Lim
---
.../interfaces/Explorer/ExplorerHomeTab.tsx | 122 ++++++++++--------
.../interfaces/Explorer/MarkdownCell.tsx | 2 +-
.../interfaces/Explorer/QueryCell/index.tsx | 2 +-
.../ExplorerLayout.constants.tsx | 33 +----
.../layouts/ExplorerLayout/ExplorerLayout.tsx | 17 ++-
.../ExplorerLayout/ExplorerNavChats.test.tsx | 2 +-
.../ExplorerLayout/ExplorerNavChats.tsx | 4 +-
.../ExplorerLayout/ExplorerNavHeader.test.tsx | 85 ++++++++++++
.../ExplorerLayout/ExplorerNavHeader.tsx | 48 +++++++
.../ExplorerLayout/ExplorerNavNotebooks.tsx | 9 +-
.../layouts/Navigation/ProductMenuBar.tsx | 20 ++-
.../layouts/Navigation/SidebarBreadcrumb.tsx | 60 +++++++++
.../MobileMenuContent.test.tsx | 115 +++++++++++++++++
.../MobileMenuContent/MobileMenuContent.tsx | 17 ++-
.../layouts/ProjectLayout/index.test.tsx | 87 ++++++++++++-
.../layouts/ProjectLayout/index.tsx | 50 +++++--
16 files changed, 546 insertions(+), 127 deletions(-)
create mode 100644 apps/studio/components/layouts/ExplorerLayout/ExplorerNavHeader.test.tsx
create mode 100644 apps/studio/components/layouts/ExplorerLayout/ExplorerNavHeader.tsx
create mode 100644 apps/studio/components/layouts/Navigation/SidebarBreadcrumb.tsx
create mode 100644 apps/studio/components/layouts/ProjectLayout/LayoutHeader/MobileMenuContent/MobileMenuContent.test.tsx
diff --git a/apps/studio/components/interfaces/Explorer/ExplorerHomeTab.tsx b/apps/studio/components/interfaces/Explorer/ExplorerHomeTab.tsx
index 80da4693f81..5deb147d84c 100644
--- a/apps/studio/components/interfaces/Explorer/ExplorerHomeTab.tsx
+++ b/apps/studio/components/interfaces/Explorer/ExplorerHomeTab.tsx
@@ -1,5 +1,6 @@
import { NotebookText, SquareCode } from 'lucide-react'
import { useState } from 'react'
+import { cn } from 'ui'
import { useCreateChat, useCreateNotebook, useCreateQuery } from './hooks'
import { NOTEBOOK_TEMPLATES } from './templates'
@@ -16,70 +17,77 @@ export const ExplorerHomeTab = () => {
const [value, setValue] = useState('')
return (
-
-
-
-
Run SQL. Chat with your project. Create a Notebook