# neon

> CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres

Latest version **8.12.1** (published 2026-10-26) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install neon
pnpm add neon
yarn add neon
bun add neon
```

Provides the command `neon`.

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types.

## Facts

| | |
|---|---|
| Version | 8.12.1 |
| Published | 2026-10-26 |
| First published | 2012-12-22 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | none |
| Module format | ESM + CommonJS |
| Node | >=20.19.0 |
| Dependencies | 34 |
| Unpacked size | 2.9 MB |
| Known vulnerabilities | 0 (+3 in 2 direct dependencies) |
| Install scripts | no |
| GitHub stars | 63 |
| Author | Neon |
| Maintainers | guillaumervls, andrelandgraf, neonteam |
| Keywords | neon, neonctl, cli, postgres, database, serverless |

## Links

- npm: https://www.npmjs.com/package/neon
- Repository: https://github.com/neondatabase/neon-pkgs
- Homepage: https://github.com/neondatabase/neon-pkgs#readme
- Issues: https://github.com/neondatabase/neon-pkgs/issues
- npm.io page: https://npm.io/package/neon

## Dependencies (34)

- [pg](https://npm.io/package/pg.md) ^8.20.0
- [ws](https://npm.io/package/ws.md) ^8.21.2
- [ajv](https://npm.io/package/ajv.md) ^8.20.0
- [diff](https://npm.io/package/diff.md) 5.2.0
- [open](https://npm.io/package/open.md) ^10.2.0
- [yaml](https://npm.io/package/yaml.md) ^2.9.0
- [chalk](https://npm.io/package/chalk.md) 5.3.0
- [cliui](https://npm.io/package/cliui.md) 8.0.1
- [execa](https://npm.io/package/execa.md) ^9.5.2
- [which](https://npm.io/package/which.md) 3.0.1
- [yargs](https://npm.io/package/yargs.md) 17.7.2
- [fflate](https://npm.io/package/fflate.md) ^0.8.3
- [semver](https://npm.io/package/semver.md) 7.7.2
- [undici](https://npm.io/package/undici.md) ^7.28.0
- [add-mcp](https://npm.io/package/add-mcp.md) 2.0.0
- [prompts](https://npm.io/package/prompts.md) 2.4.2
- [chokidar](https://npm.io/package/chokidar.md) 5.0.0
- [prettier](https://npm.io/package/prettier.md) 3.9.8
- [@neon/sdk](https://npm.io/package/@neon/sdk.md) 7.5.0
- [pg-format](https://npm.io/package/pg-format.md) 1.0.4
- [picocolors](https://npm.io/package/picocolors.md) ^1.1.1
- [@clack/core](https://npm.io/package/@clack/core.md) 0.4.2
- [ajv-formats](https://npm.io/package/ajv-formats.md) ^3.0.1
- [pg-protocol](https://npm.io/package/pg-protocol.md) ^1.14.0
- [yoctocolors](https://npm.io/package/yoctocolors.md) ^2.1.2
- [@neon/config](https://npm.io/package/@neon/config.md) 1.9.4
- [openid-client](https://npm.io/package/openid-client.md) 6.8.1
- [@clack/prompts](https://npm.io/package/@clack/prompts.md) 0.10.1
- [@neon/realtime](https://npm.io/package/@neon/realtime.md) 0.1.2
- [postgres-array](https://npm.io/package/postgres-array.md) ^3.0.4
- [@hono/node-server](https://npm.io/package/@hono/node-server.md) 2.0.4
- [@neon/config-runtime](https://npm.io/package/@neon/config-runtime.md) 1.10.3
- [pg-connection-string](https://npm.io/package/pg-connection-string.md) ^2.14.1
- [@segment/analytics-node](https://npm.io/package/@segment/analytics-node.md) 1.3.0

## Alternatives

- [@salesforce/cli](https://npm.io/package/@salesforce/cli.md) — 389.7K weekly downloads
- [@mintlify/cli](https://npm.io/package/@mintlify/cli.md) — 208.9K weekly downloads
- [@grafana/e2e-selectors](https://npm.io/package/@grafana/e2e-selectors.md) — 128.7K weekly downloads
- [mintlify](https://npm.io/package/mintlify.md) — 112.0K weekly downloads
- [@intlayer/cli](https://npm.io/package/@intlayer/cli.md) — 22.8K weekly downloads

## Recent versions

- 8.12.1 (latest) — 2026-10-26
- 8.12.0 — 2026-10-26
- 8.11.4 — 2026-10-25
- 8.11.3 — 2026-10-24
- 8.11.2 — 2026-10-24
- 8.11.1 — 2026-10-23
- 8.11.0 — 2026-10-22
- 8.10.3 — 2026-10-21
- 8.10.2 — 2026-10-21
- 8.10.1 — 2026-10-21
- 8.10.0 — 2026-10-20
- 8.9.0 — 2026-10-20
- 8.8.0 — 2026-10-16
- 8.7.2 — 2026-10-15
- 8.7.1 — 2026-10-15
- … 148 more at https://npm.io/package/neon/versions

## README

# Neon CLI

The `neon` package is a command-line interface that lets you manage [Neon](https://neon.com/) — Lakebase Postgres, Object Storage, Functions, Managed Better Auth, and the AI Gateway — directly from the terminal. For the complete documentation, see [Neon CLI](https://neon.com/docs/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**

```shell
npm i -g neon
```

Requires Node.js 20.19 or higher.

**Howebrew**

```shell
brew install neonctl
```

**Binary (macOS, Linux, Windows)**

Download a `neon-<platform>` binary from the [releases page](https://github.com/neondatabase/neon-pkgs/releases), which carries one asset per platform for each `neon@<version>` tag.

### Upgrade

**npm**

```shell
npm update -g neon
```

Requires Node.js 20.19 or higher.

**Howebrew**

```shell
brew upgrade neonctl
```

**Binary (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.

Global npm, pnpm, Bun, and Homebrew installs check for updates once per day.
When an update is available, terminal commands print the matching upgrade command
every three days. Set `NO_UPDATE_NOTIFIER=1` to disable these checks. The notifier
does not run in CI, for machine-readable output, or from standalone binaries.

On macOS and Linux, the same terminal commands warn once a day when more than one
global `neon` install is on PATH, listing each one in PATH order. A project's
`node_modules/.bin`, which `npx` and package scripts add, doesn't count. When one
of them is Homebrew, the warning recommends `brew uninstall neonctl`, which
leaves `neon` running the other install, such as npm. While duplicates remain, the update notice is held back;
`NO_UPDATE_NOTIFIER=1` silences both.

Commands also warn once a day when Neon agent skills are behind their source, in this
directory's `skills-lock.json` or the user-level skills lock (see
[`neon skills status`](#install-neon-agent-skills-skills)). This warning runs without a
terminal too, so coding agents see it. It checks GitHub once a day in the background and
follows the same CI, machine-readable output, standalone binary, and
`NO_UPDATE_NOTIFIER=1` rules.

## Connect

Run the following command to authenticate a connection to Neon:

```bash
neon login
```

`neon auth` is an alias of `neon login`. The `login` 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 login](https://neon.com/docs/cli/auth) automatically launches the browser authentication process, except for `neon me`, which reports that you are not signed in.

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:

```bash
neon projects list --api-key <neon_api_key>
```

For information about obtaining an Neon API key, see [Authentication](https://neon.com/docs/reference/api/get-started), in the _Neon API Reference_.

To check which account and credential a command uses, run `neon me` with the same `--profile` or `--api-key` option and environment as that command. The `Authentication` row names the source, such as `OAuth (profile DEFAULT)`, `API key (profile work)`, `API key (--api-key)`, or `API key (NEON_API_KEY)`. When no credential is usable, `neon me` exits 1 with `Not signed in` and the `neon login` command to run; it never starts a sign-in itself.

## 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:

```bash
# Lakebase Postgres is always included
neon claim create

# Request Managed Better Auth and the Data API too
neon claim create --service auth --service data-api

# Create the project in another region (default: aws-us-east-2)
neon claim create --region-id aws-eu-central-1
```

When 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:

```json
{
  "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`:

```json
{
  "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 `.neon` context 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
  when `neon.ts` declares them (or, with no `neon.ts`, when they are provisioned) to `.env`
  or `.env.local` (disable this with `--no-env-pull`). The write is the same `env pull` used
  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.

```bash
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> --yes
```

`status`, `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:

```bash
neon projects create --name my-project --pg-version 18
```

Supported 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:

```bash
neon branches create --project-id <project-id> --name production --protected
```

Project 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:

```bash
neon projects create --name my-project --output json --no-secrets
neon branches create --project-id <project-id> --name preview --output json --no-secrets
```

The 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:

```bash
neon projects update <project-id> --enable-logical-replication
```

The CLI asks for confirmation because enabling logical replication suspends
active endpoints and cannot be undone. For non-interactive automation, pass
`--yes` explicitly:

```bash
neon projects update <project-id> --enable-logical-replication --yes
```

## Connect 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](https://neon.com/docs/cli/psql) for the full reference.

```bash
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 connection
```

Arguments after `--` are forwarded to psql:

```bash
neon psql main -- -c "SELECT version()"
neon psql main -- -f script.sql --csv
```

Options: `--project-id`, `--role-name`, `--database-name`, `--pooled`, `--endpoint-type` (`read_only` | `read_write`), `--ssl`, plus the [global options](#global-options).

### The `--psql` flag

Several other commands accept a `--psql` flag that opens a psql session against the resolved endpoint:

```bash
neon connection-string --psql --project-id <id>
neon projects create --psql
neon branches create --psql
```

Any arguments after `--` are forwarded to psql, for example:

```bash
neon cs --psql --project-id <id> -- -c "SELECT version()"
neon cs --psql --project-id <id> -- -f script.sql --csv
```

### Embedded 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 on `connection-string`, `projects create`, and `branches 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_MODE` psql variable)
- Persistent command history (`~/.psql_history`, libreadline format)
- `~/.psqlrc` autoload (including `$PGSYSCONFDIR/psqlrc` and 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 flags `c=`/`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)
- `\copy` to/from file, `PROGRAM`, `STDIN`, `STDOUT` (incl. the `\.` EOF marker); `\g` / `\gx` / `\gset` / `\gdesc` / `\gexec` and `\g | program` pipes
- Extended query + pipeline mode (`\bind`, `\bind_named`, `\startpipeline`, `\parse`, `\sendpipeline`)
- `\crosstabview`, `\lo_*` large objects, `\e`/`\edit` (external editor), `\s` (history), `\?`/`\h` help, `\if`/`\elif`/`\else`/`\endif`, `\set`/`\unset`, `\connect`, `\encoding` (live `SET client_encoding`), `\!`, `\cd`, `\prompt` (incl. no-echo `-`), `\password`
- Tab completion (~88 rules incl. live `pg_settings` GUC lookup, deep `ALTER` sub-actions, `JOIN` clauses, window `OVER`)

**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-point` channel 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**

- `sslmode` disable → verify-full; client certs in **PEM or DER** via `sslcert` / `sslkey` (+ `sslpassword` for encrypted keys, with the libpq group/world-readable-key check)
- Trust config: `sslrootcert` (incl. `=system` with `SSL_CERT_FILE` / `SSL_CERT_DIR`), default client-cert discovery (`~/.postgresql/postgresql.{crt,key}`), `sslcertmode`
- CRL: `sslcrl` and `sslcrldir`; `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-postgres` doesn't support it either, and Neon doesn't use it. `gssencmode=disable` / `prefer` are accepted; `gssencmode=require` is rejected with a clear error. `requirepeer` is 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 is `TLS_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.

## Subscribe to a Realtime query

`neon realtime subscribe` streams a live SQL query until interrupted:

```bash
neon realtime subscribe "SELECT id, title, completed FROM realtime_todos ORDER BY id"
```

The default terminal output is append-only. It identifies the selected project,
branch, and database; prints the initial result once; reports connection-state
changes; and then prints each published change set with its transaction IDs, a compact
`+insert ~update -delete` summary, and the affected rows. It does not take over the
terminal or require keyboard navigation. Pass `--changes` to display the raw `rows`
and `changes` events instead of the materialized view.

For scripts and agents, JSON output is a JSON Lines stream with one complete event
per line. YAML output uses one document per event:

```bash
neon realtime subscribe --changes --output json "SELECT * FROM realtime_todos" |
  jq --unbuffered 'select(.type == "changes")'

neon realtime subscribe --output yaml "SELECT * FROM realtime_todos"
```

Machine-readable output retains the event stream: without `--changes`, each event is
a complete `snapshot`; with `--changes`, events are raw `rows` and `changes` objects.
Lifecycle labels and the target header are terminal-only.

## Configure autocompletion

The Neon CLI supports autocompletion, which you can configure in a few easy steps. See [Neon CLI commands — completion](https://neon.com/docs/cli/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-id` alone is enough); it's omitted only when the project has no organization (personal account). `--org-id` that the project does not confirm is an error.
- **project** is taken from `--project-id` (or chosen interactively). `--org-id` without 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 `-y` pins the default. Zero branches, a stale pin, or several branches with no TTY and no `-y` fail without writing.

When a branch is pinned, `link` also runs [`env pull`](#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-context` is **deprecated** in favor of `link` (see [below](#set-context-is-deprecated)). It still works exactly as before for now (a raw write), it just prints a deprecation warning. The `.neon` `branchId` field is also superseded by `branch` (which stores the branch **name** when known); old `branchId` files are still read and are upgraded to `branch` the next time `link`/`checkout` writes the context.

There are two modes:

**Interactive (default)** — guided prompts for humans:

```bash
$ 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:    main
```

When 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):

```bash
$ 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:

```bash
$ 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:

```bash
# 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 main
```

Every 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.

```bash
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-2
```

Organization-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 `link` auto-detects the org from the first project and prints an informational message.
- If no projects exist yet, interactive `link` errors 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:

```bash
neon link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch main
```

#### `set-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>`](#checkout)   |
| `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.

```bash
neon open

# Open a project without changing the linked context
neon open --project-id polished-snowflake-12345678
```

A 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 `--create` to create it (equivalent to `neon branch create --name <name>`: branched from the project's default branch with a read-write compute, or from `neon.ts` when that file is present), then check it out. `--create` is 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:

1. `--project-id <id>` flag
2. `projectId` from the closest `.neon` file (found by walking up from the current directory — see "Where `.neon` lives" below)
3. If still unresolved and the API key maps to exactly one project, that project is auto-detected (same behaviour as `branches` and `connection-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:

```bash
$ neon checkout main --project-id polished-snowflake-12345678
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
Created branch dev (br-dev-branch-12345678).
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`](#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:

```bash
# 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-branch`** is optional. Omit it to compare the current branch against its **parent** (`neon diff` answers "what did I change since branching?"). It accepts a branch **name** or `br-…` **id**.
- **`--branch, -b <name|id>`** overrides the side under review instead of reading `.neon` — e.g. `neon diff main --branch feature/checkout` diffs an explicit branch against `main`.
- **`--database, --db <name>`** limits the diff to one database; by default every database on the current branch is compared (each rendered as its own `diff --neon database <name>` block). A database missing on the reference side shows as fully added.
- **`--output json|yaml`** emits 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-color` and 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`](https://neon.com/docs/reference/cli-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:

1. **`--service` and/or `--env`**, when you pass either — their union is the complete selection, ignoring `neon.ts` and unselected branch variables. `--service` adds a service's complete variable bundle; `--env` adds only the individual variables you name.
2. **`neon.ts`**, when the working directory has one — the policy is the source of truth, same as `neon dev` and `neon deploy`. Declared function URLs are derived from the branch connection host; the function does not have to be deployed.
3. **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 pull` asks for it rather than detecting it and may mint a branch credential. To leave it out, name only what you do want with `--service` and/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.

```bash
# 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_URL
```

Every services flag in the CLI takes those three spellings, the same value syntax, and the same service names — see [`config init --services`](#getting-a-neonts-config-init).

| `--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.…`).

Before `neon.ts` is evaluated, `dev` loads the project directory's `.env`, then `.env.local`, which overrides it, into the environment, so Function env values written as `process.env.X!` resolve the same way they do for `neon deploy --env`. A variable already set in the shell wins over both. Local Functions inherit every loaded variable, including keys `neon.ts` doesn't declare; the deployed runtime gets only declared and Neon-injected env. `--env <file>` loads only that file, resolved from the current directory, matching `neon deploy --env`:

```bash
neon dev                       # loads .env, then .env.local (overrides .env)
neon dev --env .env.staging    # loads only .env.staging
```

`dev` writes nothing. To reuse the branch credential behind the AI Gateway and object storage, it reads only the file `env pull` writes (`.env` when it exists, otherwise `.env.local`), so an override in the other file cannot pair one credential's key ID with another's secret. 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>`. `neon init` always writes `.neon` into the current directory.

**The project directory** is the directory holding that `.neon`, or the current directory when there is none. The project's `neon.ts` and the default `.env` / `.env.local` live there and nowhere else: a `neon.ts` in a parent directory never applies. An explicit `--config` or `--file` path still wins.

**`.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`, …):

```ts
// 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) ›
◉   Postgres (always included)
     Every Neon project includes a Postgres database.
◯   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.
```

Postgres cannot be unselected. Selecting nothing else 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:

```bash
# 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

# Postgres only: the starter policy, with no prompt
neon config init --services postgres

# Scaffold but print the install command instead of running it
neon config init --no-install
```

`postgres` declares nothing, since every branch has Postgres, so it can stand alone or sit next to other services. `neon init --services` takes the same values.

Object storage is spelled `object-storage` here, matching [`env pull --service`](#env-pull) 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:

```ts
// 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. To change one, use `config add`.

### Editing a `neon.ts` (`config add`)

`neon config add` declares one service in the `neon.ts` you already have and leaves the rest of the file (comments, the `branch` closure, formatting) as it was. It is purely local — no auth, no API calls — and creates the starter `neon.ts` first when there is none.

```bash
neon config add auth                        # auth: true
neon config add data-api                    # dataApi: true, and auth: true (the default provider needs it)
neon config add ai-gateway                  # aiGateway: true
neon config add function sendemail          # functions.sendemail + functions/sendemail.ts
neon config add function sendemail --name "Send email" --source ./src/send.ts
neon config add bucket uploads              # buckets.uploads = { access: 'private' }
neon config add bucket assets --access public_read
```

Each command prints what it changed and the next step:

```
$ neon config add function sendemail
Created functions/sendemail.ts.
Updated neon.ts: added functions.sendemail.
Next: `neon dev` to run it locally, `neon config apply` to deploy.
```

- **Functions** take a slug of 1-20 lowercase letters and digits, the same rule `plan` enforces. The handler file is written first and an existing file is never overwritten, so pointing `--source` at code you already have works. A `.js` `neon.ts` gets a `.js` handler.
- Enabling a service that `neon.ts` already enables changes nothing and exits 0. Adding a function slug or bucket name that already exists is an error.
- `--config <path>` edits a specific file; otherwise the closest `neon.ts` above the current directory is used, and the suggested `plan` / `apply` commands repeat the flag.
- `--no-install` applies when the command had to create `neon.ts`; editing an existing file never installs anything.

`config add` edits the file as text and checks its own output: it re-applies the edit to the result and fails if that is not a no-op. When the file is in a shape it cannot edit safely (a single-line `defineConfig({ auth: true })`, a service declared under `preview`, a value that is not a literal), it changes nothing and prints the exact lines to paste.

Four sub-commands plus two top-level aliases drive it:

```bash
# 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 deploy
```

**Project & branch resolution** follows the same chain as the rest of the CLI, each entry winning over the next:

1. `--project-id <id>` flag
2. `projectId` from the closest `.neon` file (found by walking up from the current directory — see "Where `.neon` lives" above)
3. 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 is the `neon.ts` in the project directory (next to `.neon`, or the current directory without one); pass `--config <path>` to point at another file.

**Confirmations** (also on `deploy`). Two things need a yes before `apply` changes anything: applying to a branch Neon marks as protected, and overriding settings the branch already has (compute, TTL, `protected`, Data API settings, custom-domain targets, and disabling an existing Data API). Adding services and deploying functions is not an override, so on an unprotected branch it never asks. In a terminal, `apply` shows the settings it would override and asks once, default No. Without a terminal (CI, agents, pipes) or with `-o json|yaml`, it exits 1 before changing anything and names the flag to pass:

- `--update-existing` — override settings that differ from `neon.ts`.
- `--allow-protected` — apply to a protected branch.
- `-y`, `--yes` — both.

```bash
neon deploy --env .env.prod --allow-protected   # production main is protected, no drift expected
neon deploy -y                                   # protected branch and drift, no questions
```

**Output**: `status` prints the project, branch, and reverse-engineered config. `plan` / `apply` render a **`git diff`-style report** (matching [`neon diff`](#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. When `apply` would override settings already present remotely, it prints them as a sorted **before→after** diff (`current → desired`, old in red / new in green) before asking or, without a terminal, before exiting non-zero. 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](https://starship.rs) segment:

```toml
[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
'''
```

```bash
# 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-existing
```

Function 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`):

```bash
neon function domains register docs.example.com --slug api
neon function domains list
neon function domains delete docs.example.com
```

Register 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](../config/README.md#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`](../config/README.md#unbundleable-dependencies-externalpackages).

## 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`](https://github.com/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>`.

```bash
# 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 json
```

The target directory must be empty unless you pass `--force` (a lone `.git` is ignored, so a freshly `git init`ed folder is fine). Symlinks and executable bits in the template are preserved.

## Set up a project (`init`)

`neon init` sets up coding agents and this directory for Neon. In a terminal it asks **Recommended** or **Custom**. `-y` is Recommended with no prompts. `-y` with `--skill`, MCP flags, `--no-agent-setup`, or `--claimable` is Custom using those flags.

Recommended installs the Neon plugin for every detected agent (global config, project folders, and the host CLI). Plugin-capable agents get the plugin; the rest get skills and MCP. If none are detected, it installs the default Neon skills for Cursor and Codex in this directory (`./.agents/skills`) and does not configure MCP. It then links a project when the CLI is authenticated, and writes a default `neon.ts` (Postgres only) using the package manager already in the directory.

Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon login`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in. Without credentials, `--org-id`, `--project-id`, and `--branch` together write `.neon` offline (as `neon link --no-checks` does), and init prints `neon login` as the next step; Neon checks the IDs on the first command that reaches it. With only some of those flags, init still installs agent tooling and writes `neon.ts`, then prints `neon login` and the `neon link` command with those flags.

When init picks MCP auth (no `--mcp-auth`) and the CLI credential is an organization or project-scoped API key, which cannot mint the MCP server's key, init configures MCP with OAuth instead and continues. With `--mcp-auth api-key`, init skips MCP, finishes linking and `neon.ts`, then reports `Neon setup failed.` with the `neon mcp` commands to retry and exits 1.

Custom asks how to add Neon to coding agents (plugin, skills and MCP separately, or skip), then how to get a project (sign in and link, or a claimable project when you are not signed in), then which services `neon.ts` should declare. Skills and MCP use separate agent pickers, and either can be skipped. Postgres is listed first as always included. The package manager is inferred from the directory; Custom asks only when none is detected. `--skill` selects skills (not the plugin) and skips that picker. MCP flags (`--mcp-config-location`, `--mcp-auth`, `--mcp-project-scoped`) select skills and MCP. `--mcp-config-location` is where the config is written: `global` maps to `neon mcp`, and `project` maps to `neon mcp --project`. With `-y`, the location defaults to `global`; interactive Custom asks. If some selected agents cannot write that location, init warns and skips MCP for those agents while continuing with the supported agents. It fails when none of the selected agents support the location. `--mcp-project-scoped` limits MCP tools to the linked Neon project by passing its ID to `neon mcp --project-id`. Omitted is false, including in Custom; there is no prompt. `--no-agent-setup` skips agent setup.

Empty directories are set up in place. `neon init` does not scaffold starter files.

```bash
$ neon init
$ neon init -y
$ neon init --agent cursor --agent claude-code --no-link
$ neon init --no-agent-setup --claimable
$ neon init -y --skill neon --mcp-auth oauth --mcp-config-location project
$ neon init -y --agent cursor --project-id <project-id> \
    --mcp-auth oauth --mcp-config-location project --mcp-project-scoped
```

Without a TTY, pass `-y` or enough flags to answer every question. `-y` alone is Recommended. `-y` with `--skill`, MCP flags, `--no-agent-setup`, or `--claimable` is Custom: those flags, Recommended defaults for the rest. `--skill` selects skills (not the plugin). MCP flags select skills and MCP. `--agent` skips agent selection and, without `-y`, selects Custom. `--no-link` skips project linking without asking. `--no-config` skips `neon.ts`. `--services` implies creating `neon.ts`. `--org-id`, `--project-id`, `--project-name`, `--region-id`, and `--branch` select linking. With `--claimable`, `--region-id` picks the claimable project's region.

`--project-id`, `--org-id`, `--project-name`, `--region-id`, and `--branch` are forwarded to `link`. They select linking. A linked directory is not relinked unless you pass one of those flags.

Init pulls the branch's env vars once, after the `neon.ts` step, so the pull includes what `neon.ts` declares. The AI Gateway and Functions need no deploy and are pulled immediately. When `neon.ts` declares Auth, the Data API, Object Storage, or Realtime, init pulls Postgres, plus the AI Gateway and the scaffolded function's URL when declared. `neon deploy` provisions the rest and pulls their env vars. An existing `neon.ts` is pulled by `link` as usual.

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 [`plugins`](#install-the-neon-plugin-plugins), [`skills`](#install-neon-agent-skills-skills), [`link`](#linking-a-project), and [`mcp`](#install-the-neon-mcp-server-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.

```bash
# Interactive: global or project, then agents, then API key or OAuth.
$ 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 schema
```

On a TTY the command asks for config location (global is the default), then agents, then API key vs OAuth, then 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.

```bash
# Interactive: this directory, then agents, then skills.
$ 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 -y

# Check whether installed Neon skills are behind their source.
$ neon skills status
$ neon skills status --global
```

`neon skills status` compares each Neon skill in this directory's `skills-lock.json` (or, with `--global`, the user-level skills lock) with the skill folder on its source repository's default branch, and prints `up to date`, `outdated`, or `not found upstream` per skill. It reads GitHub anonymously; set `GITHUB_TOKEN` or `GH_TOKEN` if the GitHub API rate limit is reached. Skills installed through an agent plugin are not in either lock and are not listed.

On a TTY the command asks which agents and which skills, then installs. 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`, `neon-realtime`); `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.

```bash
neon ask --prompt "How do schema-only branches work?"
neon ask --prompt "How do schema-only branches work?" --output json
```

Default 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.

## Send feedback (`feedback`)

`neon feedback --message` sends feedback to Neon. It does not log in, and it sends only your message, not your account, project, or branch.

```bash
neon feedback --message "The branch docs were unclear"
neon feedback --message "The branch docs were unclear" --output json
```

Default table output is `Feedback received. Thank you!`. `--output json` and `--output yaml` print `{ "received": true }`. Messages are limited to 10,000 characters. Do not include passwords, API keys, connection strings, or other secrets.

## Search neon.com (`docs search`)

`neon docs search <query>` searches neon.com: the docs, API reference, PostgreSQL tutorial, guides, FAQs, changelog, and blog. It does not log in and does not use your Neon account. It uses the same search as the neon.com search box. The search service records the query, the CLI version, and the detected coding agent; `--no-analytics` turns that off.

```bash
neon docs search "reset a branch"
neon docs search "create branch" --category api --category docs
neon docs search "pg_dump" --limit 5 --output json
```

`--category` limits results to `docs`, `api`, `postgresql`, `guides`, `faqs`, `blog`, or `changelog`; repeat it for more than one. `--limit` is 1 to 40, default 10. Default output is one block per result: `Title › Section` on one line, cut to the terminal width, and the full URL under it. `--output json` and `--output yaml` print an array of hits with `title`, `url`, `category`, `breadcrumb`, `heading`, and `excerpt`, in rank order. No match prints `No results for "<query>".` and exits 0 (`[]` in JSON).

## 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.

```bash
# Interactive: pick agents, then install.
$ 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 --global
```

On a TTY the command asks which agents, then installs. 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`, `neon-realtime`. 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).

```bash
# 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](#global-options), including `--output json|yaml|table`.

## Function templates (`functions new`)

`neon functions new <template>` fetches a template from the Neon Function Registry, writes its modules into `functions/<slug>/` next to `neon.ts`, and declares the result in `neon.ts` (creating it when missing), the same way `config add function` does.

```bash
neon functions templates list
neon functions new
neon functions new basic
neon functions new basic --slug hello --no-add-to-config
neon functions new <template> --operation <id> --operation <id> --install
```

Without a template, a terminal asks which one to use; anything else exits with the available IDs. Like `bootstrap`, it asks once whether to install the template's dependencies (plus `@neon/config` and `@neon/env` when it creates `neon.ts`): `--yes` installs, `--no-install` skips, and a run without a terminal leaves the install command under `Next:`.

A template's `layout` decides what you get. `router` writes one module per operation plus a generated `index.ts` that dispatches on the URL path, and declares one function. `separate` declares each operation as its own function under the slug the template gives it. Without `--operation` or `--all-operations`, a TTY asks which operations to include and anything else uses the template's recommended ones.

Variables a template needs are declared on each function in `neon.ts` (`env: { API_KEY: process.env.API_KEY! }`) and added, without values, to `.env.example` next to it. Values are never written to `.env` or `.env.local` for you: a blank placeholder there would deploy as an empty string and delete the live value. Put the real value in the file `env pull` uses and deploy with `neon config apply --env .env.local`. `neon dev` reads function env from your shell, so export it there as well.

Nothing is written until every check passes: a file that already exists with different contents needs `--force`, and a slug `neon.ts` already declares needs `--slug`, `--force`, or `--no-add-to-config`. A `separate` template sets its own slugs, so `--slug` only changes its directory; remove the existing declarations from `neon.ts`, or use `--force` or `--no-add-to-config`.

The registry lives in [`registry/`](https://github.com/neondatabase/examples/tree/main/registry) in `neondatabase/examples`. The CLI reads the index from `https://neon.com/functions/registry/registry.json`, which serves `main` through a CDN cache, and falls back to raw GitHub when neon.com is unreachable. Template files are always read from raw GitHub. Index and template JSON are validated against the schemas at `https://neon.com/functions/registry/schemas/` (same GitHub fallback). Set `NEON_FUNCTION_REGISTRY_DIR` to a local checkout to try templates before they are merged, or `NEON_FUNCTION_REGISTRY_URL` to read an index from somewhere else.

## 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.

```bash
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-123
```

`enable` / `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.

```bash
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](#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.

```bash
neon inspect db stalled-queries
neon inspect db stalled-queries --output json
```

## Logs (`logs`)

`neon logs` reads the log records the services on a branch emit — Neon Functions, object storage, and Postgres computes. **Logs are currently available only for projects in `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`.**

Every sub-command resolves the project through the standard chain (`--project-id`, then the `.neon` context file, then a single-project auto-detect), and takes `--branch <id|name>`, defaulting to the project's default branch. `logs query` searches the previous hour by default; `logs field-values` searches the previous six hours. The maximum time window is seven days.

```bash
# The last 30 minutes on the default branch
neon logs query --since 30m

# Postgres compute errors on main, oldest first
neon logs query --branch main --source pg_endpoint --minimum-severity error --sort-order asc

# An explicit window (--start-time replaces --since; --end-time works with either)
neon logs query --start-time 2025-01-01T00:00:00Z --end-time 2025-01-01T01:00:00Z

# One request trace, across every service that took part in it
neon logs query --trace-id 4bf92f3577b34da6a3ce929d0e0e4736

# What the structured filters cannot express: a raw LogQL selection. It replaces
# them, but the window, --limit, --sort-order and --cursor still apply.
neon logs query --since 1h --logql '{entity_type="function"} |= "timeout"'

# Which fields this branch supports, and the values it has actually seen
neon logs fields
neon logs field-values service_name --since 6h --source function
```

A response holds at most `--limit` records (1–1000; default 100). When more matched, table output prints the `--cursor` to repeat the same query with; `--output json|yaml` returns `is_truncated` and `next_cursor` on the envelope instead, so nothing but the payload lands on stdout. Table output shows the common fields; use structured output for the complete records:

```console
$ neon logs query --since 24h --output json
{
  "logs": [
    {
      "timestamp": "2025-01-01T00:00:02.000Z",
      "message": "GET /api/todos 200",
      "source": "function",
      "service_name": "api",
      "severity_text": "INFO",
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "attributes": { "http_status": 200 }
    }
  ],
  "next_cursor": "eyJvZmZzZXQiOjEwMH0",
  "is_truncated": true
}
```

Two combinations are rejected before the request: `--since` with `--start-time`, and `--logql` with any of `--source`, `--service-name`, `--scope-name`, `--minimum-severity`, `--severity-text`, `--body-contains` or `--trace-id`. `--minimum-severity` and `--severity-text` are independent filters and combine with AND. If Neon reports that `--minimum-severity` is unsupported, use `--severity-text` instead; `neon logs field-values severity_text` lists the exact values present on a branch.

`--body-contains` compares a case-sensitive substring against the rendered message. Structured bodies, including object storage records, are rendered as compact JSON, so match the JSON form (for example, `"http_status":200`).

## Profiles

The CLI holds one Neon account by default. A profile adds another, and is a pointer: a
credentials file path, or the sentinel `"keyring"` when the secret is in the OS keyring.

```
~/.config/neon/
├── credentials.json          # this IS the DEFAULT profile
├── credentials.work.json     # created by `neon profile create work`
└── profiles.json             # created once a second profile exists, or DEFAULT is keyring
```

```bash
neon profile create work              # a browser sign-in, or an API key — see below
neon profile create work --keyring    # same, stored in the OS keyring
neon profile list
neon profile remove work
```

```console
$ neon profile list
Profiles
Active  Name     Account         Auth     Credentials          Scope
*       DEFAULT  me@example.com  oauth    credentials.json     account
        work     me@example.com  api key  keyring              account
        ci       org-abc-123     api key  credentials.ci.json  project proj-1
```

`Scope` is what the credential can reach. An OAuth session and an unscoped user API key both
show `account`. A project or org key names that project or org. `Credentials` is the path
relative to the config directory, or `keyring`. A path outside the config directory is shown
absolute. `--output json` keeps the absolute path, and also includes `file` (`ok`, `invalid`,
`missing`, or `unreadable`) and `storage` (`file` or `keyring`). A keyring get of null is
`unreadable`, not missing: the addon cannot tell those apart.

Select one per invocation with `--profile`, or per shell with `NEON_PROFILE`. There is no `profile use` command and nothing is stored about which profile is "current", so what you type is always what runs.

Entries in `profiles.json` are pointers — a path, or the exact string `"keyring"` — and a path may point anywhere, which is how you adopt a directory you already have without moving or re-authenticating anything:

```json
{
  "version": 1,
  "profiles": {
    "DEFAULT": { "credentials": "keyring" },
    "work": { "credentials": "../neonctl-work/credentials.json" }
  }
}
```

An unreadable `profiles.json` fails every command that needs a profile, including a
file-only DEFAULT. That file is the only record of where each account's credentials live,
so the CLI will not guess past it. Fix or delete it.

`neon profile remove` revokes what the profile holds — an OAuth refresh token at the
authorization server, or an API key this CLI minted — rather than only forgetting it locally. A
key you supplied is the exception and stays live, because nothing records its id; the command
says so. It asks for confirmation first, which `--yes` skips; without a terminal on stdin, in
CI or behind a pipe, it refuses rather than prompting into the void. It deletes the credentials
file only when the CLI created it: an adopted path like the one above is unlinked and left on
disk, and the command says so. A keyring profile's OS item is deleted when the store confirms
it is gone. If it cannot, remove still resets the profile and warns that a leftover may remain
in the OS store; it is unused once the profile is gone. Removing the last named profile
deletes `profiles.json` unless DEFAULT itself is keyring — that entry is the only record that
the secret is not in `credentials.json`. `neon profile remove DEFAULT` signs you out.

### Where the secret is stored

The default is a credentials file. A profile uses the OS keyring (macOS Keychain, Windows
Credential Manager, Linux Secret Service) only when its `profiles.json` pointer is `"keyring"`.
That is per profile. Reads never migrate.

```bash
neon login --keyring                         # sign DEFAULT into the OS keyring
neon login --keyring --profile work          # sign work into the OS keyring
neon profile create work --keyring          # create a named profile in the keyring
neon profile remove work --yes              # drop a keyring profile, then create it again as a file
```

File to keyring is `neon login --keyring` or `neon profile create … --keyring`: a new
sign-in, then the previous credential is revoked and the owned file is deleted.
`create` on an existing name always revokes after a successful write. `login` revokes
when it writes to the keyring, including a re-login that follows an existing pointer.
`login` that overwrites a file does not. Keyring to file is `remove`, then create or
log in again. `create` and `login` without `--keyring` follow an existing `"keyring"`
pointer, so a keyring profile cannot leave the OS store until `remove` succeeds.

`--api-key` and `NEON_API_KEY` skip both stores. A GitHub-release `neon-<platform>`
binary recognizes a `"keyring"` pointer and refuses: it cannot load the OS keyring addon.
Use the npm-installed `neon`. Older releases treat the sentinel as a relative path.

A `"keyring"` pointer whose OS item cannot be read is not treated as signed-out. Commands that
would otherwise open a browser fail: could not read the OS keyring item. Unlock it and
retry, or run `neon login --profile DEFAULT`. To reset the profile: `neon profile remove DEFAULT --yes`.
A missing `credentials.json` with no `profiles.json` is still
signed-out, and those commands start OAuth.

### A profile holds either a sign-in or an API key

`neon profile create` makes a profile, and how you call it decides which kind of credential it holds. A key-backed profile is what you want for an agent, a shared machine, or anything that must never be interrupted by a browser:

```bash
neon profile create work                            # sign in; replaces work if it already exists
neon profile create work --keyring                  # same, stored in the OS keyring
neon profile create work --api-key "$KEY"           # store a key you already have
echo "$KEY" | neon profile create work --api-key -  # or pipe it, keeping it out of argv
neon profile create ci --mint                       # sign in once, keep only a minted key
neon profile create ci --mint --org-id org-abc-123  # minted for an organization
neon profile create ci --mint --project-id proj-1   # minted for one project only
neon profile rotate-key work                        # mint a replacement, revoke the old one
```

Replacing a profile revokes the credential it held, so a key this CLI minted stops working
everywhere it was pasted, and an OAuth session is signed out. To keep a working profile and
swap only its key, use `rotate-key`.

`create` and `rotate-key` print the profile they wrote, so an agent needn't follow up with
`list`. Under `--output json` that is a record, and it never carries the secret:

```console
$ neon profile create ci --mint --org-id org-abc-123 --output json
{"name":"ci","account":"org-abc-123","auth":"api key","scope":"org org-abc-123","keyId":3239771,"credentials":"/home/me/.config/neon/credentials.ci.json"}
```

One flag takes the key, because the shell already covers the variations: `--api-key "$(cat
~/keys/work)"` reads a file and `--api-key "$KEY"` takes a variable. Those put the key in the
process arguments, where `ps` and shell history can see it, so `--api-key -` reads it from stdin
instead — the usual convention for a piped value. `--mint` avoids the question entirely, because
the key never leaves the CLI.

**A profile is one kind or the other, never both.** `type` in the credentials file states which:

```json
// oauth: what a plain `create` (or `neon login --profile`) writes. An absent `type` means this.
{ "access_token": "…", "refresh_token": "…", "expires_at": 1786…, "user_id": "…" }

// api_key: what `--api-key` writes
{ "type": "api_key", "api_key": "napi_…", "user_id": "…" }

// api_key from `--mint --org-id`, which records the scope it was issued at
{ "type": "api_key", "api_key": "napi_…", "key_id": 123, "org_id": "org-…" }
```

Nothing is carried over when a profile is replaced, so one profile can never hold two credentials — or two different accounts. The secret stays in the credentials file or the OS keyring and never goes into `profiles.json`, so listing profiles cannot leak one. Files are written owner-only through a temporary file and a rename, which also repairs the permissions of a file created too permissively.

`--mint` is the one to reach for. It signs you in through the browser once, mints a key with that session, stores only the key, and signs the session back out — so afterwards nothing about the profile can open a browser, and no half-forgotten login is left behind. `--org-id` and `--project-id` narrow what the minted key can reach, exactly as they do on [`neon api-keys create`](#api-keys); a project-scoped key cannot create projects, mint keys, or read any other project.

Every key is verified against the API before it is stored, and the account it belongs to is recorded so `profile list` can show it. Only a real API key is accepted: an OAuth access token authenticates today and then expires with nothing to refresh it.

`rotate-key` mints at the scope the profile already has — replacing an org key with an account key would quietly widen everything it reaches — and stores the new key before revoking the old one, so a failed write leaves the old key working.

One thing it cannot do: **an organization key cannot mint its own replacement.** Neon only accepts a personal credential when creating organization keys, so rotating an org- or project-scoped profile means signing in again — `neon profile create ci --mint --org-id org-abc-123`. `rotate-key` checks this before minting and says so, rather than letting the API answer with a rule you had no reason to expect.

Two things the CLI cannot do for a key you supplied rather than minted. It cannot revoke it, because `GET /api_keys` exposes no prefix and a stored secret cannot be matched to a listing entry, so both `rotate-key` and `profile remove` say the old key is still live and point you at `neon api-keys list`. For a key you supplied it records the organization the API reports, but cannot know whether that key was narrowed to a single project — so `rotate-key` will not suggest an organization-wide replacement without telling you to check `neon api-keys list` first.

If a stored key stops working there is nothing to refresh, so recovery is one browser sign-in: `neon profile create work --mint`.

### Which credential an invocation uses

An explicit flag always beats an environment variable:

| Given | What runs |
| --- | --- |
| `--api-key` and `--profile` | neither — contradictory, so the command fails |
| `--api-key` and `NEON_PROFILE` | the flag's key |
| `--profile` and `NEON_API_KEY` | the profile |
| `NEON_API_KEY` and `NEON_PROFILE` | the key, and the ignored profile is named in a warning |
| `--profile` or `NEON_PROFILE` alone | that profile |
| nothing | `DEFAULT` |

Passing both flags fails rather than picking a winner: `--api-key` supplies a credential and `--profile` selects a stored one, so there is no reading of the command that makes both true.

When both are only environment variables the key wins, which keeps a CI pipeline that injects `NEON_API_KEY` working even if a `NEON_PROFILE` leaks into the environment — but the disregarded profile is named on stderr rather than passed over silently.

`neon login` and the `profile` subcommands are outside all of this, because they read the same flags to mean something else: `neon login --profile work` names where to write a credential, and `neon profile create work --api-key …` names one to store.

`neon init` forwards `--profile` and `--config-dir` to agent and config children and uses them directly for its link flow. An explicit `--api-key` is passed to authenticated children through `NEON_API_KEY`, not argv.

## API passthrough (`api`)

`neon api` sends an authenticated request to any Neon API route. `neon api --list` catalogs the routes. `neon api <path> --describe` prints the OpenAPI request shape for one operation so you can fill `-F` and `-Q` without guessing. Body field names are dotted to match `-F`.

```bash
neon api --list
neon api /projects --describe
neon api /projects -X POST --describe -o json
neon api /projects/{project_id}/branches -X POST --describe
neon api /projects/foo-bar-123/branches -X POST -F branch.name=dev
```

`--describe` does not send the selected API request. It uses the same CLI authentication as `--list`. `-X` defaults to GET; if that method is missing, the error names the methods that exist.

## API keys (`api-keys`)

```bash
neon api-keys list                                        # your account's keys
neon api-keys list --org-id org-…                         # an organization's, with scope shown

neon api-keys create --name ci                            # account key
neon api-keys create --name ci    --org-id org-…          # organization key
neon api-keys create --name agent --project-id frosty-…   # can access only that project

neon api-keys revoke <id> [--org-id org-…]
```

The key is returned once, on create, and cannot be retrieved again. It prints on its own line below the metadata, so it can be selected in one gesture regardless of terminal width — and `… | tail -1` on stdout yields exactly the key, since both notices go to stderr.

```console
$ neon api-keys create --name agent --project-id proj-in-org
API key
Id       303
Name     agent
Project  proj-in-org

napi_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
WARNING: Store this key now: it is not shown again.
Limited to proj-in-org: it cannot create projects, mint API keys, or read any other project. It can still change and delete everything inside that project.
```

`--org-id` and `--project-id` are mutually exclusive. A project-scoped key *is* an organization key, and its organization is looked up from the project rather than chosen separately. With neither flag you get an account key.

### Project-scoped keys

A key created with `--project-id` bounds what it can reach to one project. Verified against a real scoped key:

| Attempt | Result |
| --- | --- |
| Read and write its own project | works |
| Read any other project | `project not found` — not even an existence oracle |
| `neon projects create` | `project-scoped keys are not allowed to create projects` |
| `neon projects list` | refused |
| `neon api-keys create` / `list` | refused (true of any organization key, not only scoped ones) |
| `neon orgs list` | **works** — it can see the id, name and handle of the organization it belongs to |
| Anything else about that organization (`GET /organizations/{id}`, members) | refused |

It is **not** read-only. Inside its one project it can do everything the API allows, including deleting branches and the project itself — `neon deploy` working at all is proof of that. What it bounds is *reach*, which is what lets you hand it to an agent or a CI job without handing over your account:

```bash
neon link --project-id frosty-…       # once, as yourself — writes .neon
NEON_API_KEY=napi_… neon deploy       # then the agent, reaching only that project
```

`neon link` needs `--project-id` explicitly when using a scoped key: the interactive picker lists your projects, which a scoped key cannot do.

`api-keys` deliberately ignores the `.neon` context file, unlike every other project command. Otherwise `neon api-keys create --name ci` inside a linked directory would silently mint a key scoped to that project instead of the account key you asked for. How far a credential reaches comes only from a flag you typed.

### Seeing what is scoped

```console
$ neon api-keys list --org-id org-7
API keys in org-7
Id   Name      Project         Created At            Last Used At          Last Used From Addr
301  scoped    proj-in-org     2026-01-02T00:00:00Z
302  org-wide  (all projects)  2026-01-03T00:00:00Z  2026-02-03T00:00:00Z  203.0.113.9
```

`last_used_at` and `last_used_from_addr` are how you spot a key worth revoking.

## Commands

| Command                                                                    | Subcommands                                                                                                  | Description                        |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------- |
| [login](https://neon.com/docs/reference/cli-auth) (`auth`)                 |                                                                                                              | Authenticate                       |
| claim (`claimable`)                                                        | `create`, `status`, `accept`, `list`, `delete`                                                               | Manage claimable projects          |
| profile                                                                    | `list`, `create`, `rotate-key`, `remove`                                                                     | Manage named sets of credentials   |
| api-keys                                                                   | `list`, `create`, `revoke`                                                                                   | Manage API keys                    |
| api                                                                        |                                                                                                              | Call any Neon API route            |
| [projects](https://neon.com/docs/reference/cli-projects)                   | `list`, `create`, `update`, `delete`, `get`                                                                  | Manage projects                    |
| [ip-allow](https://neon.com/docs/reference/cli-ip-allow)                   | `list`, `add`, `remove`, `reset`                                                                             | Manage IP Allow                    |
| [me](https://neon.com/docs/reference/cli-me)                               |                                                                                                              | Show current user                  |
| [branches](https://neon.com/docs/reference/cli-branches)                   | `list`, `create`, `rename`, `add-compute`, `set-default`, `set-expiration`, `delete`, `get`                  | Manage branches                    |
| [databases](https://neon.com/docs/reference/cli-databases)                 | `list`, `create`, `delete`                                                                                   | Manage databases                   |
| function                                                                   | `deploy`, `list`, `get`, `delete`, `domains list`, `domains register`, `domains delete`                      | Manage Neon Functions              |
| [roles](https://neon.com/docs/reference/cli-roles)                         | `list`, `create`, `delete`                                                                                   | Manage roles                       |
| [operations](https://neon.com/docs/reference/cli-operations)               | `list`                                                                                                       | Manage operations                  |
| logs                                                                       | `query`, `fields`, `field-values`                                                                            | Query branch logs (Beta)           |
| inspect                                                                    | `db stalled-queries`                                                                                         | Inspect Postgres diagnostics       |
| snapshots                                                                  | `list`, `get`, `create`, `update`, `delete`, `restore`, `finalize`, `schedule get`, `schedule set`           | Manage snapshots                   |
| triggers                                                                   | `list`, `get`, `create`, `update`, `enable`, `disable`, `delete`                                             | Manage function triggers           |
| credentials                                                                | `list`, `create`, `reveal`, `rotate`, `revoke`                                                               | Manage branch credentials          |
| [connection-string](https://neon.com/docs/reference/cli-connection-string) |                                                                                                              | Get connection string              |
| [psql](https://neon.com/docs/reference/cli-psql)                           |                                                                                                              | Connect to a database via psql     |
| set-context                                                                |                                                                                                              | Deprecated; use `link`             |
| env                                                                        | `pull`                                                                                                       | Manage a branch's env vars         |
| checkout                                                                   |                                                                                                              | Pin a branch in `.neon`            |
| diff                                                                       |                                                                                                              | Git-style schema diff vs a branch  |
| [link](https://neon.com/docs/reference/cli-link)                           |                                                                                                              | Link a directory to a project      |
| open                                                                       |                                                                                                              | Open the linked project in Console |
| config                                                                     | `init`, `status`, `plan`, `apply`                                                                            | Drive a branch from `neon.ts`      |
| deploy                                                                     |                                                                                                              | Alias for `config apply`           |
| bootstrap                                                                  |                                                                                                              | Scaffold a template, then agent tooling and link |
| init                                                                       |                                                                                                              | In place: agents, link, neon.ts       |
| mcp                                                                        |                                                                                                              | Install the Neon MCP server         |
| plugins                                                                    |                                                                                                              | Install the Neon plugin             |
| skills                                                                     | `update`                                                                                                     | Install Neon agent skills           |
| ask                                                                        |                                                                                                              | Ask a question about Neon          |
| feedback                                                                   |                                                                                                              | Send feedback to Neon              |
| bucket                                                                     | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects   |
| [completion](https://neon.com/docs/reference/cli-completion)               |                                                                                                              | Generate a completion script       |

## Global options

Global options are supported with any Neon CLI command.

| Option                      | Description                                                 | Type    | Default                        |
| :-------------------------- | :---------------------------------------------------------- | :------ | :----------------------------- |
| [-o, --output](#output)     | Set the Neon CLI output format (`json`, `yaml`, or `table`) | string  | table                          |
| [--config-dir](#config-dir) | Path to the Neon CLI configuration directory                | string  | `/home/<user>/.config/neon`    |
| [--profile](#profile)       | Named credentials to use, from `profiles.json`              | string  | `DEFAULT`                      |
| [--api-key](#api-key)       | Neon API key                                                | string  | ""                             |
| [--analytics](#analytics)   | Manage analytics                                            | boolean | true                           |
| [-v, --version](#version)   | Show the Neon CLI version number                            | boolean | -                              |
| [-h, --help](#help)         | Show the Neon CLI help                                      | boolean | -                              |

- <a id="output"></a>`-o, --output`

  Sets the output format. Supported options are `json`, `yaml`, and `table`. The default is `table`. Table output may be limited. The `json` and `yaml` output formats show all data.

  ```bash
  neon me --output json
  ```

- <a id="config-dir"></a>`--config-dir`

  Specifies the path to the `neon` configuration directory, which holds the `credentials.json` written by `neon login` and, when a second profile exists or DEFAULT is keyring, `profiles.json`. The default is `$XDG_CONFIG_HOME/neon`, or `~/.config/neon`; run `neon --help` to see the resolved path. This option is only necessary if you keep your configuration somewhere else.

  The directory was called `neonctl` before the CLI was renamed. An existing one is still read, and is used **in place** — nothing is moved or copied, so there is never a second credentials file to go stale. A directory you pass explicitly is used exactly as given and never falls back to the legacy name, so pointing a CI run at a scratch directory cannot pick up local credentials.

  ```bash
  neon projects list --config-dir /home/dtprice/.config/neon
  ```

- <a id="profile"></a>`--profile`

  Selects a named set of credentials, for holding more than one Neon account at a time. A profile is a pointer to a credentials file, recorded in `profiles.json` next to it.

  ```bash
  neon login --profile work        # create it, or sign in again
  neon profile list               # names, accounts, and where each one's credentials live
  neon projects list --profile work
  NEON_PROFILE=work neon projects list
  ```

  Precedence is `--profile`, then `NEON_PROFILE`, then `DEFAULT`. `DEFAULT` is plain `credentials.json`, so an install with a single account needs no `profiles.json` and behaves exactly as before. See [Profiles](#profiles) to list or remove them.

- <a id="api-key"></a>`--api-key`

  Specifies your Neon API key. You can authenticate using a Neon API key when running a Neon CLI command instead of using `neon login`. For information about obtaining an Neon API key, see [Authentication](https://neon.com/docs/reference/api/get-started), in the _Neon API Reference_.

  ```bash
  neon <command> --api-key <neon_api_key>
  ```

- <a id="analytics"></a>`--analytics`

  Analytics are enabled by default to gather information about the CLI commands and options that are used by our customers. This data collection assists in offering support, and allows for a better understanding of typical usage patterns so that we can improve user experience. Neon does not collect user-defined data, such as project IDs or command payloads, except the question passed to `neon ask --prompt` and the query passed to `neon docs search`. Each successful command also reports the Neon agent skills recorded in the user-level skills lock and in the current directory's `skills-lock.json`: the skill names, their lock hashes, and whether each is behind its source, read from local files without a network request. The Neon search service records each `neon docs search` query with the CLI version and the detected coding agent; with `--no-analytics` it records nothing. When the CLI fails on an internal bug (a JavaScript `TypeError`, `ReferenceError`, `RangeError`, or `SyntaxError`), it also sends that error to Neon's Sentry: the error type, the message only when it is a standard JavaScript message that names nothing but code (any other message is replaced), the stack trace with file paths cut to the package (`neon/dist/...`), the CLI and Node.js versions, and whether it ran in CI or a coding agent. To opt-out of analytics data collection and error reports, specify `--no-analytics` or `--analytics false`.

- <a id="version"></a>`-v, --version`

  Shows the Neon CLI version number.

  ```bash
  $ neon --version
  1.15.0
  ```

- <a id="help"></a>`-h, --help`

  Shows the `neon` command-line help. You can view help for `neon`, a `neon` command, or a `neon` subcommand, as shown in the following examples:

  ```bash
  neon --help

  neon branches --help

  neon branches create --help
  ```

## Contribute

This repo uses [pnpm](https://pnpm.io). The required version is pinned in `.tool-versions` and `package.json`'s `packageManager` field. The simplest way to get the right version is [mise](https://mise.jdx.dev): `mise install` reads `.tool-versions` and installs Node and pnpm. Alternatives: `npm install -g pnpm@9.15.9`, or [Corepack](https://nodejs.org/api/corepack.html) (`corepack enable pnpm`).

To run the CLI locally, execute the build command after making changes:

```shell
pnpm install
pnpm run build
```

To develop continuously:

```shell
pnpm run watch
```

To run commands from the local build, replace the `neon` command with `node dist`; for example:

```shell
node dist branches --help
```

### Embedded psql tests

The embedded TypeScript psql implementation has its own conformance test suite that runs the same scripts against the embedded psql and a reference `psql` binary, then diffs the output.

```shell
bun run test:conformance         # run against $PSQL_BINARY (defaults to the system psql)
bun run test:conformance:matrix  # run across PG 14/15/16/17/18 locally (requires Docker)
```

## Releasing

Maintainers: see the repository's [release skill](../../.claude/skills/release/SKILL.md)
for the Changesets bump and external publish flow.

---
_Source: https://npm.io/package/neon · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
