| |

npm 22 ๐ŸŒ Configuration Management (.npmrc File & Registry Config)

โฑ๏ธ Estimated read: 11 min | |

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 # .

FileLocationScopeUse
Project.npmrc in project rootCurrent project onlyRegistry, scoped packages
User~/.npmrcCurrent user, all projectsAuth tokens, personal settings
Global$PREFIX/etc/npmrcAll users on the machineSystem-wide defaults
Builtinnpm installation directorynpm itselfDistribution 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

PrioritySourceExample
1 (highest)CLI flags--registry=...
2Environmentnpm_config_registry=...
3Project .npmrc<repo>/.npmrc
4User .npmrc~/.npmrc
5Global .npmrc$PREFIX/etc/npmrc
6Builtinnpm installation
7 (lowest)DefaultsHardcoded

Common Keys

KeyPurpose
registryDefault registry URL
@scope:registryRegistry for a scope
//host/:_authTokenToken for a host
save-exactPin exact versions
engine-strictEnforce engines field
fundShow funding messages
auditEnable security audit
strict-sslVerify TLS certificates

Auth Scoping

EntryMatches
//registry.npmjs.org/:_authToken=XOnly npmjs.org
//npm.company.com/:_authToken=YEntire company host
//npm.company.com/path/:_authToken=ZOnly 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

PitfallWhy It HappensFix
Token sent to wrong hostUnscoped _authTokenScope with //host/:_authToken
Registry setting ignoredProject file not read (global mode)Use user or global .npmrc
Token not resolved in CI${NPM_TOKEN} not setSet env var in CI
npm config set fails// parsed as flagUse -- separator
Unknown config warningCustom key in .npmrcUse 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

ItemValue
Config filesProject, user, global, builtin
FormatINI, key = value
Comments; or #
Env substitution${VAR} or ${VAR?}
Default registryregistry=...
Scoped registry@scope:registry=...
Auth token//host/:_authToken=...
PrecedenceCLI > env > project > user > global > builtin
Project file in global modeNot read
Unknown keysWarned in npm 11.2+

Key takeaways:

  • The .npmrc hierarchy 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/:_authToken syntax 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 .npmrc to 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 lodash from npmjs.org and @mycompany/utils from a private registry in the same npm install. The @scope:registry key is the mechanism, and it applies to both install and publish .
  • The project .npmrc is not read in global mode. npm install -g ignores 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.

Was this helpful?


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!