Skip to main content
Learn how to configure packages using package.json fields for both CommonJS and ES modules.

Introduction

A package is a folder described by a package.json file. The package.json configures how Node.js treats JavaScript files and defines entry points.

package.json Fields

”name”

Defines the package name:
{
  "name": "my-package"
}
Used for:
  • Publishing to npm
  • Self-referencing within the package
  • Package identification

”type”

Determines how .js files are interpreted:
{
  "type": "module"
}
  • "module" - Treat .js files as ES modules
  • "commonjs" - Treat .js files as CommonJS (default)

Module Type Examples

// ES modules package
{
  "type": "module"
}
// my-app.js - Loaded as ES module
import fs from 'node:fs';
// CommonJS package
{
  "type": "commonjs"
}
// my-app.js - Loaded as CommonJS
const fs = require('fs');

”main”

Default entry point for the package:
{
  "main": "./lib/index.js"
}
// Both of these resolve to ./lib/index.js
require('my-package');
import pkg from 'my-package';

“exports”

Defines package entry points and subpath exports:
{
  "exports": "./index.js"
}

Multiple Entry Points

{
  "exports": {
    ".": "./index.js",
    "./utils": "./lib/utils.js",
    "./helpers/*": "./lib/helpers/*.js"
  }
}
import pkg from 'my-package';
import { helper } from 'my-package/utils';
import fn from 'my-package/helpers/format.js';

”imports”

Define private package imports:
{
  "imports": {
    "#utils": "./src/utils.js",
    "#config": {
      "node": "./config-node.js",
      "default": "./config-default.js"
    }
  }
}
// Inside the package
import { helper } from '#utils';
import config from '#config';
Imports must start with # and are only accessible within the package.

Conditional Exports

Provide different exports based on environment:

import vs require

{
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs"
  }
}
// Uses index.mjs
import pkg from 'my-package';

// Uses index.cjs
const pkg = require('my-package');

Environment Conditions

{
  "exports": {
    "node": "./node-specific.js",
    "default": "./universal.js"
  }
}

Nested Conditions

{
  "exports": {
    ".": {
      "import": {
        "node": "./node-esm.mjs",
        "default": "./browser-esm.mjs"
      },
      "require": "./node-cjs.cjs",
      "default": "./fallback.js"
    }
  }
}

Built-in Conditions

  • "node" - Node.js environment
  • "import" - Loaded via import or import()
  • "require" - Loaded via require()
  • "module-sync" - ES module without top-level await
  • "default" - Fallback (always last)

Custom Conditions

node --conditions=development app.js
{
  "exports": {
    "development": "./dev.js",
    "production": "./prod.js",
    "default": "./index.js"
  }
}

Subpath Exports

Define which files can be imported from your package:
{
  "name": "my-package",
  "exports": {
    ".": "./index.js",
    "./utils": "./lib/utils.js",
    "./package.json": "./package.json"
  }
}
// ✅ Allowed
import pkg from 'my-package';
import utils from 'my-package/utils';

// ❌ Not exported - throws ERR_PACKAGE_PATH_NOT_EXPORTED
import internal from 'my-package/lib/internal.js';

Subpath Patterns

Use wildcards to export multiple files:
{
  "exports": {
    "./features/*.js": "./src/features/*.js",
    "./lib/*": "./src/lib/*.js"
  }
}
import feature from 'my-package/features/auth.js';
import helper from 'my-package/lib/validators/email';

Hiding Private Modules

{
  "exports": {
    "./utils/*.js": "./lib/utils/*.js",
    "./utils/internal/*": null
  }
}
// ✅ Allowed
import util from 'my-package/utils/public.js';

// ❌ Blocked by null target
import internal from 'my-package/utils/internal/secret.js';

Subpath Imports

Create private mappings for package-internal imports:
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./polyfills/dep.js"
    },
    "#internal/*": "./src/internal/*.js"
  }
}
// Inside your package modules
import dep from '#dep';
import helper from '#internal/helper.js';

Self-Referencing

Reference your own package by name:
{
  "name": "my-package",
  "exports": {
    ".": "./index.js",
    "./utils": "./utils.js"
  }
}
// Inside my-package/lib/feature.js
import { mainFunction } from 'my-package';
import { helper } from 'my-package/utils';

Determining Module System

Node.js determines module type based on:

ES Modules

Treated as ES modules:
  • .mjs files
  • .js files with "type": "module" in package.json
  • String input with --input-type=module
  • Files with ES module syntax (import/export)

CommonJS

Treated as CommonJS:
  • .cjs files
  • .js files with "type": "commonjs" in package.json
  • .js files without package.json or type field
  • String input with --input-type=commonjs

File Extension Priority

{
  "type": "module"
}
  • .mjs - Always ES module
  • .cjs - Always CommonJS
  • .js - Follows package.json “type” field

Module Resolution

For require()

const module = require('package-name');
  1. Checks node_modules/package-name/package.json
  2. Looks for "exports" field
  3. Falls back to "main" field
  4. Defaults to index.js

For import

import module from 'package-name';
  1. Requires exact file extensions
  2. Uses "exports" field primarily
  3. Falls back to "main" if no exports
  4. No automatic extension resolution

Dual Packages (CommonJS + ESM)

Support both module systems:
{
  "type": "module",
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs"
  },
  "main": "./index.cjs"
}

Wrapper Approach

// index.mjs (ES module)
export { feature } from './src/feature.js';
// index.cjs (CommonJS wrapper)
const { feature } = require('./src/feature.js');
module.exports = { feature };

Package Entry Points

Simple Entry Point

{
  "main": "./index.js",
  "exports": "./index.js"
}

Multiple Entry Points

{
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.cjs"
    },
    "./feature": {
      "import": "./feature/index.mjs",
      "require": "./feature/index.cjs"
    }
  }
}

Best Practices

Always Include Type Field

{
  "type": "module"
}
Benefits:
  • Explicit about module format
  • Better tool support
  • Future-proof your package

Use Exports for Modern Packages

{
  "exports": {
    ".": "./index.js",
    "./package.json": "./package.json"
  }
}
Benefits:
  • Encapsulation
  • Clear public API
  • Tree-shaking support

Provide Both Main and Exports

{
  "main": "./index.js",
  "exports": "./index.js"
}
Benefits:
  • Backward compatibility
  • Works with older Node.js versions

Common Patterns

TypeScript Definitions

{
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "import": "./index.mjs",
      "require": "./index.cjs"
    }
  }
}

Development vs Production

{
  "exports": {
    ".": {
      "development": "./dev.js",
      "production": "./prod.js",
      "default": "./index.js"
    }
  }
}

Browser vs Node

{
  "exports": {
    ".": {
      "node": "./node.js",
      "browser": "./browser.js",
      "default": "./universal.js"
    }
  }
}

Migration Guide

From Main to Exports

Before:
{
  "main": "./lib/index.js"
}
After:
{
  "main": "./lib/index.js",
  "exports": {
    ".": "./lib/index.js"
  }
}

Adding Subpath Exports

Before:
{
  "main": "./index.js"
}
After:
{
  "main": "./index.js",
  "exports": {
    ".": "./index.js",
    "./utils": "./lib/utils.js",
    "./package.json": "./package.json"
  }
}

Errors

ERR_PACKAGE_PATH_NOT_EXPORTED

Attempting to import a path not defined in exports:
{
  "exports": {
    ".": "./index.js"
  }
}
// ❌ Throws ERR_PACKAGE_PATH_NOT_EXPORTED
import internal from 'my-package/internal.js';

ERR_INVALID_PACKAGE_CONFIG

Invalid package.json syntax or structure:
// ❌ Invalid - missing quotes
{
  exports: "./index.js"
}

ES Modules

ES module syntax and import/export

CommonJS

CommonJS modules with require()

node:module API

Module utilities and customization