Skip to content

/config/nodejs/package-json-exports

package.json exports field: entry points, subpaths and conditions

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · v24.20.0updated
tool
Node.js
file
package.json
key
exports
type
string | object (subpaths and/or conditions) | array
default
none (main is used, and every file of the package can be imported)

The exports field in package.json is the public interface of a package: it maps import specifiers ("pkg", "pkg/utils") to files, and can pick a different file for import and require. Once exports is present, any path that is not listed is blocked with ERR_PACKAGE_PATH_NOT_EXPORTED, and it takes precedence over main.

Example

node_modules/mylib/package.json
{
  "name": "mylib",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.cjs"
    },
    "./utils": "./lib/utils.js",
    "./package.json": "./package.json"
  }
}

See it resolve

bash
mkdir -p node_modules/mylib/lib node_modules/mylib/internal
cd node_modules/mylib
cat > package.json <<'EOF'
{ "name": "mylib", "exports": { ".": { "import": "./index.mjs", "require": "./index.cjs" }, "./utils": "./lib/utils.js" } }
EOF
echo 'export default "ESM build";' > index.mjs
echo 'module.exports = "CommonJS build";' > index.cjs
echo 'module.exports = "utils";' > lib/utils.js
echo 'module.exports = "secret";' > internal/secret.js
cd ../..
node -e 'console.log(require("mylib"), "/", require("mylib/utils"))'
node --input-type=module -e 'import m from "mylib"; console.log(m)'
node -e 'try { require("mylib/internal/secret.js") } catch (e) { console.log(e.code + ": " + e.message) }'

Output

CommonJS build / utils
ESM build
ERR_PACKAGE_PATH_NOT_EXPORTED: Package subpath './internal/secret.js' is not defined by "exports" in /tmp/work/node_modules/mylib/package.json

require got the .cjs file and import the .mjs file. The internal file exists on disk but cannot be imported, because it is not listed.

Conditions

ConditionMatches when
importThe package is loaded with import or import()
requireThe package is loaded with require()
nodeAny Node.js environment
module-syncNode.js versions that can require() ES modules (no top-level await)
defaultAlways; must come last as the fallback
typesTypeScript: the .d.ts file for that entry (put it first)
browser, development, productionCommunity conditions used by bundlers

Node.js picks the first matching key in object order, so the order of the conditions matters: specific ones first, default last.

Patterns

json
{
  "exports": {
    ".": "./index.js",
    "./features/*": "./src/features/*.js",
    "./internal/*": null
  }
}

* maps a whole folder of subpaths, and null explicitly blocks a pattern.

Sources