jsonnodejsnpmjavascript

package.json Explained: Every Field You Need to Know

·10 min read·JSON Concepts

The file that runs JavaScript

Every Node.js and front-end project has a package.json at its root. It is plain JSON, but it does a lot: it names your project, lists dependencies, defines scripts, and tells tools how your code should be loaded. Understanding its fields is fundamental to working in the JavaScript ecosystem.

json
{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": { "dev": "next dev", "build": "next build" },
  "dependencies": { "next": "^16.0.0" },
  "devDependencies": { "typescript": "^5.6.0" }
}

The core fields

FieldWhat it does
namePackage name (lowercase, URL-safe). Required to publish.
versionSemantic version (major.minor.patch).
type"module" for ES modules, "commonjs" (default) for require.
mainLegacy entry point when the package is imported.
scriptsNamed commands you run with npm run <name>.
dependenciesPackages your code needs at runtime.
devDependenciesPackages needed only to build/test.

Dependencies and version ranges

The values in dependencies are semver ranges, and the prefix matters:

  • "^16.2.3" — caret: allow minor and patch updates (>=16.2.3 <17.0.0).
  • "~16.2.3" — tilde: allow patch updates only (>=16.2.3 <16.3.0).
  • "16.2.3" — exact: only this version.
  • "*" — any version (avoid — unpredictable).

dependencies ship to production; devDependencies (build tools, types, linters) do not. Put things in the right bucket so production installs stay lean.

scripts: your project's command palette

scripts map names to shell commands:

json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "test": "vitest",
    "lint": "eslint ."
  }
}

Run them with npm run build. A few names — start, test, install — are special and run without run. Scripts can chain other scripts and reference local binaries from node_modules/.bin automatically.

The modern exports field

main is legacy. Modern packages use exports to define entry points precisely and even expose different files for ESM vs CommonJS:

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

exports also encapsulates your package — consumers can only import the paths you list, not arbitrary internal files.

Other useful fields

  • engines — declare supported Node versions: "engines": { "node": ">=18" }.
  • files — whitelist what gets published to npm (keeps the package small).
  • bin — map a command name to a script for CLI tools.
  • workspaces — manage a monorepo of packages from one root.
  • peerDependencies — packages your consumer must provide (e.g. react for a React library).

package.json vs package-lock.json

package.json declares ranges ("anything compatible with 16.2"). package-lock.json records the exact versions that were installed, so every machine gets an identical tree. Commit both; edit package.json by hand, and let npm install manage the lockfile.

Frequently asked questions

dependencies are needed when your code runs in production; devDependencies are only needed to build, test, or lint and are not installed in production installs.

It allows compatible minor and patch updates but not a new major version — ^16.2.3 means >=16.2.3 <17.0.0. The tilde (~) allows patch updates only.

Yes. It pins the exact dependency versions so every install is reproducible across machines and CI. Edit package.json by hand and let npm update the lockfile.

Try JSON Formatter

Format and validate your package.json instantly.