npm 22 ๐ Configuration Management (.npmrc File & Registry Config)
npm’s configuration system is a layered hierarchy of files, environment variables, and command-line flags. The .npmrc file is the persistent layer: an INI-formatted list of key = value pairs that npm reads at startup. The file exists at four scopesโproject, user, global, and builtinโand the values resolve in priority order, with the most specific scope winning. Registry configuration is the most common use of the file: pointing npm at a private registry, scoping packages to specific registries, and storing authentication tokens that are scoped to the host that issued them.
Key point: Authentication tokens in .npmrc must be scoped to a specific registry host using the //host/:_authToken syntax. An unscoped _authToken is not only ineffective for custom registries but can be sent to the wrong host, which is why npm requires the scope.
Why .npmrc exists
The per-project problem. A project that uses a private registry needs npm to resolve packages from that registry without changing the developer’s global configuration. The project-level .npmrc in the repository root provides that: npm reads it when running in that directory and ignores it everywhere else . The file can be committed to version control if it contains no secrets, or gitignored if it does.
The credential isolation problem. A developer who works with both the public registry and a company registry needs different credentials for each. The scoped auth syntax (//registry.company.com/:_authToken=...) ensures that the token for the company registry is only sent to that host. The public registry’s token, if any, is scoped to registry.npmjs.org and is never sent to the company registry . This prevents a compromised private registry from harvesting credentials intended for the public one.
The precedence problem. Configuration values can come from six sources, and the precedence determines which value actually takes effect. A registry setting in the project .npmrc overrides the same setting in the user .npmrc, which overrides the global config, which overrides the builtin defaults. A command-line flag overrides everything. Understanding the precedence is the difference between a configuration that works and one that appears correct but is silently overridden .
a. The Four Config Files and Their Precedence
npm reads four .npmrc files, each with a different scope. The files are INI-formatted, with key = value pairs, and comments begin with ; or # .
| File | Location | Scope | Use |
|---|---|---|---|
| Project | .npmrc in project root | Current project only | Registry, scoped packages |
| User | ~/.npmrc | Current user, all projects | Auth tokens, personal settings |
| Global | $PREFIX/etc/npmrc | All users on the machine | System-wide defaults |
| Builtin | npm installation directory | npm itself | Distribution defaults |
The precedence order, from highest to lowest, is: command-line flags, environment variables (npm_config_*), project .npmrc, user .npmrc, global .npmrc, builtin .npmrc, and finally npm’s hardcoded defaults . The project file is not read in global mode (npm install -g), and it is not published with the package, so it cannot force a consumer to use a specific registry .
# Example .npmrc
registry=https://registry.npmjs.org/
@mycompany:registry=https://npm.company.com/
//npm.company.com/:_authToken=${NPM_TOKEN}
save-exact=true
fund=false
The ${NPM_TOKEN} syntax substitutes an environment variable. This is the recommended pattern for CI: the token lives in the CI secret store, and the .npmrc file contains only the reference. The ? suffix (${VAR?}) forces the value to an empty string if the variable is undefined, which prevents an unset variable from leaving the literal ${VAR} string in the configuration .
b. Registry Configuration and Scoped Packages
The registry key sets the default registry for unscoped packages. A scoped registry key (@scope:registry) sets the registry for packages whose names begin with that scope. This allows a project to install public packages from npmjs.org and private packages from a company registry without switching the default .
# Default registry for unscoped packages
registry=https://registry.npmjs.org/
# Scoped registry for @mycompany packages
@mycompany:registry=https://npm.company.com/
When npm resolves @mycompany/utils, it uses the @mycompany:registry value. When it resolves lodash, it uses the default registry value. The scope is the mechanism that makes multi-registry workflows possible without environment switching .
For publishing, the same scope applies. A package named @mycompany/utils is published to the @mycompany:registry when npm publish runs. If no scoped registry is configured, the package is published to the default registry, which is the public npm registry unless overridden .
A project that should never be published to the public registry can set "private": true in package.json, which prevents publication entirely, or "publishConfig": {"registry": "..."} to force publication to a specific registry .
c. Authentication Tokens and Scoping
Authentication configuration must be scoped to a registry. The keys _authToken, _auth, username, _password, cafile, certfile, and keyfile are all subject to this rule . The scoping syntax uses a URI fragment that matches the registry’s host and optional path.
# Token for the public registry
//registry.npmjs.org/:_authToken=npm_xxxxxxxxxxxx
# Token for a company registry at a specific path
//npm.company.com/unique/path/:_authToken=company_token_xxxx
# Token for a different company registry
//registry.other.com/:_authToken=other_token_xxxx
The syntax is //host/path/:_authToken=value. The // prefix and the trailing /: are required. The path component is optional if the token applies to the entire host. When npm makes a request to a registry, it matches the request URL against the scoped entries and sends the corresponding token. A token scoped to //npm.company.com/ is never sent to registry.npmjs.org .
The npm config set command can write these entries, but the shell escaping can be tricky because of the // prefix. The -- separator is required to prevent the shell from interpreting the key as a flag .
# Set a scoped token via npm config
npm config set -- '//npm.company.com/:_authToken' "${NPM_TOKEN}"
# Verify the effective configuration
npm config get registry
npm config get @mycompany:registry
Complete Example Session
# ============================================
# PART 1: PROJECT .NPMRC
# ============================================
# .npmrc in the project root
registry=https://registry.npmjs.org/
@mycompany:registry=https://npm.company.com/
//npm.company.com/:_authToken=${NPM_TOKEN}
save-exact=true
engine-strict=true
# ============================================
# PART 2: USER .NPMRC
# ============================================
# ~/.npmrc
# Personal token for the public registry (if publishing)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
# Do not commit this file
# ============================================
# PART 3: ENVIRONMENT VARIABLE SUBSTITUTION
# ============================================
# The token is read from the environment
export NPM_TOKEN="npm_xxxxxxxxxxxx"
# The .npmrc contains ${NPM_TOKEN}, not the literal token
# ============================================
# PART 4: VERIFY THE EFFECTIVE CONFIG
# ============================================
npm config get registry
# https://registry.npmjs.org/
npm config get @mycompany:registry
# https://npm.company.com/
# ============================================
# PART 5: VERIFY THE PRECEDENCE
# ============================================
# A command-line flag overrides the file
npm config get registry --registry=https://other.registry.com/
# https://other.registry.com/
# ============================================
# PART 6: SCOPED AUTH TOKEN VIA CLI
# ============================================
npm config set -- '//npm.company.com/:_authToken' "${NPM_TOKEN}"
# ============================================
# PART 7: CI PATTERN
# ============================================
# In CI, the token is injected from the secret store
# The .npmrc in the repo contains only ${NPM_TOKEN}
# No secret is committed
# ============================================
# PART 8: GITIGNORE THE SECRET
# ============================================
# .gitignore
# .npmrc
# But commit .npmrc.example with placeholders
The eight parts trace the configuration from the project file through the environment variable substitution to the effective value. Part 5 shows the precedence: the command-line flag overrides the file.
Quick Reference
Config File Precedence
| Priority | Source | Example |
|---|---|---|
| 1 (highest) | CLI flags | --registry=... |
| 2 | Environment | npm_config_registry=... |
| 3 | Project .npmrc | <repo>/.npmrc |
| 4 | User .npmrc | ~/.npmrc |
| 5 | Global .npmrc | $PREFIX/etc/npmrc |
| 6 | Builtin | npm installation |
| 7 (lowest) | Defaults | Hardcoded |
Common Keys
| Key | Purpose |
|---|---|
registry | Default registry URL |
@scope:registry | Registry for a scope |
//host/:_authToken | Token for a host |
save-exact | Pin exact versions |
engine-strict | Enforce engines field |
fund | Show funding messages |
audit | Enable security audit |
strict-ssl | Verify TLS certificates |
Auth Scoping
| Entry | Matches |
|---|---|
//registry.npmjs.org/:_authToken=X | Only npmjs.org |
//npm.company.com/:_authToken=Y | Entire company host |
//npm.company.com/path/:_authToken=Z | Only that path |
Best Practices
โ Do This:
# Use environment variable substitution for tokens
//registry.company.com/:_authToken=${NPM_TOKEN} # โ
# Scope tokens to the exact host
//npm.company.com/:_authToken=${NPM_TOKEN} # โ
# Verify the effective value before debugging
npm config get registry # โ
โ Don’t Do This:
# Commit a literal token to .npmrc
//registry.npmjs.org/:_authToken=npm_live_xxxxxxxx # โ
# Use an unscoped token
_authToken=xxxxx # not sent to custom registries; may leak # โ
# Assume the project .npmrc applies in global mode
npm install -g # project .npmrc is not read # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Token sent to wrong host | Unscoped _authToken | Scope with //host/:_authToken |
| Registry setting ignored | Project file not read (global mode) | Use user or global .npmrc |
| Token not resolved in CI | ${NPM_TOKEN} not set | Set env var in CI |
npm config set fails | // parsed as flag | Use -- separator |
| Unknown config warning | Custom key in .npmrc | Use package.json#config |
Real-World Examples
1. Set Default Registry
registry=https://registry.npmjs.org/
2. Set Scoped Registry
@mycompany:registry=https://npm.company.com/
3. Scoped Token
//npm.company.com/:_authToken=${NPM_TOKEN}
4. CLI Token Set
npm config set -- '//npm.company.com/:_authToken' "${NPM_TOKEN}"
5. Verify Registry
npm config get registry
6. Verify Scoped Registry
npm config get @mycompany:registry
7. Save Exact Versions
save-exact=true
8. Enforce Engine
engine-strict=true
9. Proxy Configuration
proxy=http://proxy.company.com:8080
https-proxy=http://proxy.company.com:8080
10. Gitignore the Secret
echo ".npmrc" >> .gitignore
Visual
The Precedence Hierarchy
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HIGHEST PRIORITY โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ CLI flags โ --registry=... โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ Environment โ npm_config_registry=... โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ Project .npmrc โ <repo>/.npmrc โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ User .npmrc โ ~/.npmrc โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ Global .npmrc โ $PREFIX/etc/npmrc โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ Builtin โ npm installation โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ Defaults โ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The more specific scope wins. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Token Scoping
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ .npmrc โ
โ โ
โ //registry.npmjs.org/:_authToken=TOKEN_A โ
โ //npm.company.com/:_authToken=TOKEN_B โ
โ //other.registry.com/path/:_authToken=TOKEN_C โ
โ โ
โ Request to registry.npmjs.org โ TOKEN_A โ
โ Request to npm.company.com โ TOKEN_B โ
โ Request to other.registry.com/path โ TOKEN_C โ
โ Request to other.registry.com โ no token โ
โ โ
โ The token is matched to the request URL. โ
โ A token scoped to one host is never sent to another. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Environment Variable Substitution
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ .npmrc โ
โ //registry.company.com/:_authToken=${NPM_TOKEN} โ
โ โ
โ CI SECRET STORE โ
โ NPM_TOKEN = "company_token_xxxx" โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โ
โ โ npm reads .npmrc โ โ
โ โ substitutes โ โ
โ โ ${NPM_TOKEN} โ โ
โ โโโโโโโโโโฌโโโโโโโโโ โ
โ โผ โ
โ Effective: //registry.company.com/:_authToken=company_token โ
โ โ
โ The secret is never in the repository. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Config files | Project, user, global, builtin |
| Format | INI, key = value |
| Comments | ; or # |
| Env substitution | ${VAR} or ${VAR?} |
| Default registry | registry=... |
| Scoped registry | @scope:registry=... |
| Auth token | //host/:_authToken=... |
| Precedence | CLI > env > project > user > global > builtin |
| Project file in global mode | Not read |
| Unknown keys | Warned in npm 11.2+ |
Key takeaways:
- The
.npmrchierarchy is a precedence system. The project file overrides the user file, the user file overrides the global file, and command-line flags override everything. A setting that appears correct but does not take effect is usually being overridden by a higher-priority source.npm config get <key>reveals the effective value . - Authentication tokens must be scoped to a registry. The
//host/:_authTokensyntax ensures that a token is only sent to the host it was issued for. An unscoped token is not sent to custom registries and can create a credential leak if the configuration is wrong . - Environment variable substitution keeps secrets out of the file. The
${NPM_TOKEN}syntax allows the.npmrcto contain a reference rather than a literal token. The token is injected by the CI secret store or the developer’s shell environment. The?suffix forces undefined variables to empty strings . - Scoped registries enable multi-registry workflows. A project can install
lodashfrom npmjs.org and@mycompany/utilsfrom a private registry in the samenpm install. The@scope:registrykey is the mechanism, and it applies to both install and publish . - The project
.npmrcis not read in global mode.npm install -gignores the project file. If a global install needs a custom registry, the setting must be in the user or global config, not the project config .
Remember: The .npmrc file is the persistent layer of npm’s configuration system. It exists to make registry selection and authentication reproducible across machines and CI environments. The project file is for the project’s registry needs; the user file is for the developer’s credentials; the global file is for the machine’s defaults. The scoped token syntax is the security boundary: a token for one registry is never sent to another. The environment variable substitution is the secret management boundary: the file contains the reference, the environment contains the value. When the precedence is understood and the scoping is correct, the configuration works without exposing secrets or sending credentials to the wrong host.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!