Skip to content
FLAVIO COPES
flaviocopes.com
2026

The package.json guide

By

Learn how package.json describes a Node.js project, including scripts, dependencies, module type, package entry points, engines, and semver version ranges.

~~~

The package.json file is the manifest for a Node.js project. It records project metadata, dependencies, scripts, module settings, and package entry points.

npm reads it when you install packages or run scripts. Node.js also reads fields such as type, main, exports, and imports.

The complete field reference lives in the official npm package.json documentation.

Create a package.json file

Run this command in a new project:

npm init

npm asks a few questions and writes the file.

Use -y to accept the default values:

npm init -y

Applications can use a small package.json:

{
  "name": "notes-app",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "node --watch src/server.js",
    "start": "node src/server.js",
    "test": "node --test"
  },
  "devDependencies": {
    "eslint": "^10.6.0"
  }
}

A package published to npm needs a name and version. A private application does not need to pretend it is a public package.

The npm init guide explains the interactive and default setup modes.

package.json must be valid JSON

package.json uses JSON, not JavaScript.

Property names and string values need double quotes. Comments and trailing commas are not valid:

{
  "name": "notes-app",
  "private": true
}

name and version

The name identifies a package:

{
  "name": "string-tools"
}

Scoped package names start with an organization or username:

{
  "name": "@flaviocopes/string-tools"
}

The version identifies a release:

{
  "version": "2.1.0"
}

Published packages need both fields. Together they identify one exact package release.

description, keywords, and license

These fields help people understand a published package:

{
  "description": "Small string utilities for Node.js",
  "keywords": ["strings", "utilities"],
  "license": "MIT"
}

Use a valid SPDX license identifier such as MIT, ISC, or Apache-2.0.

author and contributors

The author can be a string:

{
  "author": "Flavio Copes <your@email.com> (https://flaviocopes.com)"
}

Or an object:

{
  "author": {
    "name": "Flavio Copes",
    "email": "your@email.com",
    "url": "https://flaviocopes.com"
  }
}

Use contributors for additional people:

{
  "contributors": [
    {
      "name": "Roger"
    },
    {
      "name": "Syd"
    }
  ]
}

homepage, bugs, and repository

These fields connect a published package to its documentation and source code:

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

If a package lives inside a monorepo, repository.directory can point to its subfolder.

private

Set private to true for an application or package that must not be published:

{
  "private": true
}

npm refuses to publish a package marked private. This protects against accidental publication.

scripts

Scripts give your project memorable commands:

{
  "scripts": {
    "dev": "node --watch src/server.js",
    "start": "node src/server.js",
    "test": "node --test",
    "lint": "eslint ."
  }
}

Run a custom script with:

npm run dev

start and test have shorter forms:

npm start
npm test

You can pass arguments to the underlying command after --:

npm test -- --test-name-pattern="user login"

The npm scripts documentation covers lifecycle scripts and the environment available to commands.

dependencies

dependencies lists packages required when the application runs.

This command adds a dependency:

npm install fastify

npm saves regular installs to dependencies by default and records the selected version range.

devDependencies

devDependencies contains tools needed to develop, test, or build the project:

{
  "devDependencies": {
    "eslint": "^10.6.0",
    "prettier": "3.9.5"
  }
}

Add one with:

npm install --save-dev eslint

A normal npm install installs both dependency groups. Production installs can omit development dependencies with npm install --omit=dev.

The official npm dependency guide explains when to use each field.

type

The type field tells Node.js how to interpret .js files:

{
  "type": "module"
}

With "type": "module", .js files use ES Modules. With "type": "commonjs", they use CommonJS.

The explicit .mjs and .cjs extensions always select ES Modules and CommonJS respectively.

Node recommends being explicit about type, especially for published packages. See the Node.js module system rules.

main and exports

main is the traditional package entry point:

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

exports is the modern way to define a package’s public entry points:

{
  "exports": {
    ".": "./dist/index.js",
    "./format": "./dist/format.js"
  }
}

When exports is present, package users cannot import unlisted internal paths. This lets package authors define a clear public API.

Conditional exports can provide different files to import and require, but test that setup carefully. The Node.js package entry points guide explains the available patterns.

engines

The engines field documents supported runtime versions:

{
  "engines": {
    "node": ">=22"
  }
}

npm usually treats incompatible engines as a warning unless engine-strict is enabled. Do not rely on this field as the only runtime check.

files

For a published package, files controls what npm includes:

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

Some files, including package.json, README, and the license, are always included. Test the final package before publishing:

npm pack --dry-run

workspaces

Workspaces let one root project manage several related packages:

{
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

npm links workspace packages during installation and can run commands in one or all workspaces. See the official npm workspaces guide.

Package version ranges

Dependencies use semantic version ranges:

{
  "dependencies": {
    "first-package": "2.3.1",
    "second-package": "~2.3.1",
    "third-package": "^2.3.1"
  }
}

The common rules are:

Caret ranges behave differently before version 1.0.0. For example, ^0.13.0 stays below 0.14.0.

Use the npm semantic versioning guide when a range is not obvious.

package-lock.json records the exact resolved dependency tree. Commit it for applications so other developers and CI can reproduce the same install.

Tagged: Node.js · All topics
~~~

Related posts about node: