a snapshot of the terminal showing the output of env dash dash help command

JavaScript is an interpreted language and its source code needs to be fed to some interpreter to run. If you want to run a JavaScript file using Node.js, you normally run this command:

$ node yourfile.js

By typing the name of the interpreter (), you are explicitly telling the shell how to run your script.

But that knowledge can be put inside the script itself so it can be run directly as if it was a binary:

$ ./yourfile.js

This will work only if you have execution permission on that file (you can set that with for example) and have set the right “shebang”.


Shebang or hashbang () is the first line of the file which tells the OS which interpreter to use. It typically looks like this:

#!/absolute/path/to/the/interpreter [optional params]

Shebang is a OS feature and can be used to run any interpreted language: Python, Perl, etc. For Node.js it can (but often doesn’t) look like this:


Node.js will happily ignore this as a comment only if it is the very first line of the file (it won’t work even if there’s an empty line or line before it). Browsers ignore it too (Chrome 74+, FF 67+).

Most people have a Node.js binary or symlink sitting at . If Node.js is not at , the OS will complain. For example bash would say . But is there a way to tell the OS to run the script with Node.js no matter where it is installed?

doesn’t work because shebang requires an absolute path.

Say hello to

is primarily intended to run a command in a modified environment. The emphasis here being “a command” because is almost always at while “a command” can be anything that’s on the .

If instead of we write, we’re telling OS to run and will run and will in turn execute the script.

The short answer

This is the most common shebang for Node scripts:

#!/usr/bin/env node

However, has a few other tricks up its sleeve that we can use.

Pass parameters to Node.js

Passing the option to causes it to parse whatever comes after which opens up a new door: passing parameters to the command.

For example let’s say we would like to run node with a special flag to enable ESM modules when running the current file. We could use this shebang:

#!/usr/bin/env -S node --experimental-module

Another example: if we want to run another script before running the current script, we can use Node’s option:

#!/usr/bin/env -S node -r ./my/other/file.js

Or to open up the inspection port:

#!/usr/bin/env -S node --inspect

Please note that if you run the script like , Node.js will not try to parse arguments from the shebang. It’ll just ignore it. It is the kernel which uses the shebang prior to running the file in order to figure out how to run it.

Set environment variables

Remember we said can run a command in a modified environment? That’s actually where it gets its name and it’s very powerful. Let’s say we want our script to run in production mode. We could set the environment variable:

#!/usr/bin/env -S NODE_ENV=production node

Without that, the would be or whatever is set at the user’s terminal when running the script.

Node.js respects a bunch of environment variables. For example we can use the to pass some CLI flags like this:

#!/usr/bin/env -S NODE_OPTIONS=--experimental-modules node

Start with an empty environment

If we wanted the script to run without access to any environment variable at the user’s terminal, we could run it with the flag which stands for “ignore environment”:

#!/usr/bin/env -S -i node

A mere implies , so we can also write it like this:

#!/usr/bin/env -S - node

Force-disable DEBUG

Maybe we don’t want to clear all environment variables but block-list a few of them. An example is (if you’re using the popular debug package). Maybe we don’t want the users of the scripts to set the flag when running it as a script. Then we’d use the flag which stands for unset environment variable.

#!/usr/bin/env -S -u=DEBUG - node

If the user runs the script as they cannot see any debugging info but you can still run it as see the DEBUG output.

Lock the Node.js runtime version

Sometimes you want to lock the version that is used to run a script. Prior to NPM@3 we could use , but that feature is removed and now we can only set the in which may or may not exist next to the script and depends on setting the config flag.

But there’s an easier way. Since is also an NPM package, and allows running any NPM package, you can write:

#!/usr/bin/env -S npx node@6

This may try download the requested version of Node upon running the script (so it won’t work without internet connection if that particular version of node does not exist in the NPX cache).

Tip: you can check the node version using

Run it with TypeScript

There’s no rule that says we have to run . Assuming and are available globally (), we could specify as the interpreter:

#!/usr/bin/env ts-node

And let it run the file as a TypeScript program.

In any of these examples, the file can have the extension or whatever else you prefer. It can even be without extentions!

Have I missed something? Do you have any tip you’d like to add? Please let me know in the comments or reach out on twitter: @alexewerlof


Knowledge Worker, MSc Systems Engineering, Tech Lead, Web Developer