If you're new to Node or have only worked on projects that already have their npm scripts set up, then you may be wondering what npm scripts are and how they work. In this article, I'll give my best intuitive explanation for how/why they work and highlight some of the key tools available for writing simple npm scripts.
What Are These Scripts?
package.json
package.json is the npm configuration file for a project, including its dependencies, project details, and scripts. npm run is the npm command that executes commands written in the scripts section.
For my template article repo, I set up a linter script lint, which is run with npm run lint. Running prettier **/*.md in the terminal directly wouldn't work because prettier isn't found in the global PATH.
"scripts": {
"lint": "prettier **/*.md"
},
"devDependencies": {
"prettier": "^2.1.2"
}
Meet PATH
PATH is an environment variable that lists directories where the shell should look for commands. You can find a more thorough explanation on Linux Hint.
In order to find out what's inside your PATH you can run echo $PATH. Running that on a repl.it sandbox, I got the following:
~/npm-scripting-tutorial$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
At least for our purposes, all npm run does is append a few more directories onto the PATH. We can confirm this by making the following npm script, and running it ourselves:
"scripts": {
"path": "echo $PATH",
"lint": "prettier **/*.md"
}
~/npm-scripting-tutorial$ npm run path
> @ path /home/runner/npm-scripting-tutorial
> echo $PATH
/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/home/runner/npm-scripting-tutorial/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
So npm run prepended the following section to the PATH:
/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/home/runner/npm-scripting-tutorial/node_modules/.bin
The /home/runner/npm-scripting-tutorial/node_modules/.bin section is what gives us access to prettier in the npm lint command.
What Does This Mean?
The expansion of the PATH is what allows us to call commands written in other npm packages without referencing their exact location in our node_modules folder. If we didn't have this, our scripts would look more like the following:
"scripts": {
"lint": "./node_modules/.bin/prettier **/*.md"
}
That wouldn't be terrible, but it's not exactly ideal.
Other Highlights
Aliases
Some commands are so common that npm aliases them so they don't need to be prefixed by run. These include:
So running npm start is the same as running npm run start.
Lifecycle Operations
There are some names that are hooks into steps of npm lifecycle commands (e.g. npm publish, npm install, npm start). You can add scripts with these names to trigger commands at those steps:
"scripts": {
"build": "tsc --project .",
"prepack": "npm run build"
}
One unintuitive quirk with lifecycle scripts is that prepare and prepublish (both of which are now deprecated) also trigger on local npm install, so if you have a build step that shouldn't trigger on install, it would be better associated with prepublishOnly or prepack.
The docs include more info on other lifecycle operations that you can hook into.
Command Arguments
Normally if we pass in a --option to npm run, it won't pass it through to the command written in scripts. For instance, to make prettier automatically fix issues, we would want to pass the --write option. If we add a -- before the options, they will be passed through. This means we update our npm run lint command above to the following to execute prettier --write:
npm run lint -- --write
Conclusion
Hopefully this quick intro to the concepts around npm scripts makes it easier to read the scripts you encounter, as well as start writing your own. If you have any other questions, I recommend you look through npm's well-written documentation, starting with the npm CLI docs.
Top comments (1)
Hi. Interesting article. I'm studying npm
preparescript and found your article.By the way,

prepublishit's deprecated, butprepareit's still supported.docs.npmjs.com/cli/v10/using-npm/s...