TypeScript

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: subpath not in exports

Read the package exports map, tell a private deep import from an import-only entry, and pick the one change that makes the require or import resolve.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
9 min read

TL;DR#

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './x' is not defined by "exports" means Node found the package but its package.json exports map gives nothing for the path you asked for, in the way you asked for it. There are two different causes behind the same message. Either the path is not listed at all (a deep import such as firebase-admin/lib/firestore), and the fix is to import a subpath the map does list. Or the path is listed, but only under the "import" condition, and you load it with require(): then switch that call to import or import(), or move to a package version that offers an entry require() can match.

The error#

The Stack Overflow question behind this page hit it on Node 16.14.2, when orbit-db 0.28.7 ran require('multiformats/cid') against multiformats 10.0.2:

text
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './cid' is not defined by "exports" in /node_modules/multiformats/package.json
    at new NodeError (node:internal/errors:371:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:453:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:671:7)
    at resolveExports (node:internal/modules/cjs/loader:482:36)

The same code, ERR_PACKAGE_PATH_NOT_EXPORTED, also comes with a second wording when the package root itself has no usable entry:

text
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in /app/node_modules/no-main/package.json

Why Node refuses a path that exists on disk#

Once a package declares exports, that map is the whole public surface. The Node.js packages documentation puts it plainly: "all subpaths of the package are encapsulated and no longer available to importers", and require('pkg/subpath.js') then throws this error. The file can sit right there in node_modules; if the map does not list it, Node will not load it.

The second half of the mechanism is less obvious. An entry in exports can be a set of conditions, and Node picks the first one that matches how the package is being loaded. The documentation defines "import" as matching import and import(), "require" as matching require(), and states the two are "always mutually exclusive". The require() algorithm resolves package exports with the conditions ["node", "require", "module-sync"] by default, or ["node", "require"] without require(esm) support. There is no "import" in either list.

So when a path is listed with only an "import" condition, require() finds no condition it can match and Node reports the path as not defined, with the very same message as a path that is missing from the map. That is exactly the Stack Overflow case. The multiformats 10.0.2 manifest on the npm registry declares "type": "module" and this entry:

json
"./cid": {
  "types": "./dist/types/src/cid.d.ts",
  "import": "./src/cid.js"
}

orbit-db's CommonJS source calls require('multiformats/cid'), so it can never match that entry.

Reproduce both causes in two minutes#

The fastest way to tell the causes apart is a throwaway project with three fake packages: one whose ./cid entry lists only "import", one that exports . and ./firestore but keeps its lib/ folder private, and one that exports ./utils but no root entry. Run on 3 October 2026 in the official node:16-alpine, node:22-alpine and node:24-alpine images, the three versions gave the same result for every case. The Node 24 run printed:

text
### node v24.21.0 1-require-cid.cjs
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './cid' is not defined by "exports" in /app/node_modules/esm-only/package.json
### node v24.21.0 2-import-cid.mjs
OK cid-from-esm
### node v24.21.0 3-dynamic-import.cjs
OK cid-from-esm
### node v24.21.0 4-deep-lib.cjs
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/firestore' is not defined by "exports" in /app/node_modules/deep-pkg/package.json
### node v24.21.0 5-public-subpath.cjs
OK firestore ok
### node v24.21.0 6-no-main.cjs
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in /app/node_modules/no-main/package.json

Two readings matter. The ./cid file loads fine through import and import(); only require() fails. And a newer Node does not change that: the require(esm) support in Node 22 and 24 loads ES modules, but it does not make require() match an "import" condition.

Fix#

1. Read the exports map of the package named in the error#

The path at the end of the message names the package.json to open. Print its exports:

bash
node -p "JSON.stringify(require('./node_modules/multiformats/package.json').exports['./cid'], null, 2)"

If the subpath you import is missing from the keys, go to step 2. If it is there but lists only import (plus types), go to step 3.

2. A deep import: use a subpath the map lists#

Editors that auto-import often reach into a build folder. The top answer on the question is exactly that: the IDE wrote firebase-admin/lib/firestore, a path the package does not export, instead of firebase-admin/firestore. The exports keys of the latest firebase-admin release on the npm registry include ./firestore, ./auth, ./app and the other service entries, and no ./lib/... path:

js
// before: private build path, throws ERR_PACKAGE_PATH_NOT_EXPORTED
// const { getFirestore } = require('firebase-admin/lib/firestore');
 
// after: the public subpath listed in "exports"
const { getFirestore } = require('firebase-admin/firestore');

No "exports" main defined has the same two causes. Either the map has no "." entry at all, and you import one of the subpaths it does list instead of the bare package name, or "." is listed only under "import" and you load the package with require(). The same throwaway project shows the second case on all three Node versions:

text
### node v24.21.0 7-require-root.cjs
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in /app/node_modules/esm-only/package.json

That one is fixed by step 3, not by a different subpath.

3. An import-only entry: load it the way the package supports#

When your own code is CommonJS, an import() expression loads an ES module and matches the "import" condition:

js
// CommonJS file (.cjs, or .js with "type": "commonjs")
async function loadCid() {
  const { CID } = await import('multiformats/cid');
  return CID;
}
 
loadCid().then((CID) => console.log(typeof CID));

import() returns a promise, so any synchronous function that needed the value becomes asynchronous, and so do its callers. The answer on the question notes that patching orbit-db this way would turn its synchronous isValid method into an asynchronous one. If the whole file can become an ES module instead (rename it to .mjs, or set "type": "module"), a plain import { CID } from 'multiformats/cid' works too.

4. The package is someone else's dependency: change the version, not your code#

When the require() call sits inside a dependency, as in the question, you cannot edit it. Two version-level routes were checked:

  • A newer release of the import-only package, on a newer Node. The multiformats 14 release tested below adds a "module-sync" condition to ./cid, which the require() conditions include on Node versions with require(esm). The Node documentation lists that support as unflagged from Node 22.12, with a backport to the Node 20 line. In the real-package run below, require('multiformats/cid') succeeded with that release on Node 22 and 24, and still failed on Node 16.
  • An older release of the calling package. The answer on the question suggests orbit-db 0.26.0, which used the CommonJS cids package instead. Note that the npm registry marks the latest orbit-db release as deprecated, which makes it a poor long-term base for new code.

Reinstalling without changing a version does nothing: the exports map ships inside the package, so the same version brings back the same map.

Verify the fix#

Run the exact statement that failed, outside your app, from the project root:

bash
node -e "const { CID } = require('multiformats/cid'); console.log('OK typeof CID =', typeof CID)"

On the real packages, installed with npm into the same three images, the version and runtime decided the result:

text
### node v16.20.2 multiformats@14.0.5: require('multiformats/cid')
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './cid' is not defined by "exports" in /app/node_modules/multiformats/package.json
### node v22.23.3 multiformats@10.0.2: require('multiformats/cid')
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './cid' is not defined by "exports" in /app/node_modules/multiformats/package.json
### node v22.23.3 multiformats@14.0.5: require('multiformats/cid')
OK typeof CID = function
### node v24.21.0 multiformats@14.0.5: require('multiformats/cid')
OK typeof CID = function

Neither change alone was enough: Node 22 with the old package failed, and the new package on Node 16 failed. Only the pair worked.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Check your own error#

Paste the full error and, if the package is yours, its package.json; the explainer tells a private subpath from an entry that has no condition for the way you load it. It runs in your browser and nothing is sent.

Runs entirely in your browser — nothing you paste is sent anywhere. Tokens and passwords that appear in paths or URLs are masked in the output.

Optional: package.json, file extension, Node.js version (sharper advice)
Try:

When you maintain the package#

If the failing exports map is your own, give require() an entry it can match. In the same throwaway project, adding a "default" condition next to "import" on ./cid let require() load it on Node 22 and 24, because "default" always matches and those versions can require() an ES module. On Node 16 the error changed instead:

text
### node v16.20.2 1-require-cid.cjs
Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/esm-only/cid.js from /app/1-require-cid.cjs not supported.

That is the next error to expect: once resolution succeeds, an older Node still cannot require() the ES module file it found. Supporting those versions takes a CommonJS build behind the "require" condition. A module with top-level await cannot be required at all; the modules documentation says it throws ERR_REQUIRE_ASYNC_MODULE and must be loaded with import(). TypeScript reads the same map for its type lookups: a "types" condition placed after "import" is a classic source of TS2305 on an export that exists, and an exports map without a reachable .d.ts produces TS7016, could not find a declaration file.

A related symptom shows up in editors rather than at runtime: a tsconfig still on "moduleResolution": "Node" ignores exports entirely, which is what breaks @vitejs/plugin-react types in vite.config.ts. And if the package you just switched to native ES modules is your test setup, the jest global disappears with it, covered in ReferenceError: jest is not defined under ESM. In every case the fix starts in the same place as this one: read the exports map, then load each entry the way it was published.

Further reading: Fix: exports is not defined in ES module scope. Also see Fix ERR_REQUIRE_ESM on cPanel Node.js apps (Passenger).

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote