OpusMill

‘NODE_ENV’ is not recognized as an internal or external command

Verified on Windows 11, Node 24.20.0 · OpusMill

'NODE_ENV' is not recognized as an internal or external command,
operable program or batch file.
The fix

Install cross-env and put it in front of the variable:

npm install --save-dev cross-env
- "build": "NODE_ENV=production webpack"
+ "build": "cross-env NODE_ENV=production webpack"

That is the whole thing. It works unchanged on Windows, macOS and Linux, so you are not adding a Windows special case.

Why it happens

VAR=value command is shell syntax, not a program. In bash it means “run command with VAR set for this one invocation.” cmd.exe has no such syntax at all, so it does the only thing it can: it treats NODE_ENV=production as the name of the program you asked it to run, fails to find it, and says so.

You hit it through npm because npm runs scripts through cmd.exe on Windows and through /bin/sh everywhere else. The same package.json therefore behaves differently depending on who is typing npm run build. Nothing in your JavaScript is wrong; the script never got as far as starting Node.

Reproducing it

Run this from cmd on any Windows machine:

C:\> NODE_ENV=production node -e "console.log(1)"
'NODE_ENV' is not recognized as an internal or external command,
operable program or batch file.

Why not just set the variable first?

You can, and for a one-off in your own terminal it is fine (set NODE_ENV=production in cmd, $env:NODE_ENV="production" in PowerShell). But it is the wrong fix for a package.json, for two reasons: it leaks into every later command in that shell, and it means your build instructions now differ per platform — which is the problem you started with.

cross-env exists for exactly this and is downloaded about 25 million times a week. It is not a workaround for something rare.

The variants you may see instead

ScriptSame cause?
NODE_OPTIONS=--experimental-vm-modules jestYes. Same fix.
CI=true npm testYes. Same fix.
TZ=UTC jestYes. Same fix.
PORT=3000 node server.jsYes. Same fix.

Any NAME=value in front of a command in an npm script is the same bug. If a script has several, cross-env takes them all: cross-env A=1 B=2 node app.js.

If you maintain the package

This one almost never breaks for the people who install your package — npm only runs your own scripts on their machine at install time. It breaks for someone who cloned your repository and typed npm test, and they usually give up rather than open an issue, because they are not a contributor yet. That is why it survives so long in projects that are otherwise well maintained.

Check your own project for the rest of this class of bug. Paste your code or your package.json into the browser checker — nothing is uploaded, it runs on your machine — or run npx github:Hackierz/winbreak over the whole repository. --fix repairs the npm scripts that have one obvious answer and refuses the ones that need a human.

Other errors in the same family:

Background: I scanned the 600 most-downloaded npm CLI packages — 17.4% have a package.json script that cannot run on Windows.