Using a private registry
Pointing a client at Verdaccio is the same four steps everywhere β point it at the registry, authenticate, publish, and keep the lockfile from undoing it. What changes is which file holds the setting and what the commands are called.
Per clientβ
Start here. Each page covers registry configuration, authentication, publishing, two-factor and the problems specific to that client:
- pnpm β including the release-age gate that hides packages you have just published
- npm β including
npm loginbehaviour across versions and mixed registries in a lockfile - yarn β classic and modern, plus the plugins that add the registry commands Yarn 4 does not ship
- bun
Both clients refuse versions published in the last day, as a supply-chain precaution. Against a private registry that usually means the package you just published cannot be installed yet β and the error says the version does not exist, not that it was filtered.
| Setting | Unit | Default | Exempt your own packages | |
|---|---|---|---|---|
| pnpm | minimumReleaseAge | minutes | 1440 (1 day) since pnpm 11 | minimumReleaseAgeExclude |
| Yarn | npmMinimalAgeGate | minutes, accepts 1d | 1d since Yarn 4.15 | npmPreapprovedPackages, or per scope |
| npm | min-release-age | days | off (null), still in npm 12 | min-release-age-exclude |
Note the units differ: min-release-age=1 is one day in npm, while 1 in pnpm or Yarn
is one minute. Copying a value between them does not do what it looks like.
The short versionβ
The same settings side by side, if you already know what you are looking for:
| pnpm | npm | Yarn 4 | |
|---|---|---|---|
| Configuration file | .npmrc | .npmrc | .yarnrc.yml |
| Registry for everything | registry=<url> | registry=<url> | npmRegistryServer: <url> |
| Registry for one scope | @scope:registry=<url> | @scope:registry=<url> | npmScopes: β npmRegistryServer |
| Authentication | //host/:_authToken=<token> | //host/:_authToken=<token> | npmAuthToken under npmRegistries: |
| Log in | pnpm login --registry <url> | npm login --registry <url> | yarn npm login --auth-type=legacy |
| Publish | pnpm publish --registry <url> | npm publish --registry <url> | yarn npm publish |
| One-off, no config | pnpm install --registry <url> | npm install --registry <url> | not supported β use the config |
Bun reads .npmrc too; see bun.
Yarn 4's yarn npm login assumes the browser-based flow, which a self-hosted registry does
not serve unless the webLogin flag is on β hence --auth-type=legacy above. Yarn 4 also
ships fewer registry commands than npm; the yarn page covers
the plugins that add the missing ones.
Which approach to pickβ
Route only your scope, and leave everything else on npmjs:
registry=https://registry.npmjs.org/
@my-company:registry=http://localhost:4873/
This is the safer default for a team. Public packages are unaffected, and if the registry is
down nobody is blocked from installing lodash. The trade-off is that you get no caching of
public packages and no single place to apply access rules.
Route everything through Verdaccio, letting it proxy npmjs with an uplink:
registry=http://localhost:4873/
One registry to configure, one place to enforce policy, and a cache of everything your team installs β which also keeps builds working when npmjs is unreachable. The trade-off is that the registry is now on the critical path of every install.
Per command, for a one-off check, with --registry. It does not persist, and it does not
carry over to a publish you run afterwards.
Stop a private package reaching npmjsβ
Worth doing whichever approach you chose, because it does not depend on anyone's .npmrc:
{
"publishConfig": {
"registry": "http://localhost:4873/"
}
}
A publish now goes to that registry regardless of local configuration. For the stronger version of this β making the package unpublishable anywhere else β see protecting packages.