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
- Official npm package.json docs - Complete reference
- Node.js package.json reference - Node.js package configuration
- Semantic Versioning (semver) - Version numbering specification
- npm Scripts guide - Script lifecycle and environment
- npm Workspaces - Monorepo management
- Package entry points - Module resolution guide