Build a valid package.json without guessing field names
Every Node.js project is anchored by a package.json — the manifest that declares its name, version, dependencies, and the scripts you run with npm run. A typo in a field name or an invalid version range silently breaks tooling. This builder collects the standard fields, validates the ones with strict rules (name casing, semver versions), and emits clean, indented JSON ready to drop into your project root.
How it works
The builder assembles a single JSON object from your inputs, only including fields you actually fill in so the output stays minimal. A few fields have rules the tool enforces:
- name is lowercased and stripped of illegal characters; scoped names like
@acme/widgetare preserved. - version follows semantic versioning,
MAJOR.MINOR.PATCH, defaulting to1.0.0. - scripts, dependencies, and devDependencies become nested objects.
Dependency versions use npm range syntax: ^1.2.3 allows compatible minor and patch updates, ~1.2.3 allows only patch updates, and an exact 1.2.3 pins the version. The output is serialized with two-space indentation, the convention npm itself writes.
Field-by-field guide
name
Must be lowercase, URL-safe, and at most 214 characters. Hyphens are allowed; spaces and uppercase are not. Scoped packages follow @scope/package-name. The scope must also be all-lowercase.
version
Semantic versioning: MAJOR.MINOR.PATCH. Increment MAJOR for breaking changes, MINOR for new backwards-compatible features, PATCH for bug fixes. Pre-release labels like 1.0.0-beta.1 are valid semver.
type
Controls how Node interprets .js files. "module" makes .js files use ES module syntax (import/export). "commonjs" (the default when omitted) uses require. Mix them by giving CommonJS files the .cjs extension and ES module files the .mjs extension regardless of type.
main vs exports
main is the legacy entry point used by require(). exports is the modern field that supports conditional exports (CommonJS vs ESM, Node vs browser) and restricts which files are accessible from outside the package. For new packages, prefer exports; for libraries targeting older Node versions, include both.
scripts
Common conventions:
{
"scripts": {
"build": "tsc",
"dev": "ts-node src/index.ts",
"test": "jest",
"lint": "eslint src",
"start": "node dist/index.js"
}
}
dependencies vs devDependencies
dependencies are installed in production and when someone else installs your package as a dependency. devDependencies are installed only in development (npm install) and skipped when the package is installed by another project (npm install --omit=dev). Putting a runtime library in devDependencies causes production failures; putting a build tool in dependencies bloats production installs.
engines
Declares the Node and npm versions your package requires:
{
"engines": { "node": ">=18.0.0", "npm": ">=9.0.0" }
}
This is advisory by default. To make it a hard constraint, callers need engine-strict=true in their .npmrc.
After generating
Save the file as package.json at the project root, then run npm install to generate package-lock.json, which pins the exact resolved versions of every transitive dependency. Commit package-lock.json to version control — it guarantees reproducible installs across machines and CI environments.