DEV Community

Cover image for Demystifying NPM Scripts

Demystifying NPM Scripts

dcwither profile image Devin Witherspoon ・3 min read

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 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"
Enter fullscreen mode Exit fullscreen mode


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 sandbox, I got the following:

~/npm-scripting-tutorial$ echo $PATH
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
~/npm-scripting-tutorial$ npm run path

> @ path /home/runner/npm-scripting-tutorial
> echo $PATH

Enter fullscreen mode Exit fullscreen mode

So npm run prepended the following section to the PATH:

Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

That wouldn't be terrible, but it's not exactly ideal.

Other Highlights


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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode


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.

Discussion (0)

Editor guide