Dependency types: dev, peer, optional

npm · Essentials · 12 min

The wrong dependency type bloats production images or breaks your library's users.

package.json has several dependency fields, each with different install behaviour:

  • dependencies: needed at runtime. Installed for your project and for anyone who installs your package.
  • devDependencies: needed only to develop, build or test (TypeScript, test runners, linters, bundlers). Installed for your project, never for consumers of a published package. Skip them in production with npm ci --omit=dev.
  • peerDependencies: "I plug into a host package the consumer provides." A React component library declares "react": "^18 || ^19" as a peer instead of bundling its own React. Since npm 7, missing peers are installed automatically, and conflicting peer ranges fail with ERESOLVE. Mark optional peers with peerDependenciesMeta.
  • optionalDependencies: installed if possible; a failure (e.g. a platform-specific native binary) does not fail the install. Your code must handle their absence.
  • bundleDependencies: shipped inside your package tarball.

For bundled frontend apps the distinction is softer, since the bundler decides what ships; many teams still keep build tools in devDependencies for faster CI installs.

--legacy-peer-deps ignores peer dependencies entirely and --force overrides conflicts. Both hide real incompatibilities; prefer fixing versions or using overrides.

Example

{
  "name": "@acme/react-charts",
  "version": "2.0.0",
  "dependencies": {
    "d3-scale": "^4.0.2"
  },
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0",
    "react-dom": "^18.0.0 || ^19.0.0"
  },
  "peerDependenciesMeta": {
    "react-dom": { "optional": true }
  },
  "optionalDependencies": {
    "fsevents": "^2.3.3"
  },
  "devDependencies": {
    "react": "^19.2.0",
    "typescript": "^5.9.0",
    "vitest": "^4.0.0"
  }
}

Remember

  • dependencies ship to consumers; devDependencies never do
  • peerDependencies: the host provides it (React, plugins)
  • optionalDependencies may fail to install
  • Avoid --legacy-peer-deps and --force as permanent fixes

Watch out for

  • Listing react as a regular dependency in a component library, causing two React copies.

Check yourself

  1. A Vite plugin needs the consumer's own copy of `vite`. Where should `vite` go?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.npmjs.com/cli/v11/configuring-npm/package-json#peerdependencies