npm lifecycle scripts, in order
npm runs special scripts at defined points in the install, test, start, stop and publish lifecycles. This reference lists each lifecycle script in execution order with what triggers it and a typical use case, plus the pre/post naming convention that lets you hook around any command. Search by name or keyword.
How lifecycle scripts work
Scripts live in the scripts field of package.json. Some names are reserved lifecycle hooks that npm invokes automatically — you do not need to call them with npm run. The three main flows:
Install flow (triggered by npm install or npm ci):
preinstall → install → postinstall → prepare
prepare also runs in this sequence but is not run when installing the package as a dependency of another project (only runs locally and before pack/publish).
Publish flow (triggered by npm publish):
prepublishOnly → prepare → prepack → [tarball built] → postpack → publish → postpublish
Test / start / stop flows each fire their pre and post hooks around the named command automatically.
The pre/post naming convention
For any script x, npm automatically runs prex before it and postx after it. This works for built-in lifecycle names and your own custom scripts:
{
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc",
"postbuild": "echo Build complete"
}
}
Running npm run build fires all three in order without any extra configuration.
prepare vs. postinstall — the key distinction
postinstall | prepare | |
|---|---|---|
Runs after npm install locally | Yes | Yes |
| Runs when installed as a dependency | Yes | No |
Runs before npm publish | No | Yes |
| Typical use | Native addon compilation, code generation | Build steps, Husky git-hook setup |
The most important rule: use prepare for build steps that should run before publishing and on local install, but not when downstream projects install your package as a dependency. Use postinstall only when you genuinely need to run something in the consumer’s environment — patching, native bindings, etc. Overusing postinstall causes security concerns and slows down everyone who installs your package.
prepublishOnly — publish gate
prepublishOnly runs only before npm publish, never on install. It is the right place to run your full test suite and linter as a final guard before the package ships. Many teams who skipped this script have published broken releases.
{
"scripts": {
"prepublishOnly": "npm test && npm run lint"
}
}
Complete typical library configuration
{
"scripts": {
"build": "tsc",
"prebuild": "rimraf dist",
"test": "vitest run",
"pretest": "eslint .",
"prepare": "npm run build",
"prepublishOnly": "npm test"
}
}
prebuildcleansdistbefore every build via the naming convention alone.pretestlints before every test run.preparebuilds on local install and before publishing.prepublishOnlygates every publish behind a passing test run.
Skipping scripts safely
To install without running any lifecycle scripts — useful in CI or when installing untrusted packages:
npm install --ignore-scripts
For Husky or similar tools that install git hooks via prepare, guard the script with a CI check:
{
"scripts": {
"prepare": "is-ci || husky install"
}
}
npm v7+ tightened the pre/post auto-hook behaviour for workspaces. In a monorepo, test that scripts fire as expected in the workspace root versus individual package directories.