Package Entry Points and `exports`

A package’s `exports` field defines which module entry points and subpaths consumers can import. It provides an explicit public surface for a package and can vary that surface by conditions such as `import`, `require`, or environment.

In a Node.js package, `package.json` can define `exports` as the package’s public import map. For example, `"exports": "./dist/index.js"` makes the package root resolve to that file. Consumers can import the package by its name without depending on the internal directory layout.

`exports` can also expose multiple subpaths. A mapping such as `"./utils": "./dist/utils.js"` permits `import "pkg/utils"`, while an unlisted subpath is not part of the exported interface. This lets package authors keep implementation files private and change their internal structure without promising those paths to consumers.

An export target can be conditional. Common conditions include `import` and `require`, allowing a package to select different files for ES module and CommonJS consumers. Browser- or environment-oriented conditions may also be used, but supported conditions and their resolution behavior depend on the module resolver and runtime.

The `exports` field is more restrictive than simply publishing files in the package. A file can physically exist inside an installed package yet still be inaccessible through package-name resolution when its path is not exported. This distinction is important when diagnosing errors such as `ERR_PACKAGE_PATH_NOT_EXPORTED`.

For frontend engineers, `exports` is especially relevant when publishing libraries consumed by bundlers such as Vite or webpack. A well-designed export map can define stable public entry points, provide separate development or production targets where appropriate, and prevent consumers from coupling to private implementation paths.

Key points

Interview questions

What problem does the `exports` field solve in `package.json`?

It defines the module entry points that consumers are allowed to access through the package name. This lets a library expose a stable public API without exposing its entire directory structure. It also allows package authors to change internal files while keeping public import paths stable.

How would you expose both a package root and a utility module?

I would use subpath exports in `package.json`. The root can map `.` to the main distribution file, while a name such as `./utils` maps to the utility implementation. Consumers can then use stable imports such as `my-lib` and `my-lib/utils` without depending on the `dist` directory layout.

What are conditional exports, and why would a package use them?

Conditional exports let a package choose an export target based on the resolver condition. A common example distinguishes `import` from `require`, allowing a package to provide appropriate module formats to different consumers. The package author should ensure that the selected targets are compatible with the consumers and runtime environments they intend to support.

Why can adding `exports` break an existing package consumer even when all the same files are still published?

Before an export map restricts package resolution, consumers may have relied on deep paths into the package’s published files. Once `exports` is present, only declared package entry points and subpaths are intended to be reachable through package-name resolution. Therefore, a previously working deep import can fail even though its underlying file still exists. This is why introducing or tightening an export map can require a compatibility review.

How would you diagnose `ERR_PACKAGE_PATH_NOT_EXPORTED`?

First, I would identify the exact package subpath the application is trying to resolve. Then I would inspect that package’s `package.json` and check whether the requested root or subpath appears in `exports`. If it is not exported, I would switch the consumer to a supported public entry point or, if I own the package, deliberately add the required subpath to the export map. I would avoid relying on an internal filesystem path as a permanent fix.