Skip to main content

Guide

patch-package vs pnpm patch vs yarn patch 2026

Compare patch-package, pnpm patch, and yarn patch for fixing bugs in third-party npm dependencies. Creating patches, maintaining across installs, monorepo.

·PkgPulse Team·
0

TL;DR

patch-package is the original tool for patching npm dependencies — edit files in node_modules/, run npx patch-package <pkg>, and it saves a .patch file that auto-applies on install. pnpm patch is pnpm's built-in patching — same workflow, integrated into the package manager, stored in patches/. yarn patch is Yarn Berry's equivalent — yarn patch <pkg> opens an editable copy, yarn patch-commit saves the diff. In 2026: use your package manager's built-in patching (pnpm patch or yarn patch) if possible, fall back to patch-package for npm.

Key Takeaways

  • patch-package: ~3M weekly downloads — works with npm, yarn classic, pnpm, package-manager-agnostic
  • pnpm patch: built into pnpm — zero extra dependencies, pnpm patch / pnpm patch-commit
  • yarn patch: built into Yarn Berry (v2+) — yarn patch / yarn patch-commit
  • Patches are git-trackable .patch files — your team gets the fix automatically
  • All three auto-apply patches on install — postinstall hook or built-in integration
  • Always file an upstream PR — patches are temporary fixes, not permanent solutions

When to Patch Dependencies

Patch when:
  ✅ Critical bug fix — upstream hasn't released yet
  ✅ TypeScript type errors — @types package has wrong types
  ✅ Security vulnerability — can't upgrade (breaking changes), patch the specific issue
  ✅ Framework compatibility — small adjustment for your specific use case

Don't patch when:
  ❌ Feature additions — fork the package instead
  ❌ Major changes — too hard to maintain across version updates
  ❌ Upstream fix is available — just upgrade

Always:
  1. File an issue/PR upstream
  2. Add a comment in the patch explaining WHY
  3. Check on every dependency upgrade if the patch is still needed

patch-package

patch-package — package-manager-agnostic:

Setup

npm install -D patch-package

# Add postinstall hook to auto-apply patches:
# package.json:
{
  "scripts": {
    "postinstall": "patch-package"
  }
}

Create a patch

# 1. Edit the file directly in node_modules:
#    Fix the bug in node_modules/some-package/dist/index.js

# 2. Create the patch:
npx patch-package some-package

# Creates: patches/some-package+1.2.3.patch
# This is a standard unified diff file

# 3. Commit the patch:
git add patches/
git commit -m "fix: patch some-package to fix XYZ bug"

Example: fixing a TypeScript type

# Problem: @types/express has wrong types for req.query
# Edit: node_modules/@types/express/index.d.ts

# Create scoped package patch:
npx patch-package @types/express

# Creates: patches/@types+express+4.17.21.patch

Example patch file

# patches/some-package+1.2.3.patch
diff --git a/node_modules/some-package/dist/index.js b/node_modules/some-package/dist/index.js
index abc1234..def5678 100644
--- a/node_modules/some-package/dist/index.js
+++ b/node_modules/some-package/dist/index.js
@@ -42,7 +42,7 @@ function processData(input) {
-  if (input.length = 0) {
+  if (input.length === 0) {
     return [];
   }

Version-specific patches

# Patch applies to exact version by default:
# patches/react-dom+18.2.0.patch  → only applies to 18.2.0

# When you upgrade react-dom to 18.3.0:
# patch-package will WARN if the patch doesn't apply cleanly
# → Review and recreate the patch if needed

pnpm patch

pnpm patch — built-in pnpm patching:

Workflow

# 1. Start patching — pnpm creates an editable copy:
pnpm patch some-package@1.2.3

# Output:
# You can now edit the following folder: /tmp/.pnpm-patch-xxx/some-package

# 2. Edit the file in the temporary folder:
# Edit /tmp/.pnpm-patch-xxx/some-package/dist/index.js

# 3. Commit the patch:
pnpm patch-commit /tmp/.pnpm-patch-xxx/some-package

# Creates: patches/some-package@1.2.3.patch
# Updates package.json:

package.json integration

{
  "pnpm": {
    "patchedDependencies": {
      "some-package@1.2.3": "patches/some-package@1.2.3.patch"
    }
  }
}

Auto-application

# Patches apply automatically on pnpm install:
pnpm install
# → Applying patches...
# → Patched some-package@1.2.3

# No postinstall script needed — pnpm handles it natively

Removing a patch

# Remove from patchedDependencies in package.json
# Delete the patch file:
rm patches/some-package@1.2.3.patch

# Reinstall:
pnpm install

yarn patch

yarn patch — Yarn Berry (v2+) built-in:

Workflow

# 1. Start patching:
yarn patch some-package

# Output:
# ➤ YN0000: Package some-package@npm:1.2.3 got extracted to /tmp/xfs-xxx

# 2. Edit files in the extracted folder:
# Edit /tmp/xfs-xxx/dist/index.js

# 3. Save the patch:
yarn patch-commit -s /tmp/xfs-xxx

# Creates: .yarn/patches/some-package-npm-1.2.3-xxxxx.patch
# Updates package.json:

package.json integration

{
  "resolutions": {
    "some-package@1.2.3": "patch:some-package@npm%3A1.2.3#~/.yarn/patches/some-package-npm-1.2.3-xxxxx.patch"
  }
}

Auto-application

# Patches apply automatically on yarn install:
yarn install
# → some-package patched

# No postinstall hook needed

Best Practices

Always document your patches

# In your codebase, create a PATCHES.md or add comments:

# patches/some-package+1.2.3.patch
# WHY: Fixes null reference when input is empty array
# UPSTREAM: https://github.com/owner/some-package/issues/123
# PR: https://github.com/owner/some-package/pull/456
# REMOVE WHEN: some-package >= 1.2.4 is released

CI verification

// package.json (patch-package):
{
  "scripts": {
    "postinstall": "patch-package",
    "verify-patches": "patch-package --error-on-fail"
  }
}
# GitHub Actions:
- name: Install dependencies
  run: npm ci
  # postinstall runs patch-package automatically

- name: Verify patches applied
  run: npx patch-package --error-on-fail
  # Fails CI if any patch doesn't apply cleanly

Monorepo patches

# patch-package in monorepo (from root):
npx patch-package some-package --patch-dir patches/

# pnpm workspace — patches defined in root package.json:
{
  "pnpm": {
    "patchedDependencies": {
      "some-package@1.2.3": "patches/some-package@1.2.3.patch"
    }
  }
}

Feature Comparison

Featurepatch-packagepnpm patchyarn patch
Package managernpm, yarn, pnpmpnpm onlyYarn Berry only
Extra dependencyYesNo (built-in)No (built-in)
WorkflowEdit node_modules → patchpnpm patch → edit copy → commityarn patch → edit copy → commit
Auto-applypostinstall hookBuilt-inBuilt-in
Config locationpatches/ dirpackage.json patchedDependenciespackage.json resolutions
Scoped packages
Monorepo
Patch on fail--error-on-failBuilt-in errorBuilt-in error
Weekly downloads~3Mbuilt-inbuilt-in

When to Use Each

Use patch-package if:

  • Using npm as your package manager (npm has no built-in patching)
  • Need a tool that works across npm, yarn classic, and pnpm
  • Team has mixed package manager usage

Use pnpm patch if:

  • Already using pnpm — zero extra dependencies, native integration
  • pnpm's patchedDependencies config is clean and declarative
  • Patches apply automatically without postinstall hooks

Use yarn patch if:

  • Using Yarn Berry (v2+) — built-in, native workflow
  • Yarn's resolution protocol handles patching transparently

General advice:

  • Always file upstream PRs — patches are temporary
  • Add comments explaining why each patch exists
  • Review patches on every dependency upgrade
  • Keep patches small — if you need major changes, fork instead

Real-World Patching Scenarios in 2026

Understanding when and how teams actually patch dependencies clarifies which tool fits your workflow.

Scenario 1: TypeScript type error in a published package

The most common patching scenario — a package ships incorrect types that cause TypeScript errors in your project, but the runtime behavior is correct. You cannot upgrade because no fix is released yet.

# Using pnpm patch (if on pnpm):
pnpm patch some-package@2.1.0

# Edit the .d.ts file in the temp directory
# For example, fix: null | undefined → undefined only

pnpm patch-commit /tmp/.pnpm-patch-xxx/some-package
# → Creates patches/some-package@2.1.0.patch
# → Updates package.json patchedDependencies

A good patch header comment that will save future you time:

# patches/some-package@2.1.0.patch
# WHY: TypeScript 5.5 strict mode flags null return on processItems()
#      Upstream fixed in v2.1.1 (not yet released as of 2026-02-14)
# ISSUE: https://github.com/owner/some-package/issues/789
# REMOVE WHEN: version >= 2.1.1

Scenario 2: Security vulnerability with no fix available

A CVE is published for a transitive dependency. Upgrading the parent would require major refactoring. Patching the vulnerable line buys time until a proper fix is available.

# Check the specific vulnerable code
cat node_modules/vulnerable-pkg/src/parser.js | grep -n "eval("

# patch-package approach (works with any package manager):
# 1. Edit the file in node_modules to remove/neutralize the vulnerability
# 2. npx patch-package vulnerable-pkg
# 3. git add patches/ && git commit -m "security: patch CVE-2025-XXXX in vulnerable-pkg"
// Document in your security tracking:
{
  "scripts": {
    "postinstall": "patch-package",
    "audit:patches": "echo 'Patches: patches/vulnerable-pkg+1.0.0.patch (CVE-2025-XXXX, remove when >= 1.0.1)'"
  }
}

Scenario 3: Bug in a Vite plugin that only affects your specific setup

Framework-specific tooling is often patched to fix issues that only manifest in edge case configurations. The upstream maintainer is responsive but their release cycle is slow.

# Quick turnaround with pnpm patch:
pnpm patch vite-plugin-broken@3.2.1
# Fix the specific method that causes issues with your config
pnpm patch-commit /tmp/.pnpm-patch-xxx/vite-plugin-broken

Community Adoption in 2026

ToolWeekly DownloadsGitHub StarsPackage Manager
patch-package~3M10,000+npm, yarn classic, pnpm
pnpm patchBuilt into pnpm(part of pnpm: 30K stars)pnpm only
yarn patchBuilt into Yarn Berry(part of Yarn: 41K stars)Yarn Berry only

patch-package's 10K GitHub stars and 3M weekly downloads reflect its status as the canonical solution for this problem over many years. Most online tutorials, Stack Overflow answers, and blog posts about patching npm dependencies reference patch-package — this documentation lead is self-reinforcing.

The "built-in" tools (pnpm patch, yarn patch) do not show separately in npm download stats, but they are increasingly the default choice for new projects. In 2026, if you are starting a new project on pnpm, there is no reason to add patch-package as an extra dependency — pnpm patch does the same thing with zero setup.

Migration Guide

From patch-package to pnpm patch:

# Existing patches in patches/ dir created by patch-package
# Convert to pnpm's format:

# 1. Apply the existing patch manually to node_modules:
git apply patches/some-package+1.2.3.patch

# 2. Use pnpm patch to create a pnpm-compatible patch:
pnpm patch some-package@1.2.3
# Edit the temp folder — copy your existing changes there

pnpm patch-commit /tmp/.pnpm-patch-xxx/some-package
# Creates patches/some-package@1.2.3.patch (pnpm format)
# Updates package.json patchedDependencies

# 3. Remove patch-package:
npm uninstall patch-package
# Remove postinstall: "patch-package" from package.json
# Remove old patches/ files (replaced by pnpm's patches/)

# 4. Verify:
pnpm install
# Should say: "Applying patches..." for each patch

From patch-package to yarn patch:

# 1. For each existing patch:
yarn patch some-package
# Edit the temp folder with your existing changes

yarn patch-commit -s /tmp/xfs-xxx/some-package
# Creates .yarn/patches/some-package-npm-1.2.3-xxxx.patch
# Updates package.json resolutions

# 2. Remove patch-package:
yarn remove patch-package
# Remove postinstall hook from package.json

# 3. Delete old patches/ directory:
rm -rf patches/

Patch Maintenance Best Practices

Patches accumulate over time and can become a maintenance burden if not managed proactively. These practices apply regardless of which patching tool you use.

Document every patch. Add a comment at the top of each patch file explaining why it exists, what upstream issue it references, and under what conditions it can be removed:

# patches/some-package+2.3.1.patch
# WHY: processList() returns null on Windows paths with spaces (upstream bug #456)
# UPSTREAM: Fixed in v2.3.2 (not yet released as of 2026-02-01)
# REMOVE WHEN: upgrade to >= 2.3.2
# TESTED: Verified fix on Windows, macOS, Linux CI

Audit patches after every dependency upgrade. Set up a CI step that verifies patches still apply cleanly after npm install:

# In CI after install, verify patches apply:
npx patch-package --error-on-fail   # Exits non-zero if any patch fails
# or for pnpm:
pnpm install  # Will error if a patchedDependency's patch doesn't apply

Remove patches when no longer needed. After upgrading a patched dependency to a version where the issue is fixed, delete the patch file and remove the entry from package.json. Leaving stale patches creates noise and can conflict with future upgrades.

Prefer upstream contributions over long-lived patches. A patch that lives longer than 3 months usually means the upstream maintainer is unresponsive or the fix is controversial. At that point, consider forking the package (with a different npm scope) or replacing it with an alternative. Patches are a short-term fix, not a long-term strategy.

Test patches in CI on multiple OS environments. Patches often fix Windows-specific issues that developers do not catch locally. Add a matrix CI job that tests on Ubuntu, macOS, and Windows (or at least ubuntu + windows) whenever you add a patch that touches path handling or filesystem operations.

Monorepo Hoisting and Patch Application Edge Cases

In monorepos where dependencies are hoisted to a shared root node_modules, patch-package must be configured at the root level to reach the hoisted dependencies. Running npx patch-package react from a workspace package directory will create the patch in that package's directory, but if React is hoisted to the root, the patch needs to be applied from the root instead. The --patch-dir flag lets you specify where patches are stored and read, allowing a single root-level patches/ directory to serve all workspace packages.

Pnpm's workspace architecture handles patches more cleanly: the patchedDependencies configuration in the root package.json applies patches to the shared virtual store. When pnpm installs packages in a workspace, it reads the root patchedDependencies and applies patches to the virtual store entries before linking them into workspace packages. This means a patch applied once at the workspace root is automatically effective for all workspace packages that depend on the patched version — no per-package patch configuration needed.

Handling TypeScript Declaration File Patches

A frequently overlooked use case for dependency patching is fixing incorrect TypeScript declaration files without modifying the runtime JavaScript. When a package ships wrong types — a callback that the docs say is optional but the types mark as required, or a return type of void when the function actually returns a value — the TypeScript compiler errors are real even if the runtime behavior is correct. Patching the .d.ts file fixes the TypeScript error without affecting the published JavaScript.

All three tools support patching .d.ts files with the same workflow as patching .js files. For pnpm patch, you edit the declaration file in the temporary directory and commit with pnpm patch-commit. The resulting patch file is a standard unified diff targeting the .d.ts file path. One important nuance: declaration files often reference other types via import type statements that resolve relative to the package root. Edits that change these references require corresponding changes to the referenced files, and the patch must include all modified files — a single-file patch that adds a new type import but doesn't add the corresponding type definition will compile on the machine where you created the patch but fail on fresh installs.

Scoped package declaration patches have a naming quirk worth knowing: the @ prefix in scoped package names is problematic in file paths on Windows. patch-package escapes the @ as a + in the patch filename — @types/express+4.17.21.patch — because @ is not valid in file paths on some operating systems. pnpm and yarn handle this differently, using @types+express@4.17.21.patch and URL-encoding the @ respectively. When migrating patches from one tool to another, these naming differences require renaming patch files and updating the configuration entries in package.json — a minor but easy-to-overlook step that causes "patch not found" errors if skipped.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on patch-package v8.x, pnpm v9.x, and yarn v4.x.

Compare package management and developer tooling on PkgPulse →

See also: taze vs npm-check-updates vs npm-check and pkg-types vs read-pkg vs read-package-up, archiver vs adm-zip vs JSZip (2026).

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.