Skip to content
Latchkey

Node.js ERR_PACKAGE_PATH_NOT_EXPORTED - Fix Subpath Not in "exports"

When a package declares an exports field, Node treats it as the complete public API. Any subpath not listed there is blocked, even if the file physically exists in node_modules.

What this error means

An import or require of a deep path inside a dependency (e.g. pkg/lib/internal.js) throws ERR_PACKAGE_PATH_NOT_EXPORTED, naming the subpath that "is not defined by exports". The file is on disk, but the package’s exports map does not expose it.

Node output
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/utils'
is not defined by "exports" in /app/node_modules/some-pkg/package.json
    at exportsNotFound (node:internal/modules/esm/resolve)
    code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'

Diagnose it: what is different about the runner?

A build that passes locally and fails on a runner differs in a small number of predictable ways. Check those before changing build configuration, because the build config is usually not the thing that changed.

.github/workflows/ci.yml
- run: |
    node --version && npm --version
    echo "NODE_ENV=$NODE_ENV  CI=$CI"
    nproc && free -h && df -h /
    ls -la node_modules/.bin | head

Common causes

Deep-importing a path the package no longer exports

A package added or tightened its exports map, so reaching into its internals (a path that used to resolve) is now blocked. Node enforces the map strictly.

Importing the bare package without a defined main

If exports defines only specific subpaths and no "." entry, even importing the package by name fails with the same error.

How to fix it

Import only the package’s public entry points

Switch to a path the exports map actually exposes - usually the package root or a documented subpath.

JavaScript
// instead of reaching into internals:
import { thing } from 'some-pkg/lib/utils' // blocked

// import what exports allows:
import { thing } from 'some-pkg'

If you truly need an internal path

  1. Check whether the package publishes a supported subpath for what you need.
  2. Open an issue/PR asking the maintainer to export it, rather than depending on internals.
  3. As a last resort, pin to the older version that exposed the path while you migrate - do not patch node_modules in CI.

The three that account for most of them

  • Case sensitivity. Linux runners are case sensitive, macOS is not. An import with the wrong case resolves locally and fails in CI.
  • Out of memory. Exit code 137 is a SIGKILL from the kernel, not a build error. Raise --max-old-space-size or use a larger runner.
  • devDependencies pruned. NODE_ENV=production makes npm ci skip devDependencies, so the build tool itself goes missing. Set it after install, not before.

How to prevent it

  • Depend only on documented entry points, never package internals.
  • Re-check imports when upgrading a dependency major.
  • Prefer the package’s public API surface over deep paths.

Frequently asked questions

What causes Node.js ERR_PACKAGE_PATH_NOT_EXPORTED?
There are 2 common causes: deep-importing a path the package no longer exports and importing the bare package without a defined main. A package added or tightened its exports map, so reaching into its internals (a path that used to resolve) is now blocked.
How do I fix Node.js ERR_PACKAGE_PATH_NOT_EXPORTED?
There are 2 fixes depending on which cause you have: import only the package’s public entry points and if you truly need an internal path. Work through them in order, since the first is the most common.
What does Node.js ERR_PACKAGE_PATH_NOT_EXPORTED actually mean?
An import or require of a deep path inside a dependency (e.g.
How do I stop Node.js ERR_PACKAGE_PATH_NOT_EXPORTED happening again?
Depend only on documented entry points, never package internals. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card