Anatomy of package.json

package.json answers three audiences at once. npm 2,036 reads name, version and the dependency objects; Node reads type, exports and imports when it resolves a specifier (How Node Resolves a Specifier); bundlers and publishers read files, sideEffects and publishConfig. A field one audience ignores another treats as law, which is why a manifest that "works locally" can still install broken:

A complete manifest for a small published libraryJSON
{
  "name": "@webcoding/slugify-lite",
  "version": "1.3.0",
  "license": "MIT",
  "type": "module",
  "engines": { "node": ">=20.19" },
  "exports": { ".": "./dist/index.js", "./package.json": "./package.json" },
  "types": "./dist/index.d.ts",
  "files": ["dist", "README.md"],
  "sideEffects": false,
  "repository": "github:example-org/slugify-lite",
  "scripts": { "build": "node scripts/build.js", "test": "node --test" },
  "publishConfig": { "access": "public", "provenance": true }
}

name must be at most 214 characters, URL-safe and lowercase; a scope (@webcoding/) costs nothing and gives you a namespace. license takes an SPDX identifier — "MIT", not "MIT License" — because tooling parses it. engines is advisory unless the installing user sets engine-strict, so treat it as documentation.

files is an allowlist: everything outside it is left out of the tarball, far safer than an .npmignore blocklist that silently ships the sources or secrets you forget to add to it. sideEffects: false promises bundlers that importing your module and not using the result changes nothing, which is what makes tree shaking legal — and drops live code if the promise is false.