How to Use Subpath Imports in package.json

This article provides an overview of the imports field in package.json, explaining how it enables native subpath imports in modern JavaScript environments. You will learn the syntax, use cases for internal module aliasing, conditional resolution patterns, and how to configure your project to eliminate deep relative paths without relying on third-party build tools.

What is the imports Field?

The imports field in package.json is a native standard supported by Node.js, modern runtimes (like Deno and Bun), and bundlers (such as Vite, Webpack, and esbuild). It defines internal module aliases that apply exclusively within the package itself.

Unlike the exports field, which controls what external consumers can access when importing your package, the imports field is strictly internal. All import specifiers defined in the imports map must begin with a hash (#) to distinguish them from standard external package names.

Setting Up Basic Subpath Imports

To configure internal path aliases, add the imports property to your package.json file.

{
  "name": "my-app",
  "type": "module",
  "imports": {
    "#utils": "./src/utils/index.js",
    "#config": "./src/config.js"
  }
}

With this configuration, any file inside your project can use #utils or #config directly, avoiding complex relative paths:

// Instead of: import { formatDate } from '../../utils/index.js';
import { formatDate } from '#utils';

Pattern Matching with Wildcards

You can use wildcard asterisks (*) to alias entire directories dynamically rather than mapping files individually.

{
  "imports": {
    "#components/*": "./src/components/*",
    "#services/*": "./src/services/*.js"
  }
}

When importing:

import Button from '#components/Button.jsx';
import { AuthService } from '#services/auth';

Every instance of * in the import specifier replaces the * in the target path.

Conditional Imports

The imports field allows conditional mapping based on runtime environments, module types, or custom flags.

{
  "imports": {
    "#database": {
      "node": "./src/db/node.js",
      "browser": "./src/db/browser.js",
      "default": "./src/db/default.js"
    }
  }
}

When code executes import db from '#database', the runtime resolves the file matching its current environment condition.

Key Rules and Best Practices

  1. Prefix with #: Every key in the imports object must start with #. Leading hashes prevent collisions with external dependencies published on npm.
  2. Relative Targets: The target paths must be explicitly relative (starting with ./).
  3. Internal Only: External packages cannot access your # aliases; they are private to your package root.
  4. TypeScript Compatibility: TypeScript supports subpath imports automatically when the moduleResolution option in tsconfig.json is set to node16, nodenext, or bundler.