What package.json Actually Does
Every Node.js project has a package.json at its root, and it is far more than metadata. It is the project's identity card (name, version, description), its instruction manual (scripts), and its bill of materials (dependencies) — the three things npm, yarn, pnpm and every CI system read before doing anything. A malformed package.json breaks installs, publishes and deploys, which is why generating it correctly matters more than it looks.
The file must be valid JSON — no comments, no trailing commas — and certain fields have strict rules. The name must be lowercase, URL-safe, and under 214 characters; scoped names like @myorg/mypackage are allowed. The version must be valid semver (major.minor.patch). Get these right and the entire npm ecosystem just works; get them wrong and you will debug cryptic errors at the worst possible moment.
Scripts: Your Project's Command Palette
The scripts field maps short commands to shell invocations, turning npm run test into a full test pipeline. A healthy project usually has a small, conventional set: start launches the app, dev starts it with reloading, build compiles or bundles, test runs the suite, and lint checks style. Newcomers to a codebase read scripts first — they are the fastest way to learn how a project is built, tested and shipped.
Scripts worth standardising
- start: node index.js — what production runs.
- dev: nodemon index.js or vite — the local development loop.
- build: tsc, vite build, or esbuild — produces deployable output.
- test: jest, vitest, or mocha — the whole suite in one command.
- lint / format: eslint . and prettier --write . — keep the codebase clean.
- prepare: runs automatically on install — ideal for building before publish.
Dependencies vs DevDependencies
Dependencies are what your code needs at runtime — express, react, lodash. DevDependencies are what you need to build and test it — typescript, jest, eslint. The distinction matters at deploy time: production installs use npm install --production (or --omit=dev), which skips devDependencies, so anything your running app imports must be a real dependency. A surprisingly common production crash is importing a package that was accidentally saved as a devDependency.
The version specifiers next to each dependency are a contract about updates. ^4.18.0 (caret) allows minor and patch updates — the default and usually right. ~4.18.0 (tilde) allows only patch updates — safer for finicky libraries. 4.18.0 pinned exactly — reproducible but frozen. Understanding these three symbols prevents the two classic failures: mysterious breakage after a fresh install (too loose) and missing security patches (too tight).
The lockfile completes the picture
package.json declares intentions; package-lock.json records the exact resolved tree. Always commit the lockfile for applications — it is what makes npm ci reproducible across machines and CI. Libraries traditionally omit it, since consumers resolve their own trees. Either way, the version ranges in package.json are the policy, and the lockfile is the snapshot.
Fields Beyond the Basics
As projects grow, a few more fields earn their place. type: module switches the project to ES modules (import syntax) instead of CommonJS. main (or exports) declares the entry point consumers import. engines documents the Node version required — engines: {node: >=18} stops teammates from running your code on ancient runtimes. files whitelists what gets published to npm, keeping test fixtures and config out of the tarball. And bin turns your package into a CLI command when installed globally.
Private vs publishable
Set private: true for anything that must never hit the npm registry — it makes npm publish refuse outright, a one-line insurance policy against the most embarrassing mistake in open source. Public packages additionally want repository, bugs, homepage, license and keywords fields so the registry page looks professional and the package is discoverable.
Common package.json Mistakes
- Trailing commas or comments: invalid JSON — the single most common syntax error.
- Uppercase or spaced names: npm rejects them at publish time.
- Runtime imports in devDependencies: crashes production installs that omit dev deps.
- Missing start script: platforms like Heroku, Railway and Render run npm start by default.
- Wildcard versions (*): installs become non-reproducible and can break overnight.
- Editing versions by hand in node_modules: changes vanish on the next install — fix the manifest instead.
Versioning Sanely with Semver
Semantic versioning is a promise: patch bumps fix bugs, minor bumps add features without breaking, major bumps may break. Consumers rely on this promise when they write ^1.2.3. Breaking it — shipping a breaking change in a minor release — is how libraries earn distrust. When in doubt about whether a change breaks, it breaks: bump the major.
How This Generator Helps
PkgJson gives you a bento of focused cards: package identity, an add/remove scripts editor, a dependency adder that attaches versions, and a live JSON preview that updates as you type. Copy it or download it — what you see is byte-for-byte valid package.json, ready to drop into a new project.
Publishing to npm
Turning a project into a published package takes five fields and one command. Set name (scoped like @myorg/mypackage if it is yours), version, description, license and repository — then npm publish. Before that, add a files whitelist so only dist ships (not src or tests), and run npm pack to inspect the tarball that will upload. The prepublishOnly script is the ideal hook for building: it runs on publish but not on install, so putting your build there guarantees you never ship unbuilt code. For scoped private packages, configure your registry and access via .npmrc — and remember that private: true in this generator blocks accidental public publishes entirely.
Monorepos and Workspaces
When one repository holds many packages, npm, pnpm and yarn workspaces let a root package.json declare workspaces and install all dependencies in one pass with hoisting. Each workspace keeps its own package.json — generated the same way as any other — while the root orchestrates scripts across them. Versioning strategies split here: fixed versioning bumps everything together, independent versioning lets each package follow semver on its own. Changesets or similar tooling automate the changelog-and-bump workflow. If your project is growing a second package, reach for workspaces before you reach for a second repository.
Keeping Dependencies Healthy
A package.json is a living document. Run npm outdated monthly to see what has drifted, and npm audit to surface known vulnerabilities — then update deliberately, not blindly. Pin exact versions for zero-surprise deploys of critical services, or use caret ranges plus a lockfile for the standard balance. Prune unused dependencies; every unused package is attack surface and install time. And document why each major dependency exists — future you, onboarding onto this project in a year, will be grateful. The dependency card in this generator is the right place to start that habit: add each package consciously, with its version policy stated.
Scripts as documentation
Treat scripts as the project's README for machines: the scripts list should tell a newcomer everything. Standard names (start, dev, build, test, lint) mean any developer can be productive in minutes. Complex pipelines belong in scripts, not in wiki pages — a deploy script that runs build, test and migrate in order beats a five-step manual every time. Keep scripts small and composed: test:unit and test:e2e called from test is clearer than one giant command. Your future CI config will thank you.
Security Hygiene for Dependencies
Every dependency is code you did not write running with your privileges. Keep the list short, prefer well-maintained packages with many dependents, and pin versions for anything security-sensitive. Run the audit command in continuous integration and fail builds on high-severity issues. Review lockfile diffs in pull requests the way you review code — a compromised transitive dependency has been the entry point for real supply-chain attacks. The generator's dependency card is where this discipline starts: each addition should be a conscious, reviewed decision rather than a reflex.