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.
{
"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
| Field | What it does |
|---|---|
name | Package name (lowercase, URL-safe). Required to publish. |
version | Semantic version (major.minor.patch). |
type | "module" for ES modules, "commonjs" (default) for require. |
main | Legacy entry point when the package is imported. |
scripts | Named commands you run with npm run <name>. |
dependencies | Packages your code needs at runtime. |
devDependencies | Packages 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:
{
"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:
{
"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.reactfor 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.