pnpm 03 📦 Initializing Projects and package.json Setup
The pnpm init command is the entry point for every new project, but its behavior has evolved significantly in pnpm 10. The default module system is now ESM ("type": "module"), a --bare flag strips the manifest down to only required fields, and version pinning is handled through the devEngines.packageManager field rather than the older packageManager field alone. Understanding these defaults prevents the common surprise of generating a package.json that is either too bloated for an application or too sparse for a published library.
Key point: pnpm init creates a package.json with "type": "module" by default. Use --bare for applications that will never be published, and use --init-package-manager to pin the pnpm version in a way that the lockfile records.
Why initialization behavior changed
The CommonJS-to-ESM migration problem. For years, Node.js defaulted to CommonJS. A package.json without a type field was treated as CommonJS, and import statements failed unless the file used .mjs. The ecosystem has shifted toward ESM. pnpm 10 reflects this shift: pnpm init now writes "type": "module" by default, matching the direction of modern Node.js tooling and eliminating the need for .mjs extensions . Projects that still require CommonJS can set --init-type commonjs.
The application-versus-library problem. A package.json for a published library needs name, version, description, main, keywords, author, and license. A package.json for a private application needs almost none of these. The license field, in particular, can cause legal confusion when an application unintentionally inherits the default ISC license. The --bare flag, added in pnpm 10.25, strips the manifest to only the required fields, removing the publishing metadata that applications do not need .
The version pinning problem. Pinning pnpm to a specific version ensures reproducibility. The --init-package-manager flag writes the pin to two places: devEngines.packageManager (which captures the resolved version in pnpm-lock.yaml) and packageManager (which Corepack reads) . The lookup checks the latest tag on the registry and pins only if that version is newer than the running pnpm. If the registry is unreachable, it pins the running version without failing.
The workspace manifest problem. pnpm init creates the manifest in the current directory, not in the nearest ancestor with a package.json . This matters in monorepos: running pnpm init inside packages/utils creates a manifest there, not at the repository root. For workspace root initialization, the manifest is created at the root, and pnpm-workspace.yaml declares which subdirectories are packages.
The trade-off. The default ESM output is correct for most new projects but breaks compatibility with older CommonJS-only tooling. The --bare flag is correct for applications but removes fields that npm publish requires. The choices are explicit and reversible, but they must be made deliberately.
a. Basic initialization and module system selection
The pnpm init command creates a package.json in the current directory. If a manifest already exists, the command fails with ERR_PNPM_PACKAGE_JSON_EXISTS .
# Default: ESM module system
pnpm init
# Explicit CommonJS
pnpm init --init-type commonjs
# Bare manifest for applications
pnpm init --bare
The default package.json includes name, version, and type: "module". The name defaults to the directory name, and version defaults to 1.0.0 unless initVersion is configured . The --bare flag removes description, main, keywords, author, and license, leaving only the fields required for installation and script execution.
b. Configuring defaults via init settings
The fields written by pnpm init are configurable through settings that can be placed in pnpm-workspace.yaml, a global configuration file, or environment variables . This allows teams to standardize new manifests without passing flags on every invocation.
# pnpm-workspace.yaml (or global config)
initLicense: MIT
initAuthorName: "Alex Chen"
initAuthorEmail: "alex@example.com"
initAuthorUrl: "https://alex.dev"
initVersion: "0.1.0"
initType: "module"
The author field is assembled in the Name <email> (url) format. If none of the three author settings are configured, no author field is written. The initLicense setting is particularly useful for organizations that want a consistent license across all new packages without manually editing each manifest.
c. Pinning the package manager and understanding the fields
The --init-package-manager flag pins the project to a pnpm version. The pin is written to both devEngines.packageManager and packageManager .
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"devEngines": {
"packageManager": "pnpm@10.25.0"
},
"packageManager": "pnpm@10.25.0"
}
The devEngines.packageManager field is read by pnpm itself to resolve the exact version and record it in the lockfile. The packageManager field is read by Corepack, which accepts only exact versions. Writing both ensures compatibility with both mechanisms.
The engines field, separate from devEngines, specifies the Node.js and pnpm versions the project supports. During local development, pnpm fails with an error if its version does not match the engines constraint . When the package is installed as a dependency, the check is advisory unless engineStrict is enabled.
The devEngines.runtime field, added in pnpm 10.14, allows specifying JavaScript runtimes (Node.js, Deno, Bun) that pnpm installs automatically. This is useful for projects that need a specific Node.js version for scripts or CLI tools .
Complete Example Session
# ============================================
# PART 1: BASIC INIT
# ============================================
mkdir my-app && cd my-app
pnpm init
# Creates package.json with type: module
# ============================================
# PART 2: INSPECT THE DEFAULT MANIFEST
# ============================================
cat package.json
# {
# "name": "my-app",
# "version": "1.0.0",
# "type": "module"
# }
# ============================================
# PART 3: BARE INITIALIZATION
# ============================================
rm package.json
pnpm init --bare
cat package.json
# {
# "name": "my-app",
# "version": "1.0.0"
# }
# ============================================
# PART 4: PIN PNPM VERSION
# ============================================
rm package.json
pnpm init --init-package-manager
cat package.json
# Includes devEngines.packageManager and packageManager
# ============================================
# PART 5: CONFIGURE DEFAULTS
# ============================================
# Add to pnpm-workspace.yaml or global config:
# initLicense: MIT
# initAuthorName: "Alex Chen"
rm package.json
pnpm init
# Manifest includes license and author fields.
# ============================================
# PART 6: ADD ENGINE CONSTRAINTS
# ============================================
# Edit package.json:
# "engines": { "node": ">=20", "pnpm": ">=10" }
pnpm install
# pnpm verifies its version against the constraint.
The six parts trace initialization: default ESM manifest, inspection, bare mode, version pinning, configured defaults, and engine constraints.
Quick Reference
pnpm init Flags
| Flag | Effect |
|---|---|
--bare | Only required fields (name, version) |
--init-type <type> | module (default) or commonjs |
--init-package-manager | Pin pnpm version in devEngines and packageManager |
Init Configuration Settings
| Setting | Default | Purpose |
|---|---|---|
initVersion | 1.0.0 | Version written to manifest |
initLicense | (none) | License field |
initType | module | Module system |
initAuthorName | (none) | Author name |
Manifest Fields
| Field | Purpose |
|---|---|
type | module (ESM) or absent (CJS) |
devEngines.packageManager | pnpm version pin (lockfile-aware) |
packageManager | Corepack-compatible pin |
engines | Node/pnpm version constraints |
Best Practices
✅ Do This:
pnpm init --bare
<!-- Applications do not need publishing metadata --> # ✅
pnpm init --init-package-manager
<!-- Pin pnpm version for reproducibility --> # ✅
initLicense: MIT
<!-- Standardize license in new manifests --> # ✅
❌ Don’t Do This:
# Assume default is CommonJS
<!-- pnpm 10 defaults to ESM --> # ❌
# Leave license field in private applications
<!-- Can imply unintended licensing --> # ❌
# Run pnpm init in a subdirectory of a monorepo root
<!-- Creates a nested manifest unexpectedly --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
import fails after init | Default is now ESM | Use require or set --init-type commonjs |
| Manifest already exists | Running init twice | Delete or edit existing package.json |
| Version pin not respected | Only packageManager set, not devEngines | Use --init-package-manager |
pnpm add fails | No package.json in directory | Run pnpm init first |
| Author field malformed | Only one of name/email/url set | Set all three for complete format |
Real-World Examples
1. Default ESM Init
pnpm init
2. CommonJS Init
pnpm init --init-type commonjs
3. Bare Init
pnpm init --bare
4. Pin pnpm
pnpm init --init-package-manager
5. Config License
initLicense: MIT
6. Config Author
initAuthorName: "Alex Chen"
initAuthorEmail: "alex@example.com"
7. Engine Constraint
{ "engines": { "node": ">=20" } }
8. Runtime Declaration
{ "devEngines": { "runtime": "node@24" } }
9. Inspect Manifest
pnpm pkg get name version
10. Fix Manifest
pnpm pkg fix
Visual
Initialization Decision Flow
┌──────────────────────────────────────────────┐
│ Will this package be published? │
└──────────┬───────────────────────┬───────────┘
│ YES │ NO
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ pnpm init │ │ pnpm init --bare │
│ (full manifest) │ │ (name, version only)│
└──────────────────────┘ └──────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Module system? │
│ ├─ ESM (default) → pnpm init │
│ └─ CommonJS → --init-type commonjs │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Pin pnpm version? │
│ ├─ YES → --init-package-manager │
│ └─ NO → (no pin) │
└──────────────────────────────────────────────┘
Version Pin Placement
┌──────────────────────────────────────────────┐
│ package.json │
│ │
│ devEngines.packageManager: "pnpm@10.25.0" │
│ └── Read by pnpm, recorded in lockfile │
│ │
│ packageManager: "pnpm@10.25.0" │
│ └── Read by Corepack │
└──────────────────────────────────────────────┘
Both fields are written by --init-package-manager.
Summary
| Item | Value |
|---|---|
| Default module type | module (ESM) |
| Bare flag | --bare |
| CommonJS flag | --init-type commonjs |
| Version pin flag | --init-package-manager |
| Pin fields | devEngines.packageManager, packageManager |
| Default version | 1.0.0 |
| Config file | pnpm-workspace.yaml or global |
Key takeaways:
pnpm initdefaults to ESM. Thetype: "module"field is written automatically. Projects requiring CommonJS must pass--init-type commonjs.--bareis for applications. It removes publishing metadata (license, author, description) that private applications do not need .- Version pinning uses two fields.
devEngines.packageManagerrecords the resolved version in the lockfile;packageManagerprovides Corepack compatibility . - Initialization is configurable.
initLicense,initAuthorName, andinitVersionstandardize new manifests across a team . pnpm initoperates on the current directory. In a monorepo, running it inside a subdirectory creates a nested manifest, not a root manifest .
Remember: pnpm init is not a passive command. It writes a manifest with defaults that reflect modern Node.js conventions (ESM) and pnpm’s version management (dual pinning). Choose --bare for applications, --init-type commonjs for legacy compatibility, and --init-package-manager for reproducibility. The defaults are sensible, but they are not universal.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!