Possible solution for NPM - Failed to replace env in config: ${NPM_TOKEN}
Quick answer
npm or yarn found an .npmrc file that references an environment variable (NPM_TOKEN) that is not defined in your shell, so it cannot expand it. Either export the variable (export NPM_TOKEN=your_token), or find the .npmrc that references it — in the project root or in your user home directory — and remove the stale entry. Run the install with --verbose to see exactly which .npmrc files are being read.
The error text itself is the giveaway: Failed to replace env in config: ${{NPM_TOKEN}. npm is telling you it tried to substitute an ${...} placeholder and there was nothing to put in its place.
Why it happens
To install a private package from the GitHub registry you point npm at that registry and give it a token. The token lives in an environment variable so it never ends up committed, and the .npmrc references it by name:
When you run npm install or yarn, npm reads that file and expands every ${VAR} against your current environment. If NPM_TOKEN is not set in that shell, it cannot replace the placeholder and aborts — that is the exact error.
The .npmrc files npm actually reads
This is the part that turns a one-minute fix into a two-hour one. npm does not read a single .npmrc; it merges several, and the closest one wins. In order of precedence:
- Project:
./.npmrc - User:
~/.npmrc(on Windows,C:\Users\<you>\.npmrc) - Global:
$PREFIX/etc/npmrc - npm's built-in config
So a line you added to your home .npmrc months ago is applied to every project you install, even ones that have no idea what NPM_TOKEN is. That is the usual reason the error shows up in a repo you never configured for the GitHub registry.
Common causes, ranked
- The variable is genuinely not defined in the shell you ran the command from.
- The
.npmrclives in your home folder, not the project — so it leaks into unrelated installs. This was my case: I had saved it inC:\Users\xabierlameiro\.npmrc, and every project inherited the${NPM_TOKEN}reference. - The variable is set in a GUI app or a different terminal tab that did not inherit the export.
- In CI, the secret exists but was never exposed to the step's environment.
How I fixed it
First I ran the install in verbose mode to see which files npm was reading:
yarn install --verbose
The log lists the paths it checks for .npmrc, and that is where I saw it was pulling the config from my user folder. I deleted it from the user folder and recreated it in the root of the project, and the error was gone.
If you instead want the token available everywhere, define it rather than removing the reference:
export NPM_TOKEN=your_token # macOS / Linuxsetx NPM_TOKEN your_token # Windows, new shells only
npm config ls -l prints the fully merged configuration, which is the fastest way to confirm whether the token was picked up.
Fixing it in CI
The most common place this bites is continuous integration, because the runner is a fresh machine with none of your local exports. The fix is to hand the secret to the step as an environment variable — the same NPM_TOKEN name the .npmrc expects. In GitHub Actions:
If you generate the .npmrc on the runner instead of committing it, write the auth line straight into the home file before installing:
echo "//npm.pkg.github.com/:_authToken=${NPM_TOKEN}" >> ~/.npmrcecho "@your-scope:registry=https://npm.pkg.github.com/" >> ~/.npmrc
Same idea in every CI provider: the .npmrc reference and the environment variable have to agree on the name, and the value has to reach the process that runs the install.
Keep the token out of git
One last thing, because it is a mistake I see often. The .npmrc that references ${NPM_TOKEN} is safe to commit: it holds only the name of the variable, not its value. What must never reach git is an .npmrc with the literal token pasted in (_authToken=ghp_...). If you did that, the token is compromised the moment it is pushed — revoke and rotate it in GitHub, do not just delete the file, because git history keeps it. The rule of thumb: an .npmrc that only references env vars is fine to commit and actually helps your teammates; one with real secrets belongs in .gitignore and, better, should not exist.
Whether it happens locally or in a pipeline, the question is always the same: does the shell that runs npm actually have
NPM_TOKEN, and is it reading the.npmrcyou think it is? Answer those two and the error disappears.
