| |

npm 04 ๐ŸŒ The package.json File Breakdown

โฑ๏ธ Estimated read: 10 min | |

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.

FieldRequiredPurpose
nameYesPackage identifier, lowercase, no spaces
versionYesSemantic version, x.x.x
descriptionNoSearch text on the npm website
keywordsNoSearch tags
authorNoOne person, string or object
contributorsNoArray of people
licenseNoSPDX license identifier
repositoryNoSource repository URL
homepageNoProject website
bugsNoIssue 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

FieldFormatExample
namelowercase, no spacesmy-package
versionx.x.x semver1.0.0

Dependency Fields

FieldInstalled in ProductionPurpose
dependenciesYesRuntime packages
devDependenciesNo (with --omit=dev)Development packages
optionalDependenciesYes, if possiblePlatform-specific packages

Version Ranges

RangeMatches
^1.2.3>=1.2.3 <2.0.0
~1.2.3>=1.2.3 <1.3.0
1.2.3Exactly 1.2.3
*Any version

Entry Points

FieldPurpose
mainCommonJS entry point
exportsModern entry point with subpaths
binCLI command mapping

Script Shortcuts

CommandEquivalent
npm startnpm run start
npm testnpm 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

PitfallWhy It HappensFix
Publish failsname has uppercase or spacesLowercase, no spaces
Dev tools in productionJest in dependenciesMove to devDependencies
Deep import blockedexports does not list the subpathAdd the subpath
Script not foundTypo in script nameCheck scripts block
Node version mismatchengines not enforcedUse 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

ItemValue
Required fieldsname, version
Runtime depsdependencies
Dev depsdevDependencies
Optional depsoptionalDependencies
Commandsscripts
CLI mappingbin
CJS entrymain
Modern entryexports
Node versionengines
Version overrideoverrides

Key takeaways:

  • Only name and version are required. The name must be lowercase with no spaces, and the version must follow semantic versioning .
  • dependencies is for runtime, devDependencies is for development. The separation is what allows npm install --omit=dev to produce a production-only tree .
  • The scripts block is the command interface. npm run <name> executes the script, and npm start and npm test are shortcuts .
  • main is the CommonJS entry point; exports is the modern replacement. When exports is defined, deep imports are blocked unless explicitly listed .
  • The bin field turns a file into a CLI command. It maps a command name to a file, and npm creates the symlink on install .
  • engines declares the Node.js version. It is advisory by default, but the engine-strict option 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!