.NPM-EXTENSION(5)
π NAME
.npm-extension - Imperative, root-owned manifest repairs
This is the .NPM-EXTENSION(5) manual, bestie β the one about giving third-party dependency manifests a root-owned glow-up before npm does its resolution thing. β¨
πΈ Description
Okay so picture this: a root-owned .npm-extension.mjs or .npm-extension.cjs file lets a project imperatively repair the manifests of third-party dependencies before npm resolves the dependency tree. It exports a transformManifest(pkg, context) function β that function receives a candidate dependency manifest and returns the effective manifest npm should use. That's the whole vibe. π
.npm-extension is the imperative counterpart to the declarative packageExtensions field at /configuring-npm/package-json#packageextensions, and it runs in the same pre-resolution phase, before packageExtensions.
Prefer packageExtensions for simple, data-only repairs. Reach for .npm-extension when you need comments and links explaining a repair, conditional logic, repeated repairs expressed as code, deletion or range rewrites, stale-repair guards, or a policy location outside package.json. No cap. π«Ά
π Example
Here's the little guy in action:
// .npm-extension.mjs
export function transformManifest (pkg, context) {
if (pkg.name === 'foo' && pkg.version.startsWith('1.')) {
pkg.dependencies = { ...pkg.dependencies, bar: '^2.0.0' }
context.log(`added bar to ${pkg.name}@${pkg.version}`)
}
return pkg
}
The .cjs form uses CommonJS exports instead:
// .npm-extension.cjs
module.exports = {
transformManifest (pkg, context) {
return pkg
},
}
π¦ The transformManifest function
transformManifest(pkg, context) receives a deeply isolated copy of a candidate dependency manifest. It may mutate and return that copy, or return a new manifest object. It must return a manifest object synchronously; returning null, undefined, a primitive, an array, or a promise fails the install. π
The context argument is intentionally small:
context.log(message)β writes an npm debug log message. πͺ΅context.rootβ the absolute path to the project root. πcontext.extensionPointβ the string"transformManifest". π·οΈ
npm provides no registry, fetch, lockfile, or extraction helpers. Keep the extension file self-contained or limited to Node builtins; npm does not guarantee that project dependencies are available when the file is loaded. Fr fr, keep it cozy and self-contained. π§
π₯ Supported mutations
Only the four resolution-affecting fields may change:
dependenciesoptionalDependenciespeerDependenciespeerDependenciesMeta
Within those fields you may add, replace, or delete entries. Changing any other field (such as scripts, bin, engines, os, cpu, exports, or main) is rejected, and the install fails with an error naming .npm-extension and the package being processed. The package tarball and the installed node_modules/<pkg>/package.json are never rewritten β npm is not doing a sneaky rewrite there. π«
π§ Discovery and extension-file
npm looks for a single .npm-extension.mjs or .npm-extension.cjs at the project root (the workspace root in a workspace project). Having both files present is an error. π¨ A .npm-extension file in a dependency or in a non-root workspace is ignored; a non-root workspace file produces a warning.
The extension-file config (/using-npm/config#extension-file) selects a different project-local file. It must resolve inside the project root and use a .mjs or .cjs extension, and it is honored only from project config or the command line β never from user, global, or builtin config.
π Interaction with packageExtensions and overrides
When both are present, transformManifest runs first and packageExtensions is applied to its output. Avoid targeting the same package with both unless you intend to rely on that ordering. overrides still controls the final resolution target of any edge, including edges created by transformManifest. That's the tea. β
π Lockfile and npm ci
A lockfile influenced by .npm-extension records an npmExtensionHash (a digest of the selected file's bytes and module format) on its root entry, and minimal npmExtensionApplied provenance on each affected package entry. Extension state requires lockfileVersion: 4.
Changing the file's contents makes npm install re-resolve the affected packages. npm ci does not import or execute .npm-extension; it verifies the recorded hash against the file and reifies the locked graph, failing if the file and lockfile disagree (or if one has extension state and the other does not).
β οΈ The hash proves only that the install uses the same extension file bytes that generated the lockfile. It does not make arbitrary JavaScript deterministic: extension output that depends on environment variables, the network, the clock, or files imported by the extension can still produce non-reproducible installs. Treat .npm-extension as trusted, deterministic project code, and only enable it in repositories you trust. π
π Disabling
Set ignore-extension (/using-npm/config#ignore-extension) to skip importing and executing .npm-extension. ignore-scripts (/using-npm/config#ignore-scripts) implies ignore-extension, since both disable root-owned install-time code. npm ci still verifies the file hash even when execution is disabled. π
π¦ Publishing
.npm-extension.mjs and .npm-extension.cjs are project configuration, not package contents. npm excludes the root file from the package tarball produced by npm pack and npm publish, even when the package's files list would include it, so a public package can keep .npm-extension in its repository for local use without publishing it. Slay. π¦
π See also
package.jsonpackageExtensions(/configuring-npm/package-json#packageextensions)package-lock.json(/configuring-npm/package-lock-json)npm help config
Okay bestie, that's the whole .NPM-EXTENSION(5) manual β NPM@12.2.1, October 2026. Go forth and repair those manifests responsibly. Mwah π
π¦ 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