npm 04 ๐ The package.json File Breakdown
The package.json file is the single most important file in a Node.js project. It is the manifest that tells npm what the project is, what it depends on, and how to run it. Every npm install, npm run, and npm publish reads this file. The npm documentation describes it as “the whole interface” to npmโwithout it, npm commands fail because there is no manifest to read . The file is JSON, but its fields fall into distinct categories: identity metadata, dependency declarations, script definitions, and entry point configuration.
The required fields are only two: name and version . Everything else is optional, but the optional fields are what make the file useful. The scripts block defines the commands a developer runs. The dependencies and devDependencies blocks declare what the project needs. The main and exports fields control how the package is imported by other code. The engines field declares which Node.js versions the project supports. This chapter breaks down each category, explains what the field does, and shows the common mistakes.
Key point: package.json requires only name and version. The scripts block defines runnable commands, dependencies and devDependencies declare runtime and development packages respectively, main is the CommonJS entry point, exports is the modern entry point with subpath and conditional support, and engines declares the supported Node.js versions.
Why the file is structured this way
The identity problem. npm needs a name and a version to identify the package. The name field must be lowercase, contain no spaces, and may include hyphens, dots, and underscores . The version must follow semantic versioning in the x.x.x format. Without these two fields, npm cannot publish the package, and the registry cannot resolve it. The identity fields are the minimum contract between a package and the registry.
The dependency problem. The dependencies and devDependencies fields separate the packages needed at runtime from the packages needed during development. The npm documentation defines dependencies as “packages required by your application in production” and devDependencies as “packages that are only needed for local development and testing” . This separation matters for production builds: npm install --omit=dev installs only the runtime dependencies, which keeps the production image smaller and reduces the attack surface.
The execution problem. The scripts block is the project’s command interface. It maps a short name like test or start to a shell command. The npm run <name> command executes the script, and the npm test and npm start commands are shortcuts that omit the word run . The scripts run from the package root regardless of the current directory, which makes them reliable across terminals and CI environments .
The entry point problem. The main field defines the module that is returned when another package does require('my-package') . The exports field is the modern replacement that adds subpath exports, conditional exports, and encapsulation. When exports is defined, deep imports like my-lib/dist/internal.js are blocked unless explicitly listed, which enforces the public API boundary . The main field is still supported for backward compatibility, but new packages should use exports.
a. Identity and metadata fields
The identity fields are name and version. The metadata fields describe the package to humans and to the registry search.
| Field | Required | Purpose |
|---|---|---|
name | Yes | Package identifier, lowercase, no spaces |
version | Yes | Semantic version, x.x.x |
description | No | Search text on the npm website |
keywords | No | Search tags |
author | No | One person, string or object |
contributors | No | Array of people |
license | No | SPDX license identifier |
repository | No | Source repository URL |
homepage | No | Project website |
bugs | No | Issue tracker URL |
{
"name": "my-package",
"version": "1.0.0",
"description": "A package that does something useful",
"keywords": ["utility", "string", "format"],
"author": "Alice <alice@example.com> (https://alice.dev)",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alice/my-package.git"
}
}
The author field accepts a single string in the format Name <email> (url), or an object with name, email, and url fields. The email and URL are optional . The license field is an SPDX identifier like MIT, ISC, or Apache-2.0. The repository field is an object with type and url, though the shorthand string form is also accepted .
The files field is the metadata field that controls what is included in the published tarball. It is an array of file patterns, and it works like a reverse .gitignore: the patterns list what to include, and anything not listed is excluded . Certain files are always included regardless of the files field: package.json, README, LICENSE, and the file in main. Certain files are always ignored: .git, node_modules, and package-lock.json .
b. Dependency fields and the version syntax
The dependency fields are the core of the manifest. They declare what the project needs, and the version range determines which updates are acceptable.
{
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.21",
"react": "18.2.0"
},
"devDependencies": {
"jest": "^29.0.0",
"typescript": "~5.4.0"
},
"optionalDependencies": {
"fsevents": "^2.3.0"
}
}
The dependencies field lists runtime packages. The devDependencies field lists development-only packages. The optionalDependencies field lists packages that are installed if possible but do not fail the install if they are missingโuseful for platform-specific native modules .
The version range syntax is the same across all three fields. The caret (^) allows minor and patch updates but not major updates. The tilde (~) allows patch updates only. An exact version (18.2.0) allows no updates. A wildcard (*) accepts any version . The range is a contract: it declares what the project has been tested against and what it is willing to accept.
The overrides field is the tool for forcing a specific version of a transitive dependency. It is only considered in the root package.json, not in installed dependencies, and it is the mechanism for patching a security vulnerability in a dependency of a dependency .
c. Scripts, entry points, and environment
The scripts block is the project’s command interface. The main and exports fields control the module entry points. The engines field declares the Node.js version.
{
"scripts": {
"start": "node src/index.js",
"test": "jest",
"build": "tsc",
"lint": "eslint src/"
},
"main": "./lib/index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/index.mjs",
"require": "./lib/index.cjs"
},
"./utils": "./lib/utils.js"
},
"engines": {
"node": ">=20"
}
}
The scripts block maps names to commands. The npm run <name> command executes the script, and npm start and npm test are shortcuts . The script runs from the package root, and npm adds node_modules/.bin to the PATH, so locally installed CLI tools are available without a full path .
The main field is the CommonJS entry point. The exports field is the modern entry point that supports subpaths (my-package/utils) and conditions (import for ESM, require for CommonJS, types for TypeScript). When exports is defined, it takes precedence over main, and deep imports are blocked unless explicitly listed .
The engines field declares the Node.js version range. It is advisory by default: npm warns but does not fail when the version does not match. The engine-strict configuration option changes it to a hard failure . The field is the way to communicate the minimum Node.js version the package supports.
Complete Example Session
The following session creates a package.json with all the field categories and verifies that npm reads it correctly.
{
"name": "weather-cli",
"version": "2.1.0",
"description": "A command-line weather client",
"keywords": ["weather", "cli", "forecast"],
"author": "Alice <alice@example.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alice/weather-cli.git"
},
"main": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"bin": {
"weather": "./dist/cli.js"
},
"scripts": {
"start": "node dist/index.js",
"test": "jest",
"build": "tsc",
"lint": "eslint src/"
},
"dependencies": {
"axios": "^1.6.0"
},
"devDependencies": {
"jest": "^29.0.0",
"typescript": "~5.4.0"
},
"engines": {
"node": ">=20"
}
}
# Install the dependencies
npm install
# Run the test script
npm test
# Run the build script
npm run build
# Check the package metadata
npm pkg get name version engines
# Output: { "name": "weather-cli", "version": "2.1.0", "engines": { "node": ">=20" } }
The bin field maps the command name weather to the file ./dist/cli.js. When the package is installed globally, npm creates a symlink in the global bin directory, and the weather command becomes available . This is how a package turns a file into a CLI tool.
Quick Reference
Required Fields
| Field | Format | Example |
|---|---|---|
name | lowercase, no spaces | my-package |
version | x.x.x semver | 1.0.0 |
Dependency Fields
| Field | Installed in Production | Purpose |
|---|---|---|
dependencies | Yes | Runtime packages |
devDependencies | No (with --omit=dev) | Development packages |
optionalDependencies | Yes, if possible | Platform-specific packages |
Version Ranges
| Range | Matches |
|---|---|
^1.2.3 | >=1.2.3 <2.0.0 |
~1.2.3 | >=1.2.3 <1.3.0 |
1.2.3 | Exactly 1.2.3 |
* | Any version |
Entry Points
| Field | Purpose |
|---|---|
main | CommonJS entry point |
exports | Modern entry point with subpaths |
bin | CLI command mapping |
Script Shortcuts
| Command | Equivalent |
|---|---|
npm start | npm run start |
npm test | npm run test |
npm run <name> | Runs the script |
Best Practices
โ Do This:
// Separate runtime and dev dependencies
"dependencies": { "express": "^4.18.0" },
"devDependencies": { "jest": "^29.0.0" }
// Use exports for modern packages
"exports": { ".": { "import": "./index.mjs", "require": "./index.cjs" } }
// Declare the Node.js version
"engines": { "node": ">=20" }
โ Don’t Do This:
// Put dev tools in dependencies
"dependencies": { "jest": "^29.0.0" } // installed in production
// Omit name or version
{ "description": "no name" } // npm cannot publish
// Use uppercase in name
"name": "MyPackage" // invalid
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Publish fails | name has uppercase or spaces | Lowercase, no spaces |
| Dev tools in production | Jest in dependencies | Move to devDependencies |
| Deep import blocked | exports does not list the subpath | Add the subpath |
| Script not found | Typo in script name | Check scripts block |
| Node version mismatch | engines not enforced | Use engine-strict or CI check |
Real-World Examples
1. Minimal manifest
{ "name": "app", "version": "1.0.0" }
2. Runtime dependency
"dependencies": { "express": "^4.18.0" }
3. Dev dependency
"devDependencies": { "typescript": "~5.4.0" }
4. Script
"scripts": { "build": "tsc" }
5. CommonJS entry
"main": "./lib/index.js"
6. Exports with conditions
"exports": { ".": { "import": "./index.mjs", "require": "./index.cjs" } }
7. CLI command
"bin": { "weather": "./cli.js" }
8. Engine constraint
"engines": { "node": ">=20" }
9. Files allowlist
"files": ["dist", "README.md"]
10. Override a transitive dependency
"overrides": { "lodash": "^4.17.21" }
Visual
Field Categories
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ IDENTITY โ
โ name, version, description, keywords โ
โ โ โ
โ โผ โ
โ DEPENDENCIES โ
โ dependencies, devDependencies, optional โ
โ โ โ
โ โผ โ
โ EXECUTION โ
โ scripts, bin โ
โ โ โ
โ โผ โ
โ ENTRY POINTS โ
โ main, exports โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Dependency Separation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ npm install โ
โ โ dependencies + devDependencies โ
โ โ
โ npm install --omit=dev โ
โ โ dependencies only โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Exports Resolution
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ import 'my-lib' โ
โ โ exports["."]["import"] โ
โ โ
โ require('my-lib') โ
โ โ exports["."]["require"] โ
โ โ
โ import 'my-lib/utils' โ
โ โ exports["./utils"] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Required fields | name, version |
| Runtime deps | dependencies |
| Dev deps | devDependencies |
| Optional deps | optionalDependencies |
| Commands | scripts |
| CLI mapping | bin |
| CJS entry | main |
| Modern entry | exports |
| Node version | engines |
| Version override | overrides |
Key takeaways:
- Only
nameandversionare required. Thenamemust be lowercase with no spaces, and theversionmust follow semantic versioning . dependenciesis for runtime,devDependenciesis for development. The separation is what allowsnpm install --omit=devto produce a production-only tree .- The
scriptsblock is the command interface.npm run <name>executes the script, andnpm startandnpm testare shortcuts . mainis the CommonJS entry point;exportsis the modern replacement. Whenexportsis defined, deep imports are blocked unless explicitly listed .- The
binfield turns a file into a CLI command. It maps a command name to a file, and npm creates the symlink on install . enginesdeclares the Node.js version. It is advisory by default, but theengine-strictoption makes it a hard failure .
Remember: The package.json file is the contract between the project and npm. The identity fields tell npm what the package is. The dependency fields tell npm what to install. The scripts tell npm what commands to run. The entry points tell Node.js what to load. Every field is optional except name and version, but the optional fields are what make the project usable, installable, and publishable. Read the file as a set of decisions: what the package is called, what it needs, how it runs, and how it is imported. Each decision has a field, and each field has a syntax.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!