Using it from npm

Create a repository with a format of npm. The format is fixed once the repository exists, because nothing migrates between formats.

Pointing npm at it

For a whole project:

registry=https://repo.example.com/repository/npm-hosted/
//repo.example.com/repository/npm-hosted/:_authToken=cnx_...

For one scope, leaving everything else on the public registry:

@your-scope:registry=https://repo.example.com/repository/npm-hosted/
//repo.example.com/repository/npm-hosted/:_authToken=cnx_...

The _authToken key is the registry URL with the scheme stripped, and the trailing slash matters. A public repository needs no token to read.

Logging in

npm login --auth-type=legacy --registry=https://repo.example.com/repository/npm-hosted/

The legacy flag is required. npm’s browser based login posts to an endpoint this server does not implement, which is deliberate: leaving it unrouted makes npm fall back to the flow that works.

An API token from the Account page does the same job without logging in.

What works

npm publish, npm install, npm view, npm dist-tag, npm search, npm deprecate and npm unpublish, plus npm token for creating, listing and revoking tokens.

A published version is immutable regardless of the repository’s allowRedeploy setting. npm clients cache by integrity, so a replaced tarball is never picked up, and pretending otherwise would only produce confusing failures.

The repository policy maps onto semver: release rejects prereleases, snapshot accepts only prereleases, and mixed takes both.

Storage layout

Packages are stored under their coordinates rather than npm’s - convention, so @types/node@20.1.0 lives at @types/node/20.1.0/. That is what lets browsing, search, storage insights and per version deletion be the same code paths Maven uses.

A scoped package maps to a namespace and a name, so @types/node is namespace types, name node. Searching for types finds every @types/* package.