NexusCS

package.json

Node.js
package.json manifest reference for Node.js projects. Define metadata, dependencies, scripts, entry points, and npm package configuration.
npm
node
package-manager

Getting started

Introduction

package.json is the manifest file for Node.js projects, defining metadata, dependencies, scripts, and entry points for npm packages.

Minimal package.json

{
  "name": "my-package",
  "version": "1.0.0",
  "description": "My awesome package",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": ["awesome"],
  "author": "Your Name",
  "license": "MIT"
}

Required for publishing: name and version

Quick initialization

# Interactive setup
npm init

# Accept all defaults
npm init -y

# Scoped package
npm init --scope=@myorg

Essential metadata

Name and version

{
  "name": "my-package",
  "name": "@scope/my-package",
  "version": "1.2.3"
}
Field Requirement
name ≤214 chars, lowercase, URL-safe
version Semver format (MAJOR.MINOR.PATCH)

Description and keywords

{
  "description": "A delightful coffee varietal",
  "keywords": ["http", "server", "framework"]
}

Shown in npm search results

Author and contributors

{
  "author": "Barney Rubble <b@rubble.com> (http://example.com/)",
  "author": {
    "name": "Barney Rubble",
    "email": "b@rubble.com",
    "url": "http://example.com/"
  },
  "contributors": ["Betty Rubble <betty@rubble.com>"]
}

License

{
  "license": "MIT",
  "license": "(ISC OR GPL-3.0)",
  "license": "UNLICENSED"
}

Use SPDX identifiers or UNLICENSED

Entry points

Main entry point

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

Default CommonJS entry point; defaults to index.js if omitted.

Modern exports (recommended)

{
  "exports": "./index.js",
  "exports": {
    ".": "./index.js",
    "./feature": "./src/feature.js",
    "./package.json": "./package.json"
  }
}

Encapsulates internals; prevents deep imports unless explicitly defined.

Conditional exports

{
  "exports": {
    ".": {
      "import": "./index-module.js",
      "require": "./index-require.cjs",
      "types": "./index.d.ts",
      "node": "./node-specific.js",
      "default": "./index.js"
    }
  }
}

Different files for different environments.

Subpath patterns

{
  "exports": {
    ".": "./index.js",
    "./features/*.js": "./src/features/*.js",
    "./features/private/*": null
  }
}

Wildcard patterns for flexible exports.

Module type

{
  "type": "module"
}
Value Effect
"module" .js files treated as ESM
"commonjs" .js files treated as CJS (default)

Browser and bin

{
  "browser": "./browser-build.js",
  "bin": {
    "myapp": "bin/cli.js"
  },
  "bin": "bin/cli.js"
}

browser: Browser-specific entry point
bin: CLI executables (requires #!/usr/bin/env node)

TypeScript types

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

Dependencies

Runtime dependencies

{
  "dependencies": {
    "foo": "1.0.0",
    "bar": "^1.2.3",
    "baz": "~2.3.4",
    "qux": ">=1.0.0 <2.0.0",
    "pkg": "latest",
    "local": "file:../local",
    "git": "git+https://github.com/user/repo.git#v1.0.0",
    "github": "user/repo#v1.0.0",
    "tarball": "https://example.com/package.tgz"
  }
}

Installed in production environments.

Version ranges

Syntax Meaning Matches
1.2.3 Exact version 1.2.3 only
^1.2.3 Compatible (MINOR/PATCH) 1.2.3, 1.9.0 (not 2.0.0)
~1.2.3 Approximately (PATCH) 1.2.3, 1.2.9 (not 1.3.0)
1.x or 1 Any 1.x.x 1.0.0, 1.9.9
* Any version All versions
>=1.2.0 <2.0.0 Range 1.2.0 through 1.x.x
1.2.3 - 2.3.4 Inclusive range >=1.2.3 <=2.3.4

Development dependencies

{
  "devDependencies": {
    "typescript": "^5.0.0",
    "jest": "^29.0.0",
    "eslint": "^8.0.0"
  }
}

Only needed during development (testing/building).

Peer dependencies

{
  "peerDependencies": {
    "react": "^18.0.0"
  },
  "peerDependenciesMeta": {
    "vue": {
      "optional": true
    }
  }
}

For plugins; consumer must install.

Optional and bundled

{
  "optionalDependencies": {
    "fsevents": "^2.3.0"
  },
  "bundleDependencies": ["renderized", "super-streams"]
}

optional: Install failures don't fail build
bundled: Packaged when publishing

Overrides (npm 8.3.0+)

{
  "overrides": {
    "foo": "1.0.0",
    "bar": {
      "foo": "1.0.0"
    }
  }
}

Force specific transitive dependency versions.

Scripts

Common scripts

{
  "scripts": {
    "start": "node server.js",
    "test": "jest",
    "build": "tsc",
    "dev": "nodemon index.js",
    "lint": "eslint src",
    "format": "prettier --write .",
    "prepare": "npm run build"
  }
}

Run with npm run <script> or npm <script> for common ones (start, test).

Pre/post hooks

{
  "scripts": {
    "prebuild": "rm -rf dist",
    "build": "tsc",
    "postbuild": "cp README.md dist/",
    "pretest": "npm run lint",
    "test": "jest"
  }
}

Hooks run automatically before/after main script.

Lifecycle scripts

Script When It Runs
prepare Before pack/publish; after install (no args)
prepublishOnly Before publish only
prepack Before tarball is packed
postpack After tarball generated
preinstall Before package installed
install During package install
postinstall After package installed

Environment variables

// Available in all scripts
process.env.npm_package_name
process.env.npm_package_version
process.env.npm_lifecycle_event
process.env.npm_package_config_port

// Config access
{
  "config": {
    "port": "8080"
  }
}

All package.json fields accessible as npm_package_*.

Configuration

Engines

{
  "engines": {
    "node": ">=18.0.0",
    "npm": "^9.0.0"
  }
}

Specify required Node.js/npm versions.

OS and CPU

{
  "os": ["darwin", "linux"],
  "os": ["!win32"],
  "cpu": ["x64", "arm64"],
  "cpu": ["!arm"]
}

Restrict supported platforms.

Private packages

{
  "private": true
}

Prevents accidental publishing to registry.

Publish configuration

{
  "publishConfig": {
    "registry": "https://npm.pkg.github.com",
    "access": "public"
  }
}

Settings for npm publish.

Files to publish

{
  "files": ["dist/", "lib/", "README.md", "LICENSE"]
}

Whitelist for published files.

Always included: package.json, README, LICENSE, main file
Always excluded: .git, node_modules, .npmrc

Repository and homepage

{
  "repository": {
    "type": "git",
    "url": "git+https://github.com/npm/cli.git",
    "directory": "workspaces/libnpmpublish"
  },
  "repository": "github:user/repo",
  "homepage": "https://github.com/owner/project#readme",
  "bugs": {
    "url": "https://github.com/owner/project/issues",
    "email": "project@hostname.com"
  }
}

Workspaces (monorepos)

Define workspaces

{
  "name": "my-monorepo",
  "workspaces": ["packages/*", "apps/web", "!packages/excluded"]
}

Glob patterns for workspace packages.

Workspace commands

# Install dependency in specific workspace
npm install lodash -w packages/app-a

# Run script in one workspace
npm run test --workspace=packages/app-a

# Run script in all workspaces
npm run test --workspaces

# Run in multiple specific workspaces
npm run build -w packages/app-a -w packages/app-b

# Run in all workspaces if present
npm run test --workspaces --if-present

Workspace dependencies

{
  "dependencies": {
    "@myorg/shared": "workspace:*",
    "@myorg/utils": "workspace:^"
  }
}

Links local workspace packages.

Subpath imports

{
  "imports": {
    "#internal/*": "./src/internal/*.js",
    "#utils": "./src/utils/index.js"
  }
}
// Usage (private mappings)
import utils from "#utils";
import thing from "#internal/thing.js";

Complete example

Full package.json

{
  "name": "@mycompany/awesome-package",
  "version": "2.4.1",
  "description": "An awesome package that does awesome things",
  "keywords": ["awesome", "utility", "helper"],
  "author": "Jane Developer <jane@example.com>",
  "license": "MIT",
  "type": "module",

  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./utils": "./dist/utils.js"
  },

  "bin": {
    "awesome-cli": "./bin/cli.js"
  },

  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "test": "vitest",
    "lint": "eslint src",
    "prepare": "npm run build"
  },

  "dependencies": {
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "typescript": "^5.3.0",
    "vite": "^5.0.0",
    "vitest": "^1.0.0",
    "eslint": "^8.0.0"
  },
  "peerDependencies": {
    "react": "^18.0.0"
  },

  "engines": {
    "node": ">=18.0.0"
  },

  "repository": {
    "type": "git",
    "url": "git+https://github.com/mycompany/awesome-package.git"
  },
  "bugs": "https://github.com/mycompany/awesome-package/issues",
  "homepage": "https://github.com/mycompany/awesome-package#readme",

  "files": ["dist", "bin", "README.md"],

  "publishConfig": {
    "access": "public"
  }
}

Gotchas

Dependencies placement

Problem: Build tools in dependencies bloat production installs.

{
  "dependencies": {
    "typescript": "^5.0.0" // ❌ Wrong
  },
  "devDependencies": {
    "typescript": "^5.0.0" // ✅ Correct
  }
}

Move build tools to devDependencies.

Version ranges for 0.x

Problem: ^0.1.2 allows 0.1.3 but NOT 0.2.0 (different from 1.x behavior).

{
  "dependencies": {
    "unstable-pkg": "^0.1.2", // ❌ Risky
    "unstable-pkg": "0.1.2" // ✅ Pin exact version
  }
}

Pin exact versions for 0.x packages.

Exports encapsulation

Problem: Deep imports break once exports is defined.

// Before exports
require("pkg/internal/util"); // ✅ Works

// After adding exports: { ".": "./index.js" }
require("pkg/internal/util"); // ❌ Breaks
{
  "exports": {
    ".": "./index.js",
    "./internal/*": "./internal/*.js" // ✅ Explicitly export
  }
}

Files not published

Problem: .gitignore rules prevent publishing build output.

# .gitignore
dist/  # ❌ dist/ won't be published
{
  "files": [
    "dist/" // ✅ Whitelist explicitly
  ]
}

Or create separate .npmignore.

Lifecycle script timing

Problem: Using deprecated prepublish that runs on install too.

{
  "scripts": {
    "prepublish": "npm run build", // ❌ Runs on install
    "prepublishOnly": "npm run build", // ✅ Only on publish
    "prepare": "npm run build" // ✅ Build for both
  }
}

Private package publishing

Problem: Accidentally publishing internal packages.

{
  "name": "@company/internal-utils",
  "private": true // ✅ Prevents publish
}

Always set private: true for internal code.

Workspace linking

Problem: Workspace dependencies not linking properly.

{
  "dependencies": {
    "@myorg/shared": "*", // ❌ May install from registry
    "@myorg/shared": "workspace:*" // ✅ Links local workspace
  }
}

Best practices

Always include type field

{
  "type": "module"
}

Explicit module system prevents ambiguity.

Version strategy

// Applications (reproducible builds)
{
  "dependencies": {
    "react": "18.2.0"  // Exact version
  }
}

// Libraries (compatibility)
{
  "peerDependencies": {
    "react": "^16.8.0 || ^17.0.0 || ^18.0.0"  // Ranges
  }
}

Define engines

{
  "engines": {
    "node": ">=18.0.0",
    "npm": "^9.0.0"
  }
}

Prevent compatibility issues early.

Use prepare for builds

{
  "scripts": {
    "prepare": "tsc"
  }
}

Runs before publish and after install.

Explicitly list files

{
  "files": ["dist", "types", "README.md"]
}

Prevents publishing sensitive/large files.

Use exports for new packages

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

Provides encapsulation and conditional exports.

Commit lock files for apps

# Applications (commit)
git add package-lock.json

# Libraries (ignore)
echo "package-lock.json" >> .gitignore

Ensures reproducible builds for applications.

Also see