Skip to main content

Connectivity & Data Access

Connecting to the Provisioned Project​

"Connecting" to a project has two distinct meanings, and this framework treats them separately: managing the project (the control plane) and running queries against it (the data plane). See the control plane and the data plane are separate.

Managing the Project (Control Plane)​

To perform management actions against one tenant's project — retrieve or reset the connection URI, create a branch, adjust compute — use a project-scoped API key. Project-scoped keys are the right tool when you want a credential that can touch exactly one tenant and nothing else, honoring least privilege. Retrieve the project's connection URI through the API rather than assembling it by hand, and store it in your secret store as the source of truth — refreshing it when it changes (a password rotation or endpoint change).

Documentation: Project-scoped API keys | Get connection URI

Running Queries (Data Plane)​

To read and write rows, the application connects with a Postgres connection string authenticating as a Neon role (a Postgres user). Provision a dedicated, least-privilege role per tenant project, store its connection string in your secret store, and inject it into the application at runtime. Never embed the credential in client-side code.

postgresql://<role>:<password>@<endpoint>.<region>.aws.neon.tech/<database>?sslmode=require

Documentation: Connect to Neon | Manage roles

Connection Pooling​

Use pooled connections for the application's normal request path — serverless functions, web backends, and any connection-per-request framework with high churn. Pooling matters because it is important to keep per-tenant computes as small as possible. max_connections scales with compute size (about 100 on a 0.25 CU compute). Without pooling, a connection-per-request application would exhaust a small tenant compute's connections — or force you to size the compute up just to hold them. The pooler multiplexes up to 10,000 client connections per project allowing each tenant's compute to stay as small as possible.

Enable pooling by adding the -pooler suffix to the endpoint host in your connection string:

# Direct
postgresql://role@ep-cool-darkness-123456.us-east-2.aws.neon.tech/db
# Pooled
postgresql://role@ep-cool-darkness-123456-pooler.us-east-2.aws.neon.tech/db

When using the pooler, note that a connection returns to the pool after each transaction, so session-scoped features do not persist across statements — SET/RESET (for example SET search_path), LISTEN/NOTIFY, WITH HOLD cursors, SQL-level PREPARE/DEALLOCATE, temporary tables, and session-level advisory locks. On the request path, code around them: fully-qualify object names or set defaults at the role level, and use protocol-level prepared statements. A workload that genuinely needs one of these features belongs on a direct connection (above).

Because Neon computes scale to zero, a suspended compute terminates its connections and the first request after idle incurs a cold start — so treat connections as disposable: verify liveness before use (a pool_pre_ping-style check) and retry transient connection errors with backoff. Watch for pool saturation via the pooler's client/server connection metrics in the Neon Console.

Use direct (unpooled) connections for schema migrations, pg_dump/pg_restore, logical replication, admin tasks, and any workload that needs session state to persist across transactions (the session features listed above). A single long-running query does not require a direct connection: transaction pooling holds the server connection for the whole transaction and won't drop it mid-query.

Documentation: Connection pooling | PgBouncer features

Reading and Writing Data​

The application backend should mediate all data access: Clients — browsers, mobile apps, agent front-ends — call your backend, which holds the per-tenant credential and runs the SQL. There are two ways for the application's backend to connect to Neon:

  • Standard Postgres driver / ORM — this should be the default. Neon is plain Postgres, so no special driver is needed: use your language's standard driver or ORM (for example psycopg or SQLAlchemy, node-postgres or Prisma, JDBC). Pooled drivers support IP Allow and private networking.
  • Neon serverless driver — connects over HTTP/WebSocket, for backends running in serverless or edge runtimes where opening a raw TCP connection per invocation is impractical. A good fit for the agent use case and for functions-as-a-service backends.

Whichever method you pick, the connection authenticates as a Postgres role (see Running Queries) — never with a Neon API key, and never with a credential that reaches the client.

Why not the Data API?

Neon's Data API is a PostgREST-based REST interface secured by JWT and RLS. A backend can call it, but a driver is strictly better: full SQL instead of CRUD-only REST, no JWT/token overhead, and support for IP Allow and Private Networking. The Data API is built for callers that can't hold a database connection or a Postgres credential — untrusted HTTP-only clients (browser, mobile) — which is a client-direct model outside this framework. Neon's JWT-based Authorize path is the same model and likewise outside it.

The table below summarizes the two methods.

Pooled driverServerless driver
Runs whereLong-running backendsServerless / edge backends
InterfacePostgres wire protocolPostgres over HTTP/WebSocket
SQL surfaceFull PostgresFull Postgres
AuthPostgres role + passwordPostgres role + password
IP Allow / Private NetworkingSupported—

Per-user isolation within a tenant's project. When a single tenant's project serves multiple end-users and you want per-user row isolation, drive RLS from your backend: authenticate the end-user in your backend, then pass the identity into the database per request with SET LOCAL app.current_user_id = '…' inside the transaction (a session-level SET does not survive transaction pooling), and have your policies key off current_setting('app.current_user_id'). The application continues to use a single per-tenant role — you don't mint a Postgres role per end-user — and RLS becomes database-enforced.

Practices that hold across all methods:

  • Least-privilege roles. Give the application role only the privileges it needs; keep migration/admin credentials separate from the runtime role.
  • Migrations over a direct connection. Run schema migrations (Prisma, Drizzle, Flyway, Alembic) on the unpooled endpoint, and design a fleet-wide strategy for applying migrations across every tenant project.
  • Use branching deliberately. Neon branches are copy-on-write and near-instant — use them for per-tenant staging, test environments, or agent sandboxes and checkpoints, and count them toward your resource metrics.
  • No hardcoded credentials. Connection strings come from the secret store at runtime, never from source or client code.

Documentation: Connect to Neon | Neon serverless driver | Neon Data API | Branching

What's Next​