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:
Used for:
Publishing to npm
Self-referencing within the package
Package identification
”type”
Determines how .js files are interpreted:
"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
.mjs - Always ES module
.cjs - Always CommonJS
.js - Follows package.json “type” field
Module Resolution
For require()
const module = require ( 'package-name' );
Checks node_modules/package-name/package.json
Looks for "exports" field
Falls back to "main" field
Defaults to index.js
For import
import module from 'package-name' ;
Requires exact file extensions
Uses "exports" field primarily
Falls back to "main" if no exports
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
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:
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