What you will learn:

  • The one result contract every method shares

  • Which namespaces and methods exist

  • How pagination and async workflows work

@neon/sdk wraps the entire Neon API in one typed, fetch-based client. You authenticate once, then reach every resource through a namespace on neon.*: projects, branches, the Postgres data plane, object storage, functions, and Managed Better Auth. Retries, readiness polling, auto-pagination, and typed errors are built in.

It replaces @neondatabase/api-client, the deprecated Axios-based SDK. New projects should use @neon/sdk. See the migration guide for method mapping and error-handling changes.

Not every endpoint has an ergonomic wrapper

createNeonClient namespaces cover common workflows (projects, branches, Postgres resources, snapshots, and more). They do not wrap every Platform API operation. For endpoints without a namespace method, use the raw layer below or the Neon API Reference.

npm install @neon/sdk
import { createNeonClient } from "@neon/sdk";

const neon = createNeonClient({ apiKey: process.env.NEON_API_KEY! });

const { data, error } = await neon.projects.list().all();
if (error) throw error; // typed NeonError
data; // ProjectListItem[]

Every method follows this shape: select a namespace, call a method, and receive a { data, error } result. The reference below documents each namespace and method against that single contract.

In the reference tables, the Returns column names the resolved resource, the type of data on success (or the value returned directly when throwOnError is set). A method resolving to void has no resource body; Paginated<T> is the lazy, auto-paginated list described below. Every method also accepts an optional trailing options argument ({ throwOnError?, waitForReadiness?, signal? }), omitted from the tables for brevity.

Nearly every method needs a projectId, and branch-scoped methods also need a branchId. Get these from neon.projects.list() and neon.branches.list(projectId) (or neon.branches.getDefault(projectId) for the default branch), reading .id off each result.

Client configuration

createNeonClient(config) accepts:

OptionTypeDefaultPurpose
apiKeystring | () => string | Promise<string>requiredBearer credential. A function is called per request, for short-lived tokens
throwOnErrorbooleanfalseThrow a NeonError instead of returning { data, error }. Overridable per call
waitForReadinessbooleanfalsePoll provisioning operations to completion before resolving. Overridable per call
wait{ pollIntervalMs?, timeoutMs? }1000 / 300000Tuning for the readiness poller
retriesnumber2Automatic retries on safe statuses (423, 429, 503)
baseUrlstringhttps://console.neon.tech/api/v2Override the API base URL
fetchtypeof fetchglobal fetchCustom fetch, for proxies, tests, or non-global runtimes
orgIdstringnoneDefault organization id, applied to project create/list and as the transfer source org. Overridable per call
const neon = createNeonClient({
  apiKey: process.env.NEON_API_KEY!,
  orgId: "org-cool-forest-12345678",
  throwOnError: true,
});

Core model

Four behaviors are shared by every method: the result envelope, typed errors, pagination, and async workflows.

The result envelope

By default, no try/catch. Each call resolves to a discriminated { data, error } envelope; check error, then data is narrowed:

const { data, error } = await neon.projects.get("late-frost-12345");
if (error) return; // error: typed NeonError union
data; // narrowed to Project

To throw instead, set throwOnError on the client (or per call). The return type narrows to the bare resource:

const neon = createNeonClient({ apiKey, throwOnError: true });
const project = await neon.projects.get("my-project"); // Project (throws on error)
const { data } = await neon.projects.get("my-project", { throwOnError: false }); // opt out per call

Typed errors

The error channel, and what throwOnError throws, is one hierarchy of Error subclasses, discriminated on kind:

kindClassRaised when
apiNeonApiErrorNon-2xx response; carries status, code, requestId, body
not_foundNeonNotFoundError404 (extends NeonApiError)
authNeonAuthError401 or 403
rate_limitNeonRateLimitError429, after retries
operationNeonOperationErrorAn awaited operation failed; carries operationId, status
timeoutNeonTimeoutErrorA readiness or wait deadline was exceeded
networkNeonNetworkErrorTransport failure, no response received
clientNeonErrorSDK-side error, such as ambiguous connection-string selection
const { error } = await neon.branches.get(projectId, "nope");
if (error?.kind === "not_found") {
  // handle the 404
}

Lazy, auto-paginated lists

Methods labeled Paginated return a Paginated<T>; the cursor is managed for you:

const { data: all } = await neon.projects.list().all(); // every page
const { data: one } = await neon.projects.list().page(); // just the first page
for await (const project of neon.projects.list()) {
  // stream item by item
}

Async workflows

Neon mutations return operations that complete in the background. A few convenience methods, noted as "creates, then polls until ready" in the reference below (projects.createAndConnect, branches.createWithCompute), do this polling for you and hand back a ready-to-use result, such as a connection string, in a single call. The primitive underneath is neon.operations.waitFor(operations).

On any namespaced mutation, pass { waitForReadiness: true } as the trailing options argument to poll before the call resolves:

const { data, error } = await neon.branches.create(
  projectId,
  { name: "preview" },
  { waitForReadiness: true }
);
if (error) throw error;
data; // Branch — provisioning finished

For raw API calls that return an operations array, use neon.operations.waitFor instead.

Namespaces

The client groups the API into resource namespaces. Projects and branches are the core surfaces: projects create, manage, and share projects, and branches branch a project's data and schema. The Postgres data plane lives under postgres: compute endpoints, roles, databases, the Data API, and connection strings.

Branch-scoped platform services include storage (S3-compatible object storage), functions, credentials, aiGateway, and auth (Managed Better Auth, OAuth providers, and users). For data lifecycle and async work, use snapshots for point-in-time snapshots and restore, and operations to poll asynchronous operations.

Account-level surfaces round out the client: consumption for billing metrics, apiKeys, and regions / user.

neon.projects

Create, manage, and share Neon projects. One API call per method; list is paginated. REST: Projects API

MethodReturnsArguments
list(query?)Paginated<ProjectListItem>query: { search?, org_id?, limit? }
get(id)Project
create(input?)Projectinput: { name?, region_id?, pg_version?, org_id?, autoscaling_limit_min_cu?, autoscaling_limit_max_cu?, settings? }
createAndConnect(input?, opts?){ project: Project, connectionString: string }Creates, then polls until ready. opts: { pooled? } (default true)
update(id, input)Projectinput: { name?, settings? }
delete(id)Project
recover(id)ProjectRecover a soft-deleted project within its retention window
transfer(input)voidinput: { fromOrgId?, toOrgId, projectIds } (fromOrgId defaults to the client orgId)
transferFromUser(input)voidinput: { toOrgId, projectIds }
// Provision a project, poll until ready, return a pooled connection string
const { data } = await neon.projects.createAndConnect(
  { name: "tenant-42", region_id: "aws-us-east-1" },
  { pooled: true }
);
// data: { project, connectionString }

neon.projects.permissions

Share a project with additional users by email.

MethodReturns
list(projectId)ProjectPermission[]
grant(projectId, email)ProjectPermission
revoke(projectId, permissionId)ProjectPermission

neon.branches

Branch a project's data and schema; optionally attach compute in one workflow. REST: Branches API

MethodReturnsArguments
list(projectId, query?)Paginated<Branch>query: { search?, sort_by?, sort_order?, include_deleted? }
get(projectId, branchId)Branch
create(projectId, input?)Branchinput: { name?, parent_id?, parent_lsn?, parent_timestamp?, protected? }
createWithCompute(projectId, input, opts?){ branch: Branch, endpoint: Endpoint, connectionString: string }Creates, then polls until ready. input: { name?, parentId?, compute?: { minCu?, maxCu?, suspendTimeoutSeconds? } }
update(projectId, branchId, input)Branchinput: { name?, protected?, expires_at? }
delete(projectId, branchId)void
getDefault(projectId)BranchResolve the project's default branch by flag, not by name
setDefault(projectId, branchId)Branch
recover(projectId, branchId)BranchRecover a soft-deleted branch within the 7-day window
finalizeRestore(projectId, branchId, input?)voidCommit a restore previewed with snapshots.restore({ finalize: false })
// Branch off the default ("production") branch with its own compute
const { data: prod } = await neon.branches.getDefault(projectId);
const { data } = await neon.branches.createWithCompute(projectId, {
  name: "preview/pr-123",
  parentId: prod?.id,
  compute: { minCu: 0.25, maxCu: 2 },
});
// data: { branch, endpoint, connectionString }

neon.postgres

The Postgres data plane of a branch: compute endpoints, roles, databases, the Data API, and a connection-string helper. REST: Endpoints, Branches, Data API

MethodReturnsArguments
connectionString(params)stringparams: { projectId, branchId?, endpointId?, databaseName?, roleName?, pooled? }. Only projectId is required; branch defaults to the project default, endpoint to the read-write one, and role/database are auto-selected when the branch has exactly one. pooled defaults to true
const { data: uri } = await neon.postgres.connectionString({ projectId });

neon.postgres.endpoints

Compute endpoints, scoped to a project.

MethodReturnsArguments
list(projectId)Endpoint[]
listByBranch(projectId, branchId)Endpoint[]
get(projectId, endpointId)Endpoint
create(projectId, input)Endpointinput: { branch_id, type, autoscaling_limit_min_cu?, autoscaling_limit_max_cu?, suspend_timeout_seconds?, provisioner? }. type is "read_write" | "read_only"
update(projectId, endpointId, input)Endpoint
delete(projectId, endpointId)void
start(projectId, endpointId)Endpoint
suspend(projectId, endpointId)Endpoint
restart(projectId, endpointId)Endpoint

neon.postgres.roles

Postgres roles, scoped to a branch.

MethodReturnsArguments
list(projectId, branchId)Role[]
get(projectId, branchId, name)Role
create(projectId, branchId, input)Roleinput: { name, no_login? }
delete(projectId, branchId, name)void
password(projectId, branchId, name)stringReveals the current password
resetPassword(projectId, branchId, name)RoleThe returned Role carries the new password
// Reveal a role's password, or rotate it
const { data: password } = await neon.postgres.roles.password(projectId, branchId, "neondb_owner");
const { data: role } = await neon.postgres.roles.resetPassword(projectId, branchId, "neondb_owner");
// role.password holds the new secret

neon.postgres.databases

Databases, scoped to a branch.

MethodReturnsArguments
list(projectId, branchId)Database[]
get(projectId, branchId, name)Database
create(projectId, branchId, input)Databaseinput: { name, owner_name }
update(projectId, branchId, name, input)Databaseinput: { name?, owner_name? }
delete(projectId, branchId, name)void

neon.postgres.dataApi

The Neon Data API, scoped to a branch and database.

MethodReturns
get(projectId, branchId, databaseName)DataApiResponse
create(projectId, branchId, databaseName, input?)DataApiCreateResponse
update(projectId, branchId, databaseName, input?)void
delete(projectId, branchId, databaseName)void

neon.storage

Branch-scoped, S3-compatible object storage. get returns whether storage is enabled and the branch's S3 endpoint metadata; buckets and objects are nested underneath. REST: Storage, Buckets

MethodReturns
get(projectId, branchId)BranchStorage

neon.storage.buckets

MethodReturnsArguments
list(projectId, branchId)Bucket[]
create(projectId, branchId, input)Bucketinput: { name, access_level? }, where access_level is "private" | "public_read"
delete(projectId, branchId, bucketName)void

neon.storage.objects

MethodReturnsArguments
list(projectId, branchId, bucketName, query?)BucketObjectsListResponsequery: { prefix?, delimiter?, cursor?, limit? }. Returns one page of folders, objects, next_cursor
get(projectId, branchId, bucketName, objectKey)BlobRaw object bytes
delete(projectId, branchId, bucketName, objectKey)void
deleteByPrefix(projectId, branchId, bucketName, prefix){ deleted: number }prefix must end with /
presign(projectId, branchId, bucketName, objectKey, input)PresignResponseinput: { operation: "upload" | "download", content_type?, expires_in_seconds? }
// Upload via a presigned PUT
const { data: presign } = await neon.storage.objects.presign(
  projectId, branchId, "avatars", "user-1.png",
  { operation: "upload", content_type: "image/png" }
);
if (!presign) throw new Error("presign failed");

await fetch(presign.url, {
  method: "PUT",
  headers: { ...presign.headers, "Content-Length": String(bytes.length) },
  body: bytes,
});

neon.functions

Branch-scoped Neon Functions. REST: Functions API

MethodReturnsArguments
list(projectId, branchId, query?)Paginated<NeonFunction>query: { limit? }
get(projectId, branchId, slug)NeonFunction
update(projectId, branchId, slug, input)NeonFunctioninput: { name? }
delete(projectId, branchId, slug)void
deploy(projectId, branchId, slug, input?)NeonFunctionDeploymentMultipart. input: { zip?: Blob | File, runtime?: "nodejs24", environment?: string }, where environment is a JSON-encoded Record<string, string>
// Deploy a bundled index.mjs inside a zip (first deploy must include the zip)
const zip = await Bun.file("bundle.zip").arrayBuffer();
const { data: deployment } = await neon.functions.deploy(projectId, branchId, "api", {
  zip: new File([zip], "bundle.zip", { type: "application/zip" }),
  runtime: "nodejs24",
});
// Poll neon.functions.get until current_deployment.status is "completed"

neon.credentials

Branch-scoped credentials with explicit scopes. Secrets (api_token, s3_secret_access_key) are returned once, on create. REST: Credentials API

MethodReturnsArguments
list(projectId, branchId)CredentialMeta[]
create(projectId, branchId, input)CreateCredentialResponseinput: { name?, scopes, principal_type: "user" }. Scopes: storage:read, storage:write, ai_gateway:invoke, functions:invoke
revoke(projectId, branchId, tokenId)void

neon.aiGateway

Branch-scoped AI Gateway endpoint metadata. REST: AI Gateway API

MethodReturnsArguments
get(projectId, branchId)BranchAiGatewayReturns 404 when AI Gateway is not enabled on the branch

neon.snapshots

Point-in-time snapshots, restore, and backup schedules. REST: Snapshots API

MethodReturnsArguments
list(projectId)Snapshot[]
create(projectId, branchId, input?)Snapshotinput: { name?, timestamp?, lsn?, expiresAt? }
update(projectId, snapshotId, input)Snapshotinput: { name? }
delete(projectId, snapshotId)void
restore(projectId, snapshotId, input?)Branchinput: { name?, targetBranchId?, finalize?, preview?, keepOnAbort? }. See below
getSchedule(projectId, branchId)BackupSchedule
setSchedule(projectId, branchId, schedule)void

restore behaves differently depending on the target:

  • As a new branch (no targetBranchId), it finalizes by default and is ready to use immediately.
  • Onto an existing branch, it does not finalize by default, so you can preview first.
  • Transaction-style with preview: it restores un-finalized, runs your callback against the restored branch, then commits if the callback returns true or aborts (deletes the preview branch) if false, unless keepOnAbort is set:
await neon.snapshots.restore(projectId, snapshotId, {
  targetBranchId,
  preview: async (branch) => (await checks(branch)) === "ok", // true commits, false aborts
});

neon.operations

Read operations and wait for them to finish. REST: Operations API

MethodReturnsArguments
list(projectId)Paginated<Operation>
get(projectId, operationId)Operation
waitFor(operations, options?)voidoptions: { pollIntervalMs?, timeoutMs?, signal? }
// Wait on operations from a raw call (or when readiness polling is off)
const { data } = await raw.createProjectBranch({
  client: neon.client,
  path: { project_id: projectId },
  body: { branch: { name: "wip" } },
});
const { error } = await neon.operations.waitFor(data!.operations, { timeoutMs: 120_000 });

neon.auth

Branch-scoped Managed Better Auth. The legacy project-scoped endpoints are deprecated and remain raw-only. REST: Authentication API

MethodReturnsArguments
get(projectId, branchId)NeonAuthIntegration
create(projectId, branchId, input)NeonAuthCreateIntegrationResponseEnable the integration
disable(projectId, branchId, input?)voidinput: { deleteData? }
updateConfig(projectId, branchId, input)NeonAuthConfigResponse

neon.auth.oauthProviders

OAuth providers (Google, GitHub, and others).

MethodReturns
list(projectId, branchId)NeonAuthOauthProvider[]
add(projectId, branchId, input)NeonAuthOauthProvider
update(projectId, branchId, providerId, input)NeonAuthOauthProvider
delete(projectId, branchId, providerId)void

neon.auth.trustedDomains

The redirect-URI whitelist.

MethodReturns
list(projectId, branchId)NeonAuthRedirectUriWhitelistDomain[]
add(projectId, branchId, input)void
delete(projectId, branchId, input)void

neon.auth.users

MethodReturns
create(projectId, branchId, input)NeonAuthCreateNewUserResponse
delete(projectId, branchId, authUserId)void
updateRole(projectId, branchId, authUserId, roles)UpdateNeonAuthUserRoleResponse

neon.consumption

Cursor-paginated billing metrics. Each method takes { from, to, granularity, org_id, project_ids? }, where from/to are ISO timestamps, granularity is "hourly" | "daily" | "monthly", and org_id names the org to report on; perBranchV2 also requires project_ids. Consumption requires a Scale plan or above. REST: Consumption API

MethodReturns
perProject(query)Paginated<ConsumptionHistoryPerProject>
perProjectV2(query)Paginated<ConsumptionHistoryPerProjectV2>
perBranchV2(query)Paginated<ConsumptionHistoryPerBranchV2>
// Stream every project's daily usage across a range
for await (const project of neon.consumption.perProject({
  from: "2026-06-01T00:00:00Z",
  to: "2026-06-30T00:00:00Z",
  granularity: "daily",
  org_id: "org-...", // the org to report on; consumption requires a Scale plan or above
})) {
  console.log(project);
}

neon.apiKeys

Manage account-level API keys. REST: API Keys API

MethodReturnsArguments
list()ApiKeysListResponseItem[]
create(keyName)ApiKeyCreateResponseThe key token is shown once
revoke(keyId)ApiKeyRevokeResponse

neon.regions / neon.user

Active regions and the current account. REST: Regions, Users

MethodReturns
regions.list()RegionResponse[]
user.me()CurrentUserInfoResponse
user.organizations()Organization[]

Raw layer

Anything not wrapped above is available as a raw, 1:1 function. Pass neon.client to reuse the client's auth and base URL:

import { raw } from "@neon/sdk";
// or, for guaranteed tree-shaking: import { getProjectBranchSchema } from "@neon/sdk/raw";

const { data, error } = await raw.getProjectBranchSchema({
  client: neon.client,
  path: { project_id, branch_id },
  query: { db_name: "neondb" }, // db_name is required
});

The raw layer speaks the same result contract as the ergonomic client: { data, error } by default, or the bare resource (throwing the typed NeonError) with throwOnError: true. There is no responseStyle switch. Every request, response, and error type is re-exported flat from @neon/sdk for import type { Project, Branch } and the rest.

How this SDK is built

The raw layer and all request, response, and error types are generated from the Neon OpenAPI spec using @hey-api/openapi-ts. The ergonomic namespaces documented above are hand-written on top of that generated layer. When the API adds an endpoint, it appears in the raw layer automatically; the namespace wrappers are added deliberately. The source lives in neondatabase/neon-pkgs.