Credentials
Which credential goes where. Management credentials, SQL connection strings, and why they are never interchangeable.
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:
- Personal access token (
sbp_...), generated in the Supabase dashboard’s account settings. This is the management credential. It authorizes the Management API atapi.supabase.com, including creating and deleting projects. Specific permissions matter: creating projects requires theorganization_projects_createscope on the token, and updating projects requiresproject_admin_write; a token without them gets the provider’s permission error, normalized asPERMISSION. 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. - The database password, which lives inside the
postgresql://connection string for a project. This is what the query adapter needs. - 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
passwordin 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’ssecrets(labelpassword). 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
- 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 asPERMISSION. - 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
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 for the full lifecycle.