The package.json guide
By Flavio Copes
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:
2.3.1accepts only version2.3.1~2.3.1accepts patch updates below2.4.0^2.3.1accepts compatible updates below3.0.0>=2.3.1 <3defines an explicit range
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.
Related posts about node: