pnpm
Verified against pnpm 10, 11 and 12.
Quick start
export VLT_TOKEN=<your token>
pnpm config set registry https://registry.vlt.io/acme/npm/ --location=projectpnpm config set @acme:registry https://registry.vlt.io/acme/main/ --location=projectThen add auth by logging in through the browser:
pnpm login --registry=https://registry.vlt.io/acme/npm/pnpm login --registry=https://registry.vlt.io/acme/main/ --scope=@acmeNow pnpm install works.
The config file
pnpm reads registry and auth settings from .npmrc, same keys as npm.
Commit the routing only, with no credentials:
registry=https://registry.vlt.io/acme/npm/@acme:registry=https://registry.vlt.io/acme/main/Registry and auth keys belong in .npmrc; every other pnpm setting
now lives in pnpm-workspace.yaml.
${VLT_TOKEN} does not work in a project .npmrc
Unlike npm, yarn, bun and deno, pnpm deliberately ignores ${...}
placeholders in a repository .npmrc for registry, proxy and
credential values. A file containing
//registry.vlt.io/acme/npm/:_authToken=${VLT_TOKEN} sends no
Authorization header at all — you get a 401 that looks like a bad
token, with no warning explaining why.
This is a supply-chain fix (a malicious repo could otherwise redirect your token to a registry it controls), landed in pnpm 11.5.3, so it applies to every current version. Use one of the two options below instead.
Which option is available depends on the version, and there is one unlucky release with neither:
| pnpm version | ${VLT_TOKEN} in .npmrc | pnpm_config_… env var |
|---|---|---|
| 11.5.2 and older | works | not supported |
| 11.5.3 | blocked | not supported |
| 11.6.0 – 11.26.0 | blocked | works |
| 12.x | blocked | works |
So on the pinned 11.26.0 above, use pnpm login or the environment
variable — never a placeholder in the file.
Option 1 — log in (recommended for laptops)
pnpm login --registry=https://registry.vlt.io/acme/npm/pnpm login --registry=https://registry.vlt.io/acme/main/ --scope=@acmeBrowser-based, and the credential is stored in your global pnpm config rather than in the repo.
Option 2 — pass the token as an environment variable (recommended for CI)
pnpm accepts any config key as a pnpm_config_-prefixed variable,
which sidesteps the placeholder restriction entirely:
env "pnpm_config_//registry.vlt.io/acme/npm/:_authToken=$VLT_TOKEN" pnpm installTwo notes:
- Use
envrather thanexport— the variable name contains/,:and., which the shell will not accept in an assignment. - Needs pnpm 11.6.0 or newer. On older versions the variable is
ignored and you get
ERR_PNPM_FETCH_401 … No authorization header was set for the request.
Install from the npm mirror
pnpm add abbrevTo move an existing project onto the mirror:
rm -rf pnpm-lock.yaml node_modulespnpm installInstall from your main registry
pnpm add @acme/my-packagePublic packages
Public packages on main install without a token — the registry line
alone is enough, with no auth line:
@acme:registry=https://registry.vlt.io/acme/main/Publish to main
{ "name": "@acme/my-package", "version": "1.0.0", "publishConfig": { "registry": "https://registry.vlt.io/acme/main/" }}pnpm publishpublishConfig.registry decides the destination, and in pnpm it is
the strongest setting — it beats @acme:registry and even an
explicit --registry flag, which is the weakest of the four. (npm
resolves these in a different order, so don’t assume a publish script
behaves identically under both.) Add --access public for a package
anyone can install.
Two flags you will probably want:
--no-git-checks— pnpm refuses to publish unless the git tree is clean and you are on the publish branch. Verified: an uncommitted file fails withERR_PNPM_GIT_UNCLEAN.--dry-run— pack and report without uploading.
With a personal token, every publish returns a one-time-password
challenge with a vlt.io URL — open it, approve, re-run. A service
token skips the prompt and is what you want in CI; see
CI & automation.
Log in
pnpm login --registry=https://registry.vlt.io/acme/main/ --scope=@acmeThis uses the browser login flow and prints an authentication URL.
--scope also records the scope-to-registry mapping for you. In
current pnpm the credential is written to your global pnpm config, not
to .npmrc.
pnpm token is not implemented in pnpm — it reports
Not implemented in pnpm. Use the npm CLI directly. Manage tokens at
vlt.io or with npm token.
Gotchas
- pnpm 12 + the npm mirror is broken. See the note at the top.
${VAR}in a project.npmrcis ignored, silently. See above.pnpm config setwrites to your machine-global config by default. Always pass--location=project, or credentials end up in your user config unexpectedly.--locationaccepts onlyprojectandglobal— there is nouser.always-authno longer exists in pnpm. Setting it produces an unrecognized-setting warning and does nothing; it isn’t needed.- Trailing slashes must match between the registry URL and the auth line.
--npmrc-auth-file(alias--userconfig) points pnpm at a different.npmrcfor auth. That file is treated as trusted, so${VAR}expansion does work there — only use it in environments that build trusted repositories.