---
title: Credentials
description: Which credential goes where. Management credentials, SQL connection strings, and why they are never interchangeable.
sidebar:
  order: 13
---

dbSDK works with two different kinds of credentials, and mixing them up is
the most common setup mistake. This page states exactly which secret goes
into which option, per provider.

## The two credential planes

| | Management plane | Query plane |
| --- | --- | --- |
| What it authorizes | Provider control-plane API calls: create, list, update, delete resources | PostgreSQL connections: queries, transactions, batches |
| Supabase secret | Personal access token (`sbp_...`) | The database password inside a `postgresql://` connection string |
| Neon secret | Neon API key | The role password inside a `postgresql://` connection string |
| dbSDK option | `supabaseManagement({ accessToken })`, `neonManagement({ apiKey })` | `supabase({ connectionString })`, `neon({ connectionString })`, `postgres({ connectionString })` |

A management credential does not authenticate a SQL connection, and a SQL
connection string cannot create or delete resources. If you pass a token to
the query adapter or a connection string to the management client, the
provider will reject you, and dbSDK reports that as an `AUTH` or
`PERMISSION` error rather than guessing.

## Supabase

Supabase issues three different kinds of secrets, and only two of them
matter for dbSDK:

1. **Personal access token** (`sbp_...`), generated in the Supabase
   dashboard's account settings. This is the management credential. It
   authorizes the Management API at `api.supabase.com`, including creating
   and deleting projects. Specific permissions matter: creating projects
   requires the `organization_projects_create` scope on the token, and
   updating projects requires `project_admin_write`; a token without them
   gets the provider's permission error, normalized as `PERMISSION`. The
   token is long-lived and powerful; treat it like root. It is not a
   database password, and the query adapter will not accept it.
2. **The database password**, which lives inside the `postgresql://`
   connection string for a project. This is what the query adapter needs.
3. **Anon and service_role API keys**: these belong to Supabase's Data API
   (PostgREST), not to the PostgreSQL wire protocol and not to the
   Management API. dbSDK uses neither; do not pass them to either client.

One timing fact to plan around: a freshly created Supabase project does
not come with a usable connection string in hand. The Management API never
echoes the database password. Two honest paths:

- Pass your own `password` in the create spec; you know it because you
  chose it.
- Omit `password`; the SDK generates a cryptographically random one and
  returns it exactly once in the result's `secrets` (label `password`).
  Store it then; it is not retrievable again through the SDK.

Either way, assemble the query connection string from the password and the
project's database host, which the management adapter can retrieve for you
(`raw.databaseHost(projectRef)`) or which you can read from the Supabase
dashboard. Full branch credentials are available the same way
(`raw.branchConfig(...)`, secrets omitted unless you opt in with
`includeSecrets: true`). The SDK never fabricates a URL from the token.

## Neon

1. **Neon API key**, created in the Neon console. This is the management
   credential for the Neon API at `console.neon.tech`. Keys can be scoped;
   a key scoped to an organization or project cannot manage resources
   outside that scope, and the provider answers such requests with its own
   permission error, normalized as `PERMISSION`.
2. **Role passwords**, which live inside Neon's `postgresql://` connection
   strings. This is what the query adapter needs.

Neon's API can hand you a ready connection URI: create responses carry
them (shown once), and the adapter's `raw.connectionUri()` retrieves one
on demand. The URI embeds the role password, so dbSDK redacts the password
by default and returns the real URI only when you pass `reveal: true`.
Treat the result as a secret either way.

## Handling rules

- Both credential kinds are server-side secrets. There is no browser build
  of dbSDK, and neither credential belongs in client-side code.
- The management credential is more powerful than any single database
  password: it can create and destroy resources on your account. Scope it
  as narrowly as the provider allows, rotate it per the provider's
  guidance, and never commit it.
- Secrets that dbSDK returns (Neon connection URIs, generated Supabase
  passwords) are shown once by the provider or by the SDK and are redacted
  from every raw payload and error message. They are never logged.
- Environment variables on the server are the expected storage. dbSDK
  reads credentials only from the options you pass, never from your
  filesystem or shell.

## Where each one goes, in one snippet

```ts
import { createDatabase } from "dbsdk";
import { createManagement } from "dbsdk/management";
import { neon } from "dbsdk/neon";
import { neonManagement } from "dbsdk/management/neon";

// Management plane: Neon API key.
const management = createManagement({
  adapter: neonManagement({ apiKey: process.env.NEON_API_KEY! }),
});

// Query plane: a postgresql:// connection string, not the API key.
const db = createDatabase({
  adapter: neon({ connectionString: process.env.DATABASE_URL! }),
});
```

The same split applies to Supabase: `accessToken` for
`supabaseManagement`, the `postgresql://` string for `supabase`. See
[Management](/docs/management) for the full lifecycle.
