Skip to main content

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 login behaviour 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
Recent versions are hidden by default on pnpm and Yarn

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.

SettingUnitDefaultExempt your own packages
pnpmminimumReleaseAgeminutes1440 (1 day) since pnpm 11minimumReleaseAgeExclude
YarnnpmMinimalAgeGateminutes, accepts 1d1d since Yarn 4.15npmPreapprovedPackages, or per scope
npmmin-release-agedaysoff (null), still in npm 12min-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.

Details and how to exempt a scope: pnpm Β· yarn Β· npm.

The short version​

The same settings side by side, if you already know what you are looking for:

pnpmnpmYarn 4
Configuration file.npmrc.npmrc.yarnrc.yml
Registry for everythingregistry=<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 inpnpm login --registry <url>npm login --registry <url>yarn npm login --auth-type=legacy
Publishpnpm publish --registry <url>npm publish --registry <url>yarn npm publish
One-off, no configpnpm 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:

.npmrc
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:

.npmrc
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:

package.json
{
"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.