neon
v5.0.1
Published
CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres
Readme
Neon CLI
The neon package is a command-line interface that lets you manage Neon — Lakebase Postgres, Object Storage, Functions, Managed Better Auth, and the AI Gateway — directly from the terminal. For the complete documentation, see Neon CLI.
The legacy neonctl package is a lightweight compatibility package that depends
on this package and invokes the same CLI entry point. The implementation and
build artifacts live only here.
Install the Neon CLI
npm
npm i -g neonRequires Node.js 20.19 or higher.
Howebrew
brew install neonctlBinary (macOS, Linux, Windows)
Download a neon-<platform> binary from the releases page, which carries one asset per platform for each neon@<version> tag.
Upgrade
npm
npm update -g neonRequires Node.js 20.19 or higher.
Howebrew
brew upgrade neonctlBinary (macOS, Linux, Windows)
To upgrade a binary version, download the latest binary file, as described above, and replace your old binary with the new one.
Connect
Run the following command to authenticate a connection to Neon:
neon authThe auth command launches a browser window where you can authorize the Neon CLI to access your Neon account. Running a Neon CLI command without authenticating with neon auth automatically launches the browser authentication process.
Alternatively, you can authenticate a connection with a Neon API key using the --api-key option when running a Neon CLI command. For example, an API key is used with the following neon projects list command:
neon projects list --api-key <neon_api_key>For information about obtaining an Neon API key, see Authentication, in the Neon API Reference.
Create a project without an account
neon claim create provisions a temporary Claimable Neon project for an agent without
requiring a Neon account or opening a browser:
# Lakebase Postgres is always included
neon claim create
# Request Managed Better Auth and the Data API too
neon claim create --service auth --service data-apiWhen the current directory has a neon.ts, claim create also requests every service
declared there. Explicit --service values are added to that set. If that policy enables
the Data API, identity includes a snake_case data_api object. A neon.ts with
dataApi: { authProvider: "external", jwksUrl: "https://idp.example.com/jwks.json" }
sends:
{
"type": "anonymous",
"capabilities": ["postgres", "data_api"],
"source": "neon_cli",
"data_api": {
"auth_provider": "external",
"jwks_url": "https://idp.example.com/jwks.json"
}
}neon claim create --service data-api with no neon.ts Data API block omits data_api:
{
"type": "anonymous",
"capabilities": ["postgres", "data_api"],
"source": "neon_cli"
}Object Storage, Functions, and the AI Gateway passed with --service are sent so
demand is recorded, then reported as unavailable until the project is claimed. The CLI
does not drop them from the request.
The bundled env pull still follows neon.ts. A neon.ts that names AI Gateway,
Functions, or Object Storage fails the pull, and the create is rolled back. Use
--config for a different policy file (registration and that pull), or --no-env-pull
to skip the dotenv write.
The command writes:
- a
.neoncontext with the project id and pinned branch; - an owner-only identity assertion under the CLI config directory;
DATABASE_URL,DATABASE_URL_UNPOOLED,NEON_BRANCH, and Auth / Data API variables whenneon.tsdeclares them (or, with noneon.ts, when they are provisioned) to.envor.env.local(disable this with--no-env-pull). The write is the sameenv pullused after create.
Subsequent project commands find that assertion by the linked project id, exchange it for a short-lived agent token, and send API calls to Claimable Neon. The service decides which operations are allowed before claim.
neon claim status # lifecycle and transfer status
neon projects get <project-id> # regular CLI command, same agent token
neon psql --role-name neondb_owner -- -c "select now()"
neon config plan
neon checkout main
neon env pull
neon claim accept # create a claim code and open the transfer URL
neon claim delete --yes # permanently delete an unclaimed project
neon claim list # local records, including expired
neon claim delete <project-id> --yesstatus, accept, and delete take an optional project id from claim list, so a
project stays manageable after its original directory is gone. list prints state
(unclaimed or expired) from the identity assertion clock and the project
expiry, plus project_expires_at. delete also drops a
local record whose identity assertion has expired or been revoked.
neon claimable is an alias for neon claim. For local service development, set
CLAIMABLE_NEON_HOST=http://localhost:8787; non-local origins must use HTTPS.
Project and branch creation
Choose the PostgreSQL version when creating a project:
neon projects create --name my-project --pg-version 18Supported versions are 14 through 19; version 19 is available only in
regions where it has been enabled.
Create a protected branch when it should not be modified or deleted by routine automation:
neon branches create --project-id <project-id> --name production --protectedProject and branch creation include connection credentials in their output by
default for backward compatibility. When the output may be logged or passed to
an agent, use --no-secrets to return only the created resource metadata:
neon projects create --name my-project --output json --no-secrets
neon branches create --project-id <project-id> --name preview --output json --no-secretsThe flag omits the complete connection_uris block rather than redacting one
field. Retrieve a connection string separately when it is actually needed.
Enable logical replication
Enable logical replication for every endpoint in an existing project:
neon projects update <project-id> --enable-logical-replicationThe CLI asks for confirmation because enabling logical replication suspends
active endpoints and cannot be undone. For non-interactive automation, pass
--yes explicitly:
neon projects update <project-id> --enable-logical-replication --yesConnect with psql
The psql command
neon psql [branch] opens a psql session against a branch. It builds the connection string for the branch and launches psql — a shortcut for neon connection-string --psql. See Neon CLI commands — psql for the full reference.
neon psql # default branch
neon psql main # a specific branch
neon psql main@2024-01-01T00:00:00Z # point-in-time (branch@timestamp or branch@lsn)
neon psql --pooled # use the pooled connectionArguments after -- are forwarded to psql:
neon psql main -- -c "SELECT version()"
neon psql main -- -f script.sql --csvOptions: --project-id, --role-name, --database-name, --pooled, --endpoint-type (read_only | read_write), --ssl, plus the global options.
The --psql flag
Several other commands accept a --psql flag that opens a psql session against the resolved endpoint:
neon connection-string --psql --project-id <id>
neon projects create --psql
neon branches create --psqlAny arguments after -- are forwarded to psql, for example:
neon cs --psql --project-id <id> -- -c "SELECT version()"
neon cs --psql --project-id <id> -- -f script.sql --csvEmbedded psql fallback
If the system has psql installed on $PATH, --psql continues to spawn the native binary — there is no behavior change for existing users.
If psql is not found on $PATH, neon now falls back to an embedded TypeScript implementation. There is nothing to install or configure; it ships with neon. This removes the "no psql binary" trap on machines (and CI runners) that don't have PostgreSQL client tools installed.
Automatic fallback is the intended path — there is normally no flag to set. The embedded implementation can also be force-selected (primarily for tests and CI, e.g. to exercise it even when a native psql is present):
--fallback— force the embedded implementation onconnection-string,projects create, andbranches create. Intentionally hidden from--help: it's a test/CI knob, not a user-facing option (the automatic fallback above is the supported behavior).NEONCTL_PSQL_FALLBACK=1— environment variable with the same effect as--fallback. Convenient for scripts and CI.
The embedded implementation is verified against a conformance suite that diffs its behavior against real PostgreSQL (14–18) and the upstream psql regression + TAP tests.
What works
REPL & scripting
- Interactive REPL with a hand-rolled VT100 line editor (no native bindings); vi and emacs edit modes (
VI_MODEpsql variable) - Persistent command history (
~/.psql_history, libreadline format) ~/.psqlrcautoload (including$PGSYSCONFDIR/psqlrcand version-suffixed variants)- Scripted modes:
-c "SQL",-f script.sql, and stdin;--single-transaction,ON_ERROR_STOP,ECHO,--echo-all SINGLELINE(-S),\timing,\watch(named flagsc=/i=/m=, unbounded continuous mode)
Backslash commands
- All output formats: aligned, unaligned, wrapped, csv, json, html, asciidoc, latex, latex-longtable, troff-ms (
\a \H \t \x \pset \f \C…) - All
\d*describe commands with full upstream parity (columns, indexes, foreign keys, triggers, view definitions, sequences, RLS, replica identity, partitions, tablespaces, access methods, inheritance, FDW, stats objects, publications, subscriptions, per-column FDW options, TOAST owner) \copyto/from file,PROGRAM,STDIN,STDOUT(incl. the\.EOF marker);\g/\gx/\gset/\gdesc/\gexecand\g | programpipes- Extended query + pipeline mode (
\bind,\bind_named,\startpipeline,\parse,\sendpipeline) \crosstabview,\lo_*large objects,\e/\edit(external editor),\s(history),\?/\hhelp,\if/\elif/\else/\endif,\set/\unset,\connect,\encoding(liveSET client_encoding),\!,\cd,\prompt(incl. no-echo-),\password- Tab completion (~88 rules incl. live
pg_settingsGUC lookup, deepALTERsub-actions,JOINclauses, windowOVER)
Connection & authentication
- libpq-equivalent lookup precedence: argv flags > URI >
PG*env vars >~/.pgpass>pg_service.conf> libpq defaults - SCRAM-SHA-256 / SCRAM-SHA-256-PLUS with
tls-server-end-pointchannel binding (channel_binding); MD5 and cleartext;require_auth - Multi-host failover & load balancing:
target_session_attrs(any / read-write / read-only / primary / standby / prefer-standby),load_balance_hosts, DNS fan-out,hostaddr - Unix-domain sockets (host beginning with
/); TCP keepalives (keepalives,keepalives_idle)
TLS
sslmodedisable → verify-full; client certs in PEM or DER viasslcert/sslkey(+sslpasswordfor encrypted keys, with the libpq group/world-readable-key check)- Trust config:
sslrootcert(incl.=systemwithSSL_CERT_FILE/SSL_CERT_DIR), default client-cert discovery (~/.postgresql/postgresql.{crt,key}),sslcertmode - CRL:
sslcrlandsslcrldir;ssl_min_protocol_version/ssl_max_protocol_version;sslsni - Direct-SSL negotiation (
sslnegotiation=direct, PostgreSQL 17+, via ALPN)
What's not supported
- GSSAPI / SSPI (
gssencmode, Kerberos/SSPI auth,requirepeer). GSS transport encryption needs a native Kerberos binding, which the embedded psql deliberately avoids (pure TypeScript, zero native dependencies — the same reason the line editor is hand-rolled).node-postgresdoesn't support it either, and Neon doesn't use it.gssencmode=disable/preferare accepted;gssencmode=requireis rejected with a clear error.requirepeeris parsed but a Unix-socket connection that sets it is refused (Node exposes no peer-credential API — it is not silently ignored). keepalives_interval/keepalives_count— Node's socket API exposes only keepalive enable + initial delay, so these are accepted but not applied.
Known limitations
- TLS cipher is runtime-dependent. The negotiated TLS 1.3 ciphersuite is chosen by the host runtime's TLS library from an offer byte-identical to libpq's. Under Node (OpenSSL) that is
TLS_AES_256_GCM_SHA384, matching vanilla psql; under Bun (BoringSSL) it isTLS_AES_128_GCM_SHA256. Both are TLS 1.3 AEAD suites with no practical security difference, and neither runtime exposes a client-side knob to steer the selection.
Configure autocompletion
The Neon CLI supports autocompletion, which you can configure in a few easy steps. See Neon CLI commands — completion for instructions.
Linking a project
neon link is a Vercel-style command that binds the current directory to a Neon project. It picks (or creates) an organization and a project and writes a .neon file ({ "orgId", "projectId", "branch" }) that subsequent commands run in this directory (or any sub-directory) pick up automatically. Personal-account projects omit orgId.
link resolves what it can and verifies every identifier you pass before writing, so a successful .neon always has a project and a branch (and an organization when the project has one):
- org is inferred from the project (so
--project-idalone is enough); it's omitted only when the project has no organization (personal account).--org-idthat the project does not confirm is an error. - project is taken from
--project-id(or chosen interactively).--org-idwithout a project opens the project picker in a TTY. Without a TTY it needs-y(select the only project, or print IDs) or--project-id. - branch is taken from
--branch, a still-valid pin for the same project, or the project's branch list: one branch is pinned automatically; several prompt in a TTY, or-ypins the default. Zero branches, a stale pin, or several branches with no TTY and no-yfail without writing.
When a branch is pinned, link also runs env pull so the branch's Neon env vars (DATABASE_URL, …) land in a local .env. Pass --no-env-pull to skip the pull (for example when injecting env at runtime with neon-env run or neon dev).
Migrating from
set-context?set-contextis deprecated in favor oflink(see below). It still works exactly as before for now (a raw write), it just prints a deprecation warning. The.neonbranchIdfield is also superseded bybranch(which stores the branch name when known); oldbranchIdfiles are still read and are upgraded tobranchthe next timelink/checkoutwrites the context.
There are two modes:
Interactive (default) — guided prompts for humans:
$ neon link
? Which organization would you like to link? › Personal Org (org-abc123)
? Which project would you like to link? › + Create new project…
? Name for the new project: › my-app
? Which region should the new project run in? › AWS US East (Ohio) (aws-us-east-2)
Created project polished-snowflake-12345678 ("my-app") in aws-us-east-2.
Linked .neon:
orgId: org-abc123
projectId: polished-snowflake-12345678
branch: mainWhen you link an existing project that has more than one branch, the interactive flow adds a
final step to pick which branch to pin — the same + Create a new branch… + list selector used by
neon checkout (a single-branch project is pinned automatically, no prompt):
$ neon link
? Which organization would you like to link? › Personal Org (org-abc123)
? Which project would you like to link? › my-app (polished-snowflake-12345678)
? Which branch would you like to link? › [default] main (br-main-branch-87654321)link --project-id … skips org and project. One branch is pinned with no prompt. Several branches
in a TTY show the branch prompt; -y pins the default; no TTY without -y or --branch exits 1:
$ neon link --project-id polished-snowflake-12345678
? Which branch would you like to link? › [default] main (br-main-branch-87654321)Non-interactive (-y, flags, or --params JSON) — for scripts, CI, and agents:
# One organization and one project: link them. Several: print IDs and exit 1.
neon link -y
# After choosing an organization from that list, discover its project the same way
neon link -y --org-id org-abc123
# Link to an existing project (org is inferred). Pins the only branch;
# several branches prompt in a TTY, or -y pins the default.
neon link --project-id polished-snowflake-12345678
# Same, pin the project's default branch when several exist
neon link --project-id polished-snowflake-12345678 -y
# Same, but also pin a branch (name or id — resolved and stored as its name)
neon link --project-id polished-snowflake-12345678 --branch main
# Pin/switch the branch in the already-linked project
neon link --branch main # alias: --branch-id
# Create a new project and link it (pins the new project's default branch)
neon link --org-id org-abc123 --project-name my-app --region-id aws-us-east-2
# Same payload, one JSON blob
neon link --params '{"orgId":"org-abc123","projectName":"my-app","regionId":"aws-us-east-2"}'
# Forget the current context
neon link --clear
# Offline write — no API calls, no verification (see --no-checks below)
neon link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch mainEvery supplied identifier is checked before anything is written, with actionable errors — e.g. Project '…' not found, You don't have access to project '…', Organization '…' not found, or your API key doesn't have access to it, Project '…' belongs to organization 'A', not 'B', or Branch '…' not found in project '…'. Available branches: ….
Agents and scripts: neon link -y selects the only organization and project. If several exist, it prints their IDs (human table, or --output json / --output yaml) and names the flag to pass. --org-id alone, without -y and without a TTY, is not a completed link — pass --project-id or -y. neon link --help prints the explicit-flag recipe.
neon link -y
neon link -y --org-id <org-id>
neon link -y --project-id <project-id>
neon orgs list --output json
neon projects list --org-id <org-id> --output json
neon link --project-id <project-id> [--branch <name> | -y]
neon link --org-id <org-id> --project-name <name> --region-id aws-us-east-2Organization-scoped API keys cannot list user organizations (orgs list) or call the regions endpoint:
- Pass
--org-id(Neon Console → Settings) or--project-id(org is inferred from the project). - If the key is org-scoped and at least one project already exists, interactive
linkauto-detects the org from the first project and prints an informational message. - If no projects exist yet, interactive
linkerrors pointing at--org-id. - When the regions endpoint is not allowed, interactive create falls back to a built-in static region list. Non-interactive create already requires
--region-id.
Offline writes (--no-checks) — write the .neon with no API calls at all: no org inference, no existence/access verification, no env pull. Because nothing can be resolved offline, it requires --org-id, --project-id, and --branch (stored verbatim). Handy for scripted/CI setups or re-creating a .neon from values you already trust:
neon link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch mainset-context is deprecated
set-context is deprecated in favor of link and prints a deprecation warning (to stderr, so it never pollutes stdout or scripts). For backward compatibility its behavior is unchanged: it's still a raw, offline write of exactly the fields you pass (no org inference, no verification, no env pull), and bare set-context still clears the file. Nothing breaks today — but new work should use link, and set-context will be removed in a future major release.
How today's set-context uses map onto link:
| set-context (deprecated) | Recommended link equivalent |
| --------------------------------------- | ----------------------------------------------------------------------------- |
| neon set-context --project-id <id> | neon link --project-id <id> (infers org + verifies; pins the only branch, or -y for the default) |
| neon set-context --org-id <id> | Not a completed link. Pass --project-id / -y, or an --org-id flag on the org-scoped command |
| neon set-context --branch-id <id> | neon link --branch <name\|id> or neon checkout <branch> |
| neon set-context (clear) | neon link --clear |
| a raw local write (no network) | neon link --no-checks --org-id <id> --project-id <id> --branch <name> |
The key difference: link resolves and verifies before writing a complete context, whereas set-context writes whatever you give it verbatim. The closest like-for-like replacement for the old raw write is link --no-checks with org, project, and branch.
open
open launches the linked project's page in the Neon Console. It reads the closest .neon file without authenticating or calling the Neon API, so it works from any sub-directory of a linked project.
neon open
# Open a project without changing the linked context
neon open --project-id polished-snowflake-12345678A branch pinned in .neon does not change the destination. .neon stores the branch as a name, while the Console route requires its ID; resolving it would turn this local command into an authenticated API call.
checkout
checkout [id|name] switches the pinned branch in a linked folder. It resolves the branch (by name or id) against the project, then heals the .neon file: it always (re)writes projectId, branch, and orgId (when the project has one), so a .neon that was missing fields or drifted ends up complete and consistent. The branch is stored as its name when known (matching link). When orgId isn't already known (from --org-id or the existing .neon), it's looked up from the project itself.
The branch argument is optional: run neon checkout with no branch in an interactive terminal to fetch the project's branches and pick one from a list. In a non-interactive context (CI or no TTY), a branch must be passed explicitly.
Branch id vs name is detected automatically (a br-… value is treated as an id):
- id — matched strictly by id. A non-existent id is a hard "not found" error (ids are server-assigned, so checkout never creates one, including with
--create). - name — matched by name. If the name doesn't exist, pass
--createto create it (equivalent toneon branch create --name <name>: branched from the project's default branch with a read-write compute, or fromneon.tswhen that file is present), then check it out.--createis a no-op when the name already exists. Without--create, an interactive terminal offers to create the branch; in CI or with no TTY the error names--create.
The project is resolved through the standard neon chain, each entry winning over the next:
--project-id <id>flagprojectIdfrom the closest.neonfile (found by walking up from the current directory — see "Where.neonlives" below)- If still unresolved and the API key maps to exactly one project, that project is auto-detected (same behaviour as
branchesandconnection-string)
If none of those resolve a project, checkout prints a telling error explaining the chain above. In an interactive terminal it then offers to run neon link in the current folder so you can pick (or create) a project on the spot; once linked, it continues and pins the requested branch. In non-interactive contexts (CI or no TTY) it exits with a non-zero code and the same guidance instead of prompting.
The resolved branch is then written (by name) to the same .neon file link uses:
$ neon checkout main --project-id polished-snowflake-12345678
INFO: Checked out branch br-main-branch-87654321 on project polished-snowflake-12345678. Updated /path/to/cwd/.neon.
$ neon checkout dev --create --project-id polished-snowflake-12345678
INFO: Created branch dev (br-dev-branch-12345678).
INFO: Checked out branch br-dev-branch-12345678 on project polished-snowflake-12345678. Updated /path/to/cwd/.neon.
$ cat .neon
{
"orgId": "org-abc123",
"projectId": "polished-snowflake-12345678",
"branch": "dev"
}After pinning the branch, checkout also runs env pull by default, so the branch's Neon env vars are written to your local .env and you can start building right away — the branch-first loop is just link + checkout. Pass --no-env-pull to skip it (for example when env is injected at runtime via neon-env run / neon dev, or to keep secrets out of the working tree). A pull failure never undoes the checkout: the branch stays pinned and the failure is surfaced as a warning pointing you at neon env pull (or neon deploy if a neon.ts-declared service is missing).
diff
diff [compare-branch] prints a git-style schema diff between the branch you're on and another branch — the top-level companion to checkout. It reads the branch pinned in .neon as the side under review (the +++ side) and compares it against the branch you name (the ---, reference side), so + lines are what your current branch adds on top of the reference:
# On feature/add-comments (pinned in .neon), see how it differs from main:
$ neon diff main
→ Comparing schema main → feature/add-comments
diff --neon database neondb
--- main (br-crimson-snow-12345678)
+++ feature/add-comments (br-dry-salad-87654321)
@@ -63,7 +64,8 @@
CREATE TABLE public.users (
id integer NOT NULL,
email text NOT NULL,
- created_at timestamp with time zone DEFAULT now()
+ created_at timestamp with time zone DEFAULT now(),
+ display_name text
);compare-branchis optional. Omit it to compare the current branch against its parent (neon diffanswers "what did I change since branching?"). It accepts a branch name orbr-…id.--branch, -b <name|id>overrides the side under review instead of reading.neon— e.g.neon diff main --branch feature/checkoutdiffs an explicit branch againstmain.--database, --db <name>limits the diff to one database; by default every database on the current branch is compared (each rendered as its owndiff --neon database <name>block). A database missing on the reference side shows as fully added.--output json|yamlemits a structured result per database ({ database, base_branch, compare_branch, has_changes, diff }) for scripting; the default renders the colorized git-style diff (respecting--no-colorand non-TTY pipes).
The human-readable summary line goes to stderr and the diff body to stdout, so neon diff main > changes.patch captures just the diff. When the schemas match, diff prints No schema differences … and writes nothing to stdout. For history-aware comparisons (a branch against its own past state at a timestamp or LSN), use branches schema-diff.
env pull
env pull writes the linked branch's Neon environment variables into a local dotenv file: an existing .env if you have one, otherwise .env.local (override with --file <path>). Only Neon-managed keys are written (see the table below); any other lines in the file are preserved. The branch comes from the closest .neon file, so no --branch is needed (pass --branch <id|name> to target another branch).
What gets pulled, in precedence order:
--serviceand/or--env, when you pass either — their union is the complete selection, ignoringneon.tsand unselected branch variables.--serviceadds a service's complete variable bundle;--envadds only the individual variables you name.neon.ts, when the working directory has one — the policy is the source of truth, same asneon devandneon deploy. Declared function URLs are derived from the branch connection host; the function does not have to be deployed.- Everything the branch has otherwise — Postgres, Neon Auth, the Data API, object storage, and function invocation URLs read back from the branch, plus the AI Gateway. The gateway has no branch-level state to read back, so a bare
env pullasks for it rather than detecting it and may mint a branch credential. To leave it out, name only what you do want with--serviceand/or--env.
On an unclaimed Claimable Neon project, neon.ts is still the source of truth when it only declares Postgres, Auth, and the Data API. A neon.ts that declares AI Gateway, Functions, or Object Storage fails: those cannot be used until the project is claimed. Without a neon.ts, a bare pull writes provisioned Postgres, Auth, and Data API. Naming AI Gateway, Functions, or Object Storage with --service / --env warns and writes nothing for them. --config selects the policy file, the same flag as claim create and config plan.
If the gateway can't be resolved, it is dropped with a warning and the rest of the pull still lands. Gateway variables already in your file for this branch are left alone — a pull that couldn't reach the gateway is no evidence the branch has stopped having one — while ones left over from a different branch are pruned like any other stale value.
# Refresh the linked branch's vars in place
neon env pull
# Pull a specific branch into a specific file
neon env pull --branch preview --file .env.preview
# Use a specific neon.ts
neon env pull --config ./claimable.ts
# Only the AI Gateway
neon env pull --service ai-gateway
# Repeat the flag or comma-separate; -s, --service and --services are all accepted
neon env pull -s postgres -s data-api
neon env pull -s postgres,auth
# Pull one exact variable; repeat -e or comma-separate for more
neon env pull -e DATABASE_URL
neon env pull -e DATABASE_URL,NEON_AUTH_BASE_URL
# The selectors compose as a union: all Auth vars plus DATABASE_URL
neon env pull -s auth -e DATABASE_URL
# Function invocation URLs
neon env pull -s functions
neon env pull -e NEON_FUNCTION_HELLO_BASE_URLEvery services flag in the CLI takes those three spellings, the same value syntax, and the same service names — see config init --services.
| --service | Variables |
| --- | --- |
| postgres | DATABASE_URL, DATABASE_URL_UNPOOLED |
| auth | NEON_AUTH_BASE_URL, NEON_AUTH_JWKS_URL |
| data-api | NEON_DATA_API_URL |
| functions | NEON_FUNCTION_<SLUG>_BASE_URL for each deployed function |
| object-storage | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ENDPOINT_URL_S3, AWS_REGION |
| ai-gateway | NEON_AI_GATEWAY_TOKEN, NEON_AI_GATEWAY_BASE_URL |
-e, --env accepts any variable in the table plus NEON_BRANCH, or NEON_FUNCTION_<SLUG>_BASE_URL where <SLUG> is the function slug uppercased. It is case-sensitive and rejects unknown names rather than silently widening the pull. NEON_BRANCH is written by unscoped and service-scoped pulls because it is branch identity, not a service; an env-only pull writes it only when you select it.
--env never narrows --service: neon env pull -s postgres -e DATABASE_URL still pulls the complete Postgres bundle (DATABASE_URL, DATABASE_URL_UNPOOLED, and NEON_BRANCH). The two selectors always form a union.
A scoped pull is scoped in both directions. An unscoped env pull owns the Neon-named variables: pointing a directory at a branch without Neon Auth prunes the stale NEON_AUTH_* lines, and a branch without those functions prunes stale NEON_FUNCTION_*_BASE_URL lines. --service narrows that to the services you named, while --env narrows it to the exact keys you named, so env pull -e DATABASE_URL never touches DATABASE_URL_UNPOOLED. (AWS_* is never pruned by any pull: those names collide with credentials you may set yourself, so env pull only ever writes them.)
A scoped pull also never revokes a credential. Where an unscoped pull revokes the credential it replaces, a scoped one leaves the old one live — it can't tell which other variables still use it. It says so when it happens; revoke it in the Neon Console if nothing does.
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be selected together. Neon issues them as one object-storage credential; pulling only one would pair a fresh half with whatever old half remains in the file.
Naming a service the branch does not have is an error, not an empty pull:
--service auth: branch br-snowy-frost-12345 has no Neon Auth integration, so there are no
auth env vars to pull. Provision it first (`neon deploy`, `neon config apply`, or the Neon
Console), or drop auth from --service.link, checkout, and config apply invoke env pull automatically (see above). Those bundled pulls follow rules 2 and 3 above without the implied AI Gateway: minting a credential for a service you never named isn't something a side effect of another command should do. Run neon env pull to get it.
If you'd rather not keep env vars on disk, inject them at runtime instead with neon-env run -- <your dev command> (from @neon/env) or neon dev, and pass --no-env-pull to link / checkout.
neon dev resolves the same set, by the same rules — including the AI Gateway on a branch with no neon.ts. A function running locally gets the same Postgres / Auth / Data API / storage / AI Gateway vars a deploy would inject. Each NEON_FUNCTION_<SLUG>_BASE_URL this process is serving is rewritten to http://localhost:<port>: every neon.ts function, or the matching slug when --source names one. neon env pull and neon-env run write the production URL (https://<branchId>-<slug>.compute.…). dev writes nothing, but it does read your .env / .env.local to reuse the branch credential behind the AI Gateway and object storage. Without a file to read from it issues one on every start and leaves the last one live — it has nowhere to keep it, and so cannot name it to revoke it. It says so when it happens; run env pull (or just link / checkout) once and restarts reuse the credential instead.
Where .neon lives: link writes .neon into the current working directory by default. If an existing .neon is found in any parent directory, that file is reused — so commands run from a sub-directory of a linked project still pick up the project's context. To pin the location explicitly, pass --context-file <path>.
.gitignore scaffolding: when .neon is created for the first time, the CLI also makes sure a .gitignore sits alongside it listing .neon. If .gitignore doesn't exist it's created with a single .neon line; if it does exist, .neon is appended only when missing (no duplicates, your other entries are left alone). On subsequent updates to an existing .neon, .gitignore is left untouched — so if you deliberately un-ignore .neon (e.g. to commit shared context), the entry is not re-added on every command.
Config as code (config / deploy)
Describe a branch's desired state in a neon.ts policy and reconcile it from the CLI — the Neon equivalent of terraform status / plan / apply. A policy splits into a static existential set — top-level auth / dataApi / aiGateway / functions / buckets / triggers (aiGateway, functions, and buckets still work under deprecated preview) that decide what exists — and a dynamic branch closure that tunes each branch (compute settings, TTL, protection, parent) based on the branch it's evaluated for (name, isDefault, …):
// neon.ts
import { defineConfig } from '@neon/config/v1';
export default defineConfig({
// Static: what exists on every branch (drives the typed env).
auth: true,
// Dynamic: per-branch tuning only — cannot add/remove services.
branch: (branch) => {
if (branch.isDefault) {
return { protected: true };
}
return { parent: 'main', ttl: '7d' };
},
});Getting a neon.ts (config init)
neon config init scaffolds the policy and installs @neon/config / @neon/env, so a project can go straight to plan / apply. It is purely local — no auth, no API calls. In an interactive terminal it asks which services the policy should declare:
? Which Neon services should neon.ts declare? (space to toggle, enter to confirm) ›
◯ Managed Better Auth
Authentication with users and sessions stored in Postgres.
◯ Data API
PostgREST-compatible HTTP API. Also declares Auth; the default provider needs it.
◯ Functions
Long-running, without timeouts, and closer to your database.
◯ Object Storage
S3-compatible blob storage that branches with your projects.
◯ AI Gateway
All models, one API, one bill. Powered by Databricks. Not available on the Neon free plan.Selecting nothing is a valid answer: you get the starter policy, which is also what a non-interactive run (CI, no TTY) writes. Pass --services to skip the prompt anywhere:
# Pick interactively (TTY) or take the starter policy (CI)
neon config init
# Declare services with no prompt
neon config init --services auth,functions,object-storage,ai-gateway
# Data API also writes auth: true (the default provider requires it)
neon config init --services data-api
# Repeat the flag instead, and shorten it — every services flag takes all three spellings
neon config init -s auth -s functions
# Explicitly ask for the bare starter policy
neon config init --services none
# Scaffold but print the install command instead of running it
neon config init --no-installObject storage is spelled object-storage here, matching env pull --service and the rest of the CLI. The old storage still works and warns; it will be removed.
Choosing Functions also writes the handler the policy points at, since source is only resolved when apply bundles it — a declared function with no file on disk fails at deploy:
// hello.ts
export default async function hello(): Promise<Response> {
return new Response('Hello from Neon Functions');
}An existing neon.ts (or hello.ts) is never overwritten.
Four sub-commands plus two top-level aliases drive it:
# Scaffold a neon.ts and install the config packages (local only)
neon config init
# Inspect the branch's live Neon state (read-only — never mutates)
neon config status
# `neon status` is an alias for `neon config status`
neon status
# Dry-run diff: show exactly what `apply` would change
neon config plan
# Reconcile the policy against the branch
neon config apply
# `neon deploy` is an alias for `neon config apply`
neon deployProject & branch resolution follows the same chain as the rest of the CLI, each entry winning over the next:
--project-id <id>flagprojectIdfrom the closest.neonfile (found by walking up from the current directory — see "Where.neonlives" above)- If still unresolved and the API key maps to exactly one project, that project is auto-detected
The branch is chosen with --branch <id|name>; without it the project's default branch is used. The policy itself is found by walking up from the current directory for a neon.ts, or pass --config <path> to point at one explicitly.
Apply-only flags (also available on deploy):
--update-existing— auto-confirm overriding existing remote settings on the branch. Without it, drift on settings already present remotely (compute, TTL,protected) is reported as a conflict andapplymakes no changes until you resolve it or pass this flag.--allow-protected— auto-confirm applying to a branch Neon marks as protected. Without it,applyrefuses to touch a protected branch.
Output: status prints the project, branch, and reverse-engineered config. plan / apply render a git diff-style report (matching neon diff): service changes (Neon Auth, Data API, buckets, functions) list as green + additions, while branch setting changes (TTL, protected, compute) show grouped under a ~ <branch> header, one sorted field → value line each. A bare apply that hits drift on settings already present remotely prints those as a sorted before→after diff (current → desired, old in red / new in green) and exits non-zero until you pass --update-existing. Pass --output json (or --output yaml) to emit the full machine-readable result (PushResult) instead, for piping into other tools or CI.
config status --current-branch (alias neon status --current-branch) prints only the branch pinned in the local .neon file — no network, no auth, no analytics — and exits non-zero when none is pinned. This behavior lets it safely drive a shell prompt. Example starship segment:
[custom.neon]
description = "Current Neon database branch"
format = "[$symbol$output]($style) "
style = "bold green"
# `symbol` below uses a Nerd Font glyph; swap it for a plain
# label/emoji if you don't have a Nerd Font installed.
symbol = " "
command = "neon status --current-branch"
# Starship evaluates this on EVERY prompt render. To keep prompts instant
# everywhere outside a Neon project, do a zero-subprocess walk-up for an
# ancestor `.neon` first (the same walk the CLI does, stopping at $HOME and /).
# Only when one is found do we invoke the CLI, whose exit code is the real
# gate: non-zero (no branch pinned) hides the segment cleanly.
when = '''
d="$PWD"
while [ "$d" != "$HOME" ] && [ "$d" != / ]; do
if [ -e "$d/.neon" ]; then
neon status --current-branch >/dev/null 2>&1
exit $?
fi
d=$(dirname "$d")
done
exit 1
'''# CI gate: fail the build if the branch has drifted from the policy
neon config plan --project-id polished-snowflake-12345678 --output json
# Reconcile a feature branch, overriding any manual tweaks made in the console
neon deploy --branch my-feature --update-existingFunction deploys declared under functions are bundled with esbuild by default. A directory source is discovered as index.ts, then index.js, then index.mjs. Set bundler: "none" to ship a prebuilt directory as-is. neon function deploy --no-bundle is the same switch without a neon.ts.
Point a domain you already own at a function with neon function domains (alias domain):
neon function domains register docs.example.com --slug api
neon function domains list
neon function domains delete docs.example.comRegister prints the record, then an INFO line CNAME <domain> to <cname_target>. The domain goes live after that CNAME resolves and a certificate is issued on the first request.
A neon.ts customDomains list is applied by neon config plan, neon config apply, and neon checkout only on the project's default branch. Other branches still deploy the function. Set customDomains in the branch closure to register a hostname on a child. See @neon/config Default-branch-only fields.
When a package cannot be bundled — a native addon with no esbuild loader, or an optional peer dependency a library references on an untaken code path — list it in that function's externalPackages and the bundler leaves the import alone. neon dev honours the same list. It does not make the package resolvable in the deployed archive (there is no node_modules next to the bundle), so it only unblocks an import that is never evaluated — a dependency the handler actually calls has to be bundled, and a natively-backed one cannot be. See @neon/config.
Scaffold a project (bootstrap)
neon bootstrap copies a Neon starter template into a new (or current) directory — conceptually like degit, but it only pulls from a small set of templates we maintain in the public neondatabase/examples repo. The template copy needs no Neon login: it downloads files from GitHub.
After scaffolding, an interactive terminal asks about dependency install and git, then finishes agent setup before asking whether to link a Neon project. Dependency install is last, except when the template has a neon.ts and you chose to link — then install runs first so the link flow can pull env. --default / -y skips the template, install, git, and agent pickers, then installs agent tooling for project folders, else the host CLI agent. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit --default / -y in a terminal to pick. --agent / -a names coding agents, skips agent selection, and is forwarded to plugins, or to skills and mcp, not both. Linking uses neon link -y: the only organization and project are selected, or their IDs are printed and the command exits. --no-agent-setup and --no-link skip those steps. Non-interactive without --default prints next steps and does not install, set up agents, or link.
Pass a target directory (or . for the current one). In an interactive terminal you pick the template from a list; in CI / non-interactive contexts pass --template <id>.
# Pick a template interactively and scaffold it into ./my-app
$ neon bootstrap my-app
# Scaffold a specific template into the current directory (skips the template picker)
$ neon bootstrap . --template hono
# Skip agent selection; install the plugin for those agents
$ neon bootstrap my-app --agent cursor --agent claude-code
# List templates
$ neon bootstrap --list-templates
# Machine-readable catalog
$ neon bootstrap --list-templates --output jsonThe target directory must be empty unless you pass --force (a lone .git is ignored, so a freshly git inited folder is fine). Symlinks and executable bits in the template are preserved.
Set up a project (init)
neon init sets up this directory for Neon as one flow: agents, a linked project, and optionally neon.ts.
An empty directory (nothing except .git) asks how to set up the directory. Pick a starter template, or skip scaffolding and only install agent tooling, link a project, and optionally write neon.ts. That skip is only on init — neon bootstrap always scaffolds.
-y in an empty directory scaffolds the default template (bootstrap --default) and does not add a later config init step. --skip-template skips scaffolding even with -y. --template <id> scaffolds that template; combine it with -y to skip the remaining bootstrap pickers.
An existing app (or skip-template) installs agent tooling, then asks whether to link unless .neon already has a projectId, then asks whether to create neon.ts. Linking uses the same organization, project, and branch flow as neon link. Saying no to neon.ts skips the services picker and does not write the file. --no-config does the same without asking. -y writes config init --services none unless --no-config. --services implies creating neon.ts.
In an interactive terminal it offers one of: the Neon plugin (neon plugins), skills and MCP separately (neon skills, then neon mcp), or skip agent setup. Agent selection and setup finish before the link question. It never runs plugin and skills+MCP together.
$ neon init
$ neon init --skip-template
$ neon init --skip-template --no-link
$ neon init -y
$ neon init --agent cursor --agent claude-codeWithout a TTY, pass -y. --agent skips agent selection but does not replace -y for link or templates. --no-link skips project linking without asking, including when a template is scaffolded.
-y skips the template picker and the agent-setup offer. Empty dir: bootstrap --default. --skip-template or an existing app: plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent; otherwise skills and MCP. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. VS Code, GitHub Copilot CLI, and Grok only take the plugin user-level (neon plugins --global), so -y uses skills and MCP for those.
--agent / -a (repeatable) names coding agents and skips agent selection, interactive or with -y. Init forwards those names to plugins, or to skills and mcp, not both.
--project-id, --org-id, --project-name, --region-id, and --branch are forwarded to link, including the link step inside nested bootstrap when a template is scaffolded. They are not filled from .neon; a linked directory is not relinked unless you pass one of those flags.
--config, --no-config, and --services apply on the existing-app and --skip-template path. A template's own neon.ts is left as the template shipped it. Passing those flags while scaffolding prints a warning and still copies the template as shipped. After a new neon.ts on a pinned branch, init runs env pull.
-y forwards -y to plugins or skills/mcp, uses link -y (auto-select or print IDs and exit), passes --default to nested bootstrap, and forwards --services none to config init. --agent is forwarded to agent setup. mcp -y is the global install. Default linking skips the "already linked" confirmation.
A failed step stops the rest. --profile and --config-dir are forwarded to each child. --output json and --output yaml are refused; the commands init runs print their own output.
skills needs Node.js 22.20 or newer. See bootstrap, plugins, skills, link, and mcp for what those commands write.
Install the Neon MCP server (mcp)
neon mcp writes the hosted Neon MCP server (https://mcp.neon.tech/mcp) into coding-agent config files.
# Interactive: global or project, then agents, then API key or OAuth, then confirm.
$ neon mcp
# Skip prompts. Global config, installed apps else the host CLI agent, reuse or mint an API key.
$ neon mcp -y
# OAuth: no API key minted. The agent prompts for Neon sign-in on first use.
$ neon mcp --oauth
# Named agents.
$ neon mcp --agent cursor --agent claude-code
# Project-level config. A minted key is still account-wide unless a project is pinned.
$ neon mcp --project
# Hide write tools. Does not change the minted key.
$ neon mcp --read-only
# Pin MCP tools to one project. A newly minted API key is limited to that project.
$ neon mcp --project-id <project-id>
# Limit which tool categories are visible.
$ neon mcp --category querying --category schemaOn a TTY the command asks for config location (global is the default), then agents, then API key vs OAuth, then a summary to confirm before it writes. Detected agents start selected: globally installed agents or project-folder markers such as .cursor when the install is project.
-y skips those questions. neon mcp -y writes https://mcp.neon.tech/mcp into global config for globally installed apps, else the host CLI agent, reuses an existing Neon MCP API key or mints an account-wide key, leaves write tools enabled, exposes every tool category, and does not pin a project (including from .neon). --agent, --project, --oauth, --read-only, --project-id and --category still apply with -y. --read-only and --category are flags only and are never prompted. A linked project-folder install asks whether to pin MCP tools to that .neon project (?projectId=). If you pin and selected API-key auth, the minted key is limited to that project too. An unlinked project folder does not ask. Global installs never add that param unless you pass --project-id. Without a TTY, pass -y to mint into every detected agent, --agent <name> to name them, or --oauth to write the URL only. If -y finds no agent, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. neon mcp --help lists the server URL, those -y defaults, the supported agent names, and the --category values.
Supported agents: antigravity, cline, cline-cli, claude-code, codex, cursor, gemini-cli, goose, github-copilot-cli, grok-build, mcporter, opencode, vscode, windsurf, zed. Project installs drop antigravity, cline, cline-cli, goose and windsurf. claude-desktop is a known name that is then skipped.
The default mints an account-wide API key (or reuses the Bearer already configured for Neon at https://mcp.neon.tech/mcp) and writes it into each selected agent's config. That key reaches everything the account can, in every organization. Revoke it with neon api-keys revoke <id>. --oauth writes the URL with no Authorization header; the agent signs in on first use. --project writes into the project config (.cursor/mcp.json and similar). --read-only adds ?readonly=true. --project-id adds ?projectId= and, when a key is minted, limits that key to the named project. Accepting the linked-project pin does the same. Revoke a project-scoped key with neon api-keys revoke <id> --org-id <org>. A reused Bearer keeps the scope it already has. --category adds ?category= (repeatable or comma-separated: projects, branches, schema, querying, neon_auth, data_api, observability, docs). --read-only and --category restrict which MCP tools the server exposes; they do not change what the minted key can do.
Install Neon agent skills (skills)
neon skills installs Neon agent skills by running npx skills add. It does not call the Neon API. This command needs Node.js 22.20 or newer. The rest of the CLI supports Node.js 20.19 or newer.
# Interactive: this directory, then agents, then skills, then confirm.
$ neon skills
# Skip prompts. This directory, detected agents (project folders, else the host CLI agent), the default skills.
$ neon skills -y
# Named skills into detected agents.
$ neon skills -y -s neon -s neon-ai-gateway
# Named skills into a named agent.
$ neon skills -s neon -s neon-ai-gateway --agent cursor
# One skill, no prompts.
$ neon skills -s neon-auth --agent cursor -y
# User-level skills.
$ neon skills --global
# Update installed skills in this directory.
$ neon skills update
$ neon skills update -y
$ neon skills update --global -yOn a TTY the command asks which agents and which skills, then shows a summary to confirm. Detected agents start selected from project-folder markers such as .cursor. Default skills start selected. neon-postgres-agent-platforms is offered and starts unselected.
-y skips those questions and installs the default skills into detected agents: project-folder markers such as .cursor, else the agent driving the CLI. --agent / -a names coding agents and skips the agent picker. --skill / -s names specific skills and skips the skill picker; it does not select agents. --global -y uses installed apps, else the host CLI agent. Without a TTY, pass -y, or --skill <name> (add --agent <name> to name agents). If -y finds no agent, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick.
--skill names by source repo: neondatabase/agent-skills (neon, neon-ai-gateway, neon-auth, neon-functions, neon-object-storage, neon-postgres, neon-postgres-branches, neon-postgres-egress-optimizer); neondatabase/neon-for-agent-platforms (neon-postgres-agent-platforms).
Supported agents match neon mcp, minus agents that cannot install skills: antigravity, cline, cline-cli, claude-code, claude-desktop, codex, cursor, gemini-cli, goose, github-copilot-cli, grok-build, opencode, vscode, windsurf, zed. mcporter is a known MCP name that is then skipped. neon skills --help lists the same skill and agent values, and that -y leaves out neon-postgres-agent-platforms.
Ask the Neon assistant (ask)
neon ask --prompt asks the hosted Neon assistant a question about Neon. It does not log in and does not use your Neon account.
neon ask --prompt "How do schema-only branches work?"
neon ask --prompt "How do schema-only branches work?" --output jsonDefault table output is the assistant's text. On a TTY that is a spinner, then the streamed reply. --output json and --output yaml print { "text": "…" } after the full response.
Install the Neon plugin (plugins)
neon plugins installs the Neon agent plugin (neon-postgres) by running npx plugins add. It does not call the Neon API.
# Interactive: agents, then confirm.
$ neon plugins
# Skip prompts. Detected agents (project folders, else the host CLI agent).
$ neon plugins -y
# Named agents.
$ neon plugins --agent cursor --agent claude-code
# User-level install.
$ neon plugins --globalOn a TTY the command asks which agents, then shows a summary to confirm. Detected agents start selected from project-folder markers such as .cursor. There is one plugin (neon-postgres); there is no plugin picker and no update subcommand.
-y skips those questions and installs into detected agents: project-folder markers such as .cursor, else the agent driving the CLI. --agent / -a names coding agents and skips the agent picker. --global -y uses installed apps, else the host CLI agent. Without a TTY, pass -y or --agent <name>. If -y finds no agent, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick.
Default scope is project. --global is user. On macOS and Linux, Cursor and Claude Code store the plugin cache under ~/.claude/plugins; on Windows, Cursor installs into Cursor extensions. project vs user is the scope field the plugins CLI records, not a directory in the repo. VS Code, GitHub Copilot CLI, and Grok Build only install user-level: they are skipped at the default scope with a warning, and a VS Code-only project fails if nothing else is selected. Pass --global for those.
Supported agents with a plugins mapping: claude-code, claude-desktop, codex, cursor, github-copilot-cli, grok-build, vscode. claude-desktop installs as Claude Code; detecting both produces one install and lists both names in the table. mcporter is a known MCP name that is then skipped.
The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is neon-postgres from neondatabase/agent-skills. It includes the Neon MCP server (https://mcp.neon.tech/mcp) and these skills: neon, neon-ai-gateway, neon-auth, neon-functions, neon-object-storage, neon-postgres, neon-postgres-branches, neon-postgres-egress-optimizer. It does not include neon-postgres-agent-platforms.
neon plugins --help lists the plugin, those contents, and the supported agent names.
Snapshots (snapshots)
neon snapshots (alias neon snapshot) manages snapshots — point-in-time backups of a branch that you can list, rename, expire, restore into a branch, or schedule automatically. Snapshots are a Beta Neon feature and were previously only available in the Console and REST API; this command group brings them to the CLI.
Every sub-command resolves the project through the standard chain (--project-id, then the .neon context file, then a single-project auto-detect). Branch-scoped sub-commands (create, schedule) default to the branch pinned in .neon, falling back to the project's default branch, and accept --branch <id|name>. The get, update, delete, and restore sub-commands take a snapshot id or name as their positional argument (an id wins; an ambiguous name errors and asks you to use the id).
# Snapshot the head of the current/default branch
neon snapshots create --name pre-migration
# Snapshot a specific branch at a point in time (RFC 3339 timestamp OR LSN — mutually exclusive)
neon snapshots create --branch main --timestamp 2025-01-01T00:00:00Z
neon snapshots create --branch main --lsn 0/1F3C8A0 --expires-at 2025-12-31T23:59:59Z
# List / inspect
neon snapshots list
neon snapshots get pre-migration
# Rename or change expiration (omit both to error; --expires-at and --clear-expiration conflict)
neon snapshots update snap-1234 --name nightly
neon snapshots update snap-1234 --expires-at 2030-01-01T00:00:00Z
neon snapshots update snap-1234 --clear-expiration # keep indefinitely
# Restore a snapshot to a NEW branch
neon snapshots restore snap-1234 --name recovered
# Restore ONTO an existing branch. Without --finalize the restore is left un-finalized
# so you can inspect it first, then swap it in:
neon snapshots restore snap-1234 --target-branch main
neon snapshots finalize br-restored-1234 # commit the swap
# …or do it in one step:
neon snapshots restore snap-1234 --target-branch main --finalize
# Delete
neon snapshots delete snap-1234
# Automatic snapshot (backup) schedule of a branch
neon snapshots schedule get --branch main
neon snapshots schedule set --branch main --frequency daily --hour 3 --retention 604800
neon snapshots schedule set --branch main --schedule '[{"frequency":"weekly","day":1,"hour":2},{"frequency":"daily","hour":3}]'All sub-commands honor the global options, including --output json|yaml|table.
Function triggers (triggers)
neon triggers (alias neon trigger) manages Function triggers on a branch. Types are schedule (a five-field UTC cron expression) and storage_object_created (an object-storage bucket, with an optional object-key prefix).
Every sub-command resolves the project through the standard chain (--project-id, then the .neon context file, then a single-project auto-detect) and the branch through --branch <id|name>, the .neon pin, or the project's default branch.
neon triggers create --function-slug uptime --name uptime-check --cron '*/15 * * * *'
neon triggers create --function-slug ingest --name on-upload --bucket assets --prefix 'logos/' --function-path /object
neon triggers list
neon triggers get trigger-test-123
neon triggers update trigger-test-123 --cron '0 3 * * *'
neon triggers update trigger-storage-123 --bucket assets --prefix 'incoming/'
neon triggers enable trigger-test-123
neon triggers disable trigger-test-123
neon triggers delete trigger-test-123enable / disable are wrappers over update --enabled. List shows inherited when the effective config was authored on an ancestor branch. create takes --cron or --bucket, not both.
Branch credentials (credentials)
neon credentials (alias neon credential) lists, issues, reveals, rotates, and revokes branch-scoped credentials — the tokens behind Object Storage (AWS_*) and the AI Gateway (NEON_AI_GATEWAY_TOKEN). Beta. In regions where those services exist, a new project already has default credentials named Default AI gateway credential and Default object storage credential; list then reveal recovers their secrets without minting another token.
neon credentials list
neon credentials create --name app --scope storage:read --scope storage:write
neon credentials reveal nak_live_…
neon credentials rotate nak_live_…
neon credentials revoke nak_live_…create always issues a customer-managed (user) credential. --scope is repeatable; --help lists the values. reveal and rotate print api_token and s3_secret_access_key. Rotation keeps the same token_id (it is the AWS_ACCESS_KEY_ID) and is not idempotent: a retry after a lost response mints another secret.
All sub-commands honor the global options, including --output json|yaml|table.
Database diagnostics (inspect)
neon inspect db stalled-queries takes a read-only snapshot of active queries that have run for more than 30 seconds and groups parallel workers with their leader. Oldest group first. Table output shows duration, wait event, blocking pids, role, query group, and query. --output json adds timestamps, query IDs, pids, database, and the rest of the row. A blocking pid can belong to an idle-in-transaction backend this command does not list; neon inspect db locks shows lock holders.
neon inspect db stalled-queries
neon inspect db stalled-queries --output jsonLogs (logs)
neon logs r
