NPM-SCRIPTS(7)
π NPM-SCRIPTS(7) π
okay bestie, welcome to the manual page for Scripts β that's how npm handles the "scripts" field. this is NPM-SCRIPTS(7), and yes we're reading the whole thing, grab a snack π
πΈ Description πΈ
so the "scripts" property of your package.json file? it's lowkey iconic. it supports a bunch of built-in scripts plus their preset life cycle events, and whatever arbitrary scripts you wanna throw in there. all of them get executed by running npm run <stage>.
and listen β pre and post commands with matching names will be run for those too. like premyscript, myscript, postmyscript. the whole trio, no cap.
you can also run scripts from dependencies with npm explore <pkg> -- npm run <stage>. iykyk π
π Pre & Post Scripts π
wanna make "pre" or "post" scripts for anything you defined in the "scripts" section of your package.json? it's literally so easy β just make another script with a matching name and stick "pre" or "post" at the beginning of it. that's it. that's the whole vibe β¨
{
"scripts": {
"precompress": "{{ executes BEFORE the `compress` script }}",
"compress": "{{ run command to compress files }}",
"postcompress": "{{ executes AFTER `compress` script }}"
}
}
so in this example, npm run compress would execute these scripts exactly as described. main character energy from all three of them tbh π
β¨ Life Cycle Scripts β¨
there are some special life cycle scripts that only happen in certain situations. these are in addition to the pre<event>, post<event>, and <event> scripts β not instead of them, bestie π«Ά
prepare,prepublish,prepublishOnly,prepack,postpack,dependencies
π prepare (since npm@4.0.0)
- runs BEFORE the package is packed, i.e. during
npm publishandnpm pack - runs on local
npm installwithout package arguments (runs with flags like--productionor--omit=dev, but does not run when installing specific packages likenpm install express) - runs AFTER
prepublishOnlyandprepack, but BEFOREpostpack - runs for a package if it's being installed as a link through
npm install <folder> - NOTE: if a package being installed through git contains a
preparescript, its dependencies and devDependencies will be installed, and thepreparescript will be run, before the package is packaged and installed π«£ - as of npm@7 these scripts run in the background. to see the output, run with:
--foreground-scripts - in workspaces,
preparescripts run concurrently across all packages. if you have interdependent packages where one must build before another, consider using--foreground-scripts(which can be set in.npmrcwithforeground-scripts=true) to run scripts sequentially, or structure your build differently π
π prepublish (DEPRECATED)
- does not run during
npm publish, but does run duringnpm ciandnpm install. see below for more info.
this one is deprecated, bestie. it's giving legacy. keep reading, there's a whole explanation later π
πΈ prepublishOnly
- runs BEFORE the package is prepared and packed, ONLY on
npm publish
π¦ prepack
- runs BEFORE a tarball is packed (on
"npm pack","npm publish", and when installing a git dependency) - NOTE:
"npm run pack"is NOT the same as"npm pack"."npm run pack"is an arbitrary user defined script name, whereas"npm pack"is a CLI defined command. do not mix these up, i'm begging π
π postpack
- runs AFTER the tarball has been generated but before it is moved to its final destination (if at all β publish does not save the tarball locally)
π dependencies
- runs AFTER any operations that modify the
node_modulesdirectory IF changes occurred - does NOT run in global mode
π¦ Prepare and Prepublish π¦
π· Deprecation Note: prepublish
since npm@1.1.71, the npm CLI has run the prepublish script for both npm publish and npm install, because it's a convenient way to prepare a package for use (some common use cases are described in the section below). it has also turned out to be, in practice, very confusing β¨https://github.com/npm/npm/issues/10074β©. like, legendary levels of confusing π΅βπ«
as of npm@4.0.0, a new event has been introduced, prepare, that preserves this existing behavior. a new event, prepublishOnly has been added as a transitional strategy to allow users to avoid the confusing behavior of existing npm versions and only run on npm publish (for instance, running the tests one last time to ensure they're in good shape).
see β¨https://github.com/npm/npm/issues/10074β© for a much lengthier justification, with further reading, for this change. it's a whole saga, fr fr π
β Use Cases
use a prepare script to perform build tasks that are platform-independent and need to run before your package is used. this includes tasks such as:
- compiling TypeScript or other source code into JavaScript
- creating minified versions of JavaScript source code
- fetching remote resources that your package will use
running these build tasks in the prepare script ensures they happen once, in a single place, reducing complexity and variability. additionally, this means that:
- you can depend on build tools as devDependencies, and thus your users don't need to have them installed π
- you don't need to include minifiers in your package, reducing the size for your users
- you don't need to rely on your users having
curlorwgetor other system tools on the target machines
literally a glow-up for your package size, we love to see it π β¨
π Dependencies π
the dependencies script is run any time an npm command causes changes to the node_modules directory. it's run AFTER the changes have been applied and the package.json and package-lock.json files have been updated. so it always gets the freshest possible tea β
π Life Cycle Operation Order π
here's the order things fire in, and yes, order matters, bestie β don't skip this part π
π npm help
prepare
π npm help ci
preinstall(before dependencies are installed)installpostinstallprepublishprepreparepreparepostprepare
preinstall runs before any dependencies are fetched or unpacked into node_modules, so scripts can prepare the environment (for example, setting up authentication for a private registry) before tarballs are fetched. for npm ci, preinstall fires after the lockfile has been validated against package.json, so it cannot influence dependency resolution β that remains locked to package-lock.json. the remaining scripts run after the installation of modules into node_modules, in order, with no internal actions happening in between. clean, sequential, iconic β¨
because preinstall runs before reify, scripts cannot rely on packages from node_modules. npm ci wipes node_modules before preinstall fires, so require() of a dependency will always fail. use install or postinstall for setup that depends on installed packages. no cap, require() will simply not work here π
π¦ npm help diff
prepare
πΈ npm help install
these run on a bare npm install in a local project (no package arguments).
preinstall(before dependencies are installed)installpostinstallprepublishprepreparepreparepostprepare
preinstall runs before any dependencies are fetched or unpacked into node_modules, so scripts can prepare the environment (for example, setting up authentication for a private registry) before resolution begins. the remaining scripts run after installation has completed.
because preinstall runs before reify, scripts cannot rely on packages from node_modules. on a fresh checkout, require() of a dependency will fail. on a repeat npm install against an existing node_modules/, it may incidentally succeed because the previously-installed tree is still on disk, but the version available is whatever was previously installed and may be removed or replaced by the upcoming install. use install or postinstall for setup that depends on installed packages. don't let the accidental success gaslight you π«
if there is a binding.gyp file in the root of your package and you haven't defined your own install or preinstall scripts, npm will default the install command to compile using node-gyp via node-gyp rebuild.
these are run from the scripts of <pkg-name>.
π npm help pack
prepackpreparepostpack
π npm help publish
prepublishOnlyprepackpreparepostpackpublishpostpublish
π npm help rebuild
preinstallinstallpostinstallprepare
prepare is only run if the current directory is a symlink (e.g. with linked packages). only under that condition, bestie β it's picky π
π₯ npm help restart
if there is a restart script defined, these events are run; otherwise, stop and start are both run if present, including their pre and post iterations)
prerestartrestartpostrestart
π npm run <user defined> β¨/commands/npm-runβ©
pre<user-defined><user-defined>post<user-defined>
πΈ npm help start
prestartstartpoststart
if there is a server.js file in the root of your package, then npm will default the start command to node server.js. prestart and poststart will still run in this case. so your pre and post don't get ghosted π»
π npm help stop
prestopstoppoststop
π§ npm help test
pretesttestposttest
π npm help version
preversionversionpostversion
π A Note on a lack of npm help uninstall scripts
while npm v6 had uninstall lifecycle scripts, npm v7 does not. removal of a package can happen for a wide variety of reasons, and there's no clear way to currently give the script enough context to be useful. it's not that npm hates you, it's that the context just isn't there π«Ά
reasons for a package removal include:
- a user directly uninstalled this package
- a user uninstalled a dependent package and so this dependency is being uninstalled
- a user uninstalled a dependent package but another package also depends on this version
- this version has been merged as a duplicate with another version
- etc.
due to the lack of necessary context, uninstall lifecycle scripts are not implemented and will not function. so don't write them and then wonder why nothing happens π
π Working Directory for Scripts π
scripts are always run from the root of the package folder, regardless of what the current working directory is when npm is invoked. so your scripts can reliably assume they're running in the package root. comforting, honestly π·
if you want your script to behave differently based on the directory you were in when you ran npm, you can use the INIT_CWD environment variable, which holds the full path you were in when you ran npm run. that's your little time capsule of where you started π°οΈβ¨
π¦ Historical Behavior in Older npm Versions π¦
for npm v6 and earlier, scripts were generally run from the root of the package, but there were rare cases and bugs in older versions where this was not guaranteed. so if your package must support very old npm versions, you may wish to add a safeguard in your scripts (for example, by checking process.cwd()). be safe out there, bestie π«‘
for more details, see:
- npm v7 release notes β¨https://github.com/npm/cli/releases/tag/v7.0.0β©
- discussion about script working directory reliability in npm v6 and earlier β¨https://github.com/npm/npm/issues/12356β©
β¨ ENVIRONMENT β¨
okay so bestie, when your package scripts run, they don't run in a vacuum β npm hands them a whole lil care package of info about how npm is set up and what's going on in the process rn. it's giving "prepared". π
π€οΈ path
so if you depend on modules that ship executable scripts β think test suites and stuff β those executables get dropped onto the PATH for running your scripts. like, if your package.json has this:
{
"name" : "foo",
"dependencies" : {
"bar" : "0.1.x"
},
"scripts": {
"start" : "bar ./test"
}
}
then you could run npm start to execute the bar script, which gets exported into the node_modules/.bin directory on npm install. no cap, it just works. π¦
π¦ package.json vars
npm sets these environment variables straight from your package.json:
npm_package_nameβ the package namenpm_package_versionβ the package versionnpm_package_bin_*β each executable defined in thebinfieldnpm_package_engines_*β each engine defined in theenginesfieldnpm_package_config_*β each config value defined in theconfigfieldnpm_package_jsonβ the full path to thepackage.jsonfile
and additionally, for install scripts (preinstall, install, postinstall), npm sets these ones too:
npm_package_resolvedβ the resolved URL for the packagenpm_package_integrityβ the integrity hash for the packagenpm_package_optionalβ set to"true"if the package is optionalnpm_package_devβ set to"true"if the package is a dev dependencynpm_package_peerβ set to"true"if the package is a peer dependencynpm_package_dev_optionalβ set to"true"if the package is both dev and optional
so like, for example, if you had {"name":"foo", "version":"1.2.5"} in your package.json file, then your package scripts would have the npm_package_name environment variable set to "foo", and npm_package_version set to "1.2.5". you access these in your code with process.env.npm_package_name and process.env.npm_package_version. main character accessors, honestly. π
Note: in npm 7 and later, mostpackage.jsonfields are no longer provided as environment variables. scripts that need otherpackage.jsonfields should read thepackage.jsonfile directly. thenpm_package_jsonenvironment variable gives you the path to the file for exactly this purpose. π«Ά
see package.json (the page at /configuring-npm/package-json) for more on package configs.
π·οΈ versioning variables
for versioning scripts (preversion, version, postversion), npm sets these environment variables:
npm_old_versionβ the version before being bumpednpm_new_versionβ the version after being bumped
π¬ current lifecycle event
lastly (love a finale), the npm_lifecycle_event environment variable is set to whichever stage of the cycle is being executed. so you could have one single script doing different parts of the process, switching based on what's currently happening. one script, many vibes. π
objects are flattened following this format, so if you had {"scripts":{"install":"foo.js"}} in your package.json, then you'd see this in the script:
process.env.npm_package_scripts_install === "foo.js"
β¨ Examples
for example, if your package.json contains this:
{
"scripts" : {
"prepare" : "scripts/build.js",
"test" : "scripts/test.js"
}
}
then scripts/build.js will be called for the prepare stage of the lifecycle, and you can check the npm_lifecycle_event environment variable if your script needs to behave differently in different contexts. contextual queen. π
if you wanna run build commands, go off β this works just fine:
{
"scripts" : {
"prepare" : "npm run build",
"build" : "tsc",
"test" : "jest"
}
}
πͺ EXITING
scripts are run by passing the line as a script argument to /bin/sh on POSIX systems or cmd.exe on Windows. and yes, you can control which shell gets used by setting the script-shell configuration option (the page at /using-npm/config#script-shell).
if the script exits with a code other than 0, then this will abort the process. like, full stop, the whole thing. π
note that these script files don't have to be Node.js or even JavaScript programs at all β they just have to be some kind of executable file. be free. π§
π BEST PRACTICES
- don't exit with a non-zero error code unless you really mean it. if the failure is minor or only prevents some optional features, it's better to just print a warning and exit successfully. lowkey the move. πΈ
- try not to use scripts to do what npm can do for you. read through
package.json(the page at/configuring-npm/package-json) to see all the things you can specify and enable by simply describing your package appropriately. in general, this leads to a more robust and consistent state. trust the process. β¨ - inspect the env to figure out where to put things. for instance, if the
NPM_CONFIG_BINROOTenvironment variable is set to/home/user/bin, then don't try to install executables into/usr/local/bin. the user probably set it up that way for a reason. respect the setup fr fr. π - don't prefix your script commands with
"sudo". if root permissions are required for some reason, then it'll fail with that error, and the user willsudothe npm command in question. let them handle it. π - don't use
install. use a.gypfile for compilation, andpreparefor anything else. you should almost never have to explicitly set apreinstallorinstallscript. if you're doing this, please consider whether there's another option. the only valid use ofinstallorpreinstallscripts is for compilation which must be done on the target architecture. periodt. π₯
π SEE ALSO
npm help runpackage.json(the page at/configuring-npm/package-json)npm help developersnpm help install
and that's the whole page, bestie β you ate that π this lil masterpiece is from npm version 12.2.1, and you're reading the NPM-SCRIPTS(7) manual page. go build something iconic πβ¨
π¦ where this page comes from
- arch rolling: npm 12.2.1-1 Β· Artistic-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms