The short version
A PostgreSQL connection string is a single URL that tells a client how to reach your database. It looks like this:
postgresql://postgres:[YOUR-PASSWORD]@db.abcdefghij.supabase.co:5432/postgres
Every provider builds on the same format, and the differences that trip people up are almost never the syntax. They're the port and host your provider hands you. Supabase alone has three different strings for the same database, and two of them won't work from serverless apps.
Anatomy of a connection string
postgresql:// user : password @ host : port / database
| | | | | |
protocol username secret server port db name
| Part | Example | Notes |
|---|---|---|
| Protocol | postgresql:// | postgres:// also works |
| User | postgres | In Supabase: postgres.[project-ref] on pooler URLs |
| Password | [your-password] | URL-encode special chars (@ → %40) |
| Host | db.xxxx.supabase.co | Provider-specific |
| Port | 5432 | 6543 = transaction pooler |
| Database | postgres | Default database name |
You can append query parameters: ?sslmode=require (force TLS) and ?channel_binding=require.
Supabase: three strings for one database
Supabase is where most people hit the wall, because the right string depends on where your code runs:
| Mode | Host | Port | Best for |
|---|---|---|---|
| Direct | db.[project-ref].supabase.co | 5432 | Migrations, pg_dump, long-lived backend |
| Shared pooler (session) | aws-[region].pooler.supabase.com | 5432 | Persistent backend on IPv4 |
| Shared pooler (transaction) | aws-[region].pooler.supabase.com | 6543 | Serverless / edge / short-lived connections |
# Direct
postgresql://postgres:[YOUR-PASSWORD]@db.[project-ref].supabase.co:5432/postgres
# Shared pooler session mode
postgres://postgres.[project-ref]:[YOUR-PASSWORD]@aws-[REGION].pooler.supabase.com:5432/postgres
# Shared pooler transaction mode (the one most apps want)
postgres://postgres.[project-ref]:[YOUR-PASSWORD]@aws-[REGION].pooler.supabase.com:6543/postgres
The gotcha: direct connections run on IPv6. On an IPv4-only network you get ENOTFOUND, not a friendly error. The shared pooler is IPv4-only and fixes it. For serverless (Vercel, edge, Lambda) use transaction mode (port 6543).
This is why tools like dbdiagramr ask for the transaction pooler URL — otherwise the connection silently fails from IPv4-only hosts.
Neon: pooled vs direct
# Pooled (through PgBouncer, use by default)
postgresql://user:pass@ep-cool-rain-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require
# Direct
postgresql://user:pass@ep-cool-rain-123456.us-east-2.aws.neon.tech/neondb?sslmode=require
Rule of thumb: use the -pooler host unless you have a specific reason not to. Grab both from the Connect button in the Neon dashboard.
Railway: the plain standard
postgresql://postgres:[YOUR-PASSWORD]@[host].railway.app:5432/railway
No pooler decision — textbook format, easiest to read and to misplace a password in. Store in DATABASE_URL, never in code.
All the formats in one table
| Provider | Connection string shape |
|---|---|
| Standard / Railway | postgresql://user:pass@host:5432/db |
| Supabase (direct) | postgresql://postgres:pass@db.[ref].supabase.co:5432/postgres |
| Supabase (session) | postgres://postgres.[ref]:pass@aws-[region].pooler.supabase.com:5432/postgres |
| Supabase (transaction) | postgres://postgres.[ref]:pass@aws-[region].pooler.supabase.com:6543/postgres |
| Neon (pooled) | postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/db?sslmode=require |
| Neon (direct) | postgresql://user:pass@ep-xxx.region.aws.neon.tech/db?sslmode=require |
Gotchas that will actually bite you
- IPv4 vs IPv6. Supabase direct is IPv6. IPv4-only hosts get
ENOTFOUND. The pooler strings are IPv4 and sidestep it. - Serverless wants the transaction pooler. Long-lived backends can hold a connection; serverless functions open one per invocation. Session pooling chokes; transaction pooling (port 6543) exists for it.
- A connection string is a secret. Password in plain text. Never commit it, never paste into a shared doc. Most good tools never store it beyond one request.
- URL-encode passwords. A password with
@,:, or/breaks the URL. Encode those.
Curious what that pooler URL contains after it connects? The Supabase auth schema diagram was created by introspecting a live Supabase database through a transaction pooler string.
FAQ
What is a PostgreSQL connection string?
A single URL with everything needed to reach a Postgres database: protocol, username, password, host, port, and database name.
Should I use a direct connection or a pooler?
Persistent backend → direct is fine. Serverless/edge → use a pooler (Supabase transaction mode port 6543 or Neon -pooler host).
Why does my Supabase connection fail with ENOTFOUND?
You're using the direct string over IPv4, and Supabase's direct endpoint is IPv6. Switch to a shared pooler string.
Is my connection string a secret?
Yes — password in plain text. Keep in environment variables, rotate if leaked, only hand to tools that won't store it.