---
name: composer-patching
description: 'Create and apply Composer patches for Drupal contrib modules and core. Use when: applying a Drupal.org issue queue patch, creating a local patch from your own code changes, fixing a contrib module bug without forking, adding patches to composer.patches.json, debugging a patch that fails to apply, updating a patch after a module upgrade. Covers: cweagans/composer-patches setup, composer.patches.json format, patchLevel for core, local patch creation with git diff, remote patches from drupal.org, patch file naming convention, common failure modes.'
---

# Composer Patching

We use `cweagans/composer-patches` with a separate `composer.patches.json` file.
Patches live in `patches/` at the project root. Remote patches reference the drupal.org URL directly.

## Prerequisites — Verify Setup

These keys must already exist in `composer.json` before any patch will apply.

### `require` block
```json
"cweagans/composer-patches": "^1.7"
```

### `extra` block
```json
"extra": {
    "patches-file": "composer.patches.json",
    "enable-patching": true,
    "composer-exit-on-patch-failure": true,
    "patchLevel": {
        "drupal/core": "-p2"
    }
}
```
> `patchLevel -p2` is required for Drupal core patches from drupal.org. Leave it
> out or use `-p1` for contrib modules.

### `config.allow-plugins` block
```json
"config": {
    "allow-plugins": {
        "cweagans/composer-patches": true
    }
}
```

---

## `composer.patches.json` Format

```json
{
  "patches": {
    "drupal/package_name": {
      "Human-readable description of what this fixes": "patches/package_name-short-desc.patch"
    },
    "drupal/another_module": {
      "Fix from drupal.org issue queue | https://www.drupal.org/project/another_module/issues/1234567": "https://www.drupal.org/files/issues/2025-01-01/description-1234567-10.patch"
    }
  }
}
```

**Description string convention:**
- Local patches: `Short description of the fix`
- Remote patches: `Short description | https://www.drupal.org/project/{module}/issues/{nid}`
  (Always include the issue URL so future developers know where to check for an official fix.)

---

## Option A — Apply a Patch From the Drupal.org Issue Queue

1. Find the issue on drupal.org. Look for a patch file in the comments (`.patch` extension).
2. Copy the file URL from the issue (right-click the `.patch` link → Copy Link Address).
3. Add to `composer.patches.json`:
   ```json
   "drupal/module_name": {
     "Fix description | https://www.drupal.org/project/module_name/issues/NNNNNNN": "https://www.drupal.org/files/issues/YYYY-MM-DD/filename-NNNNNNN-N.patch"
   }
   ```
4. Apply:
   ```bash
   lando composer update drupal/module_name --no-scripts
   ```
   Or to force re-application of all patches:
   ```bash
   lando composer install
   ```
5. Verify the site still works. Commit `composer.patches.json` only (not `composer.lock` changes unless intentional).

---

## Option B — Create a Local Patch From Your Own Code Changes

Use this when you have modified a file inside `vendor/` directly (e.g. to fix a contrib bug)
and need to convert that change into a tracked patch file.

### Step 1 — Isolate the changed file(s)

Navigate to the vendor module directory and confirm what changed:
```bash
cd web/modules/contrib/module_name
git diff
```

### Step 2 — Create the patch file

From the **project root**:
```bash
cd web/modules/contrib/module_name
git diff > ../../../../patches/drupal-module_name-issue-NNNNNNN-short-desc.patch
cd ../../../../
```

Or from the project root using a relative path:
```bash
git -C web/modules/contrib/module_name diff > patches/drupal-module_name-short-desc.patch
```

### Step 3 — Verify the patch file looks correct

```bash
cat patches/drupal-module_name-short-desc.patch
```

The file should start with `--- a/` and `+++ b/` lines. If it's empty, the diff wasn't
captured correctly (the vendor directory may not be a git repo — use the method below instead).

### Step 4 — Alternative: patch from a manually edited file

If the vendor directory is not a git repo, create the patch manually using `diff`:
```bash
# Get the original from packagist/git and save as .orig
cp web/modules/contrib/module_name/src/File.php /tmp/File.php.orig
# Make your edits, then:
diff -u /tmp/File.php.orig web/modules/contrib/module_name/src/File.php > patches/drupal-module_name-short-desc.patch
```

### Step 4b — Already edited the file without a backup?

If you edited the vendor file directly and no longer have the original, use `composer reinstall`
to restore it, copy the original, then re-apply your edit:

```bash
# 1. Save your current (patched) version
cp web/modules/contrib/module_name/path/to/file.js /tmp/file.js.patched

# 2. Reinstall the module to get the clean original.
#    --no-scripts prevents cweagans/composer-patches from trying to apply
#    patches at this point — the patch file doesn't exist yet and would fail.
lando composer reinstall drupal/module_name --no-scripts

# 3. Copy the clean original
cp web/modules/contrib/module_name/path/to/file.js /tmp/file.js.orig

# 4. Re-apply your edit to the vendor file
cp /tmp/file.js.patched web/modules/contrib/module_name/path/to/file.js

# 5. Generate the patch
diff -u /tmp/file.js.orig /tmp/file.js.patched > patches/drupal-module_name-short-desc.patch
```

**Do NOT write patch hunk headers (`@@ -N,N @@`) by hand.** Always generate them with
`git diff` or `diff -u`. Manually written headers will almost always have the wrong line
number and fail silently or produce a "hunk failed" error.

### Step 5 — Register the patch

Add to `composer.patches.json`:
```json
"drupal/module_name": {
  "Short description of fix": "patches/drupal-module_name-short-desc.patch"
}
```

### Step 6 — Test the patch applies cleanly from scratch

Delete the vendor copy and reinstall to simulate what CI and other developers will experience:
```bash
lando composer reinstall drupal/module_name
```

If that command isn't available on older Composer:
```bash
rm -rf web/modules/contrib/module_name
lando composer install
```

The output should include `  - Applying patches for drupal/module_name`.

---

## Option C — CSS / Asset File Replacement Patch (Empty File Trick)

Used when a contrib module ships CSS that conflicts with the theme and you want to
completely remove it (e.g. `gin-media-library-fix.patch`, `remove-claro-media-library-theme.patch`).

```bash
# Get the original file path (relative to the module root)
# e.g. dist/css/components/media_library.css

cd web/themes/contrib/gin   # or the relevant vendor dir
# Create a patch that replaces the file contents with nothing
git diff HEAD -- dist/css/components/media_library.css > ../../../../patches/gin-media-library-fix.patch
cd ../../../../
```

If the file isn't tracked by git in vendor, use `diff`:
```bash
diff -u web/themes/contrib/gin/dist/css/components/media_library.css /dev/null \
  > patches/gin-media-library-fix.patch
```

> The resulting patch will show all lines as removals (`-`). This is correct and
> expected — it empties the file on patch application.

---

## Patch File Naming Convention

```
patches/{vendor}-{package_name}-{drupal-issue-number}-{short-desc}.patch
```

Examples from our projects:
- `patches/drupal-paragraphs-title-label.patch`
- `patches/gin-media-library-fix.patch`
- `patches/remove-claro-media-library-theme.patch`
- `patches/views_ajax_history-jquery-unique-removed.patch`

Rules:
- All lowercase, hyphens only (no underscores in the filename, but `_` in package name is fine)
- Include the issue number when patching from drupal.org
- Keep it short enough to read in a PR diff

---

## Common Failure Modes

### "Cannot apply patch"
The patch was created against a different version of the file than what Composer installed.

**Fix:** Download or regenerate the patch against the exact installed version:
```bash
lando composer show drupal/module_name   # check installed version
# Find the patch for that exact version on drupal.org, or recreate from scratch
```

### Patch silently not applying
Check that `enable-patching: true` is in `extra` and that `cweagans/composer-patches`
is in `require` (not `require-dev`).

```bash
lando composer install --verbose 2>&1 | grep -i patch
```

Should show: `  - Applying patches for drupal/module_name`

### "Could not find a matching patch file"
The path in `composer.patches.json` is wrong. Paths are **relative to the project root**
(not relative to `composer.json` or `patches/`).

Correct: `"patches/drupal-module_name-fix.patch"`
Wrong: `"./patches/drupal-module_name-fix.patch"` (leading `./` can cause issues)

### Patch fails after `composer update`
The module was updated to a new version and the patched lines have changed.

**Fix steps:**
1. Check if the issue has been fixed in the new version (may be able to remove the patch entirely)
2. If not, check the drupal.org issue queue for a newer patch version
3. If using a local patch, recreate it against the new version

### Core patches failing — wrong `-p` level
Drupal core patches from drupal.org use `-p2` (strip two path components). Make sure
`patchLevel` is set in `extra`:
```json
"patchLevel": {
    "drupal/core": "-p2"
}
```

Without this, core patches will fail with "can't find file to patch".

### `composer-exit-on-patch-failure: true` breaking CI
If a patch fails, the entire `composer install` fails. This is intentional — it means
a broken patch won't silently deploy. Fix the patch; don't disable this setting.

---

## Full Example: Adding a New Remote Patch

```bash
# 1. Find the patch URL on drupal.org issue queue
# 2. Add to composer.patches.json:
```

```json
{
  "patches": {
    "drupal/paragraphs": {
      "Paragraph Title or ID for unpublished paragraphs.": "patches/paragraphs-title-label.patch"
    },
    "drupal/layout_paragraphs": {
      "TypeError: Cannot read properties of undefined | https://www.drupal.org/project/layout_paragraphs/issues/3467793": "https://www.drupal.org/files/issues/2024-10-07/layout_paragraphs-3467793-15.patch"
    }
  }
}
```

```bash
# 3. Apply
lando composer install

# 4. Verify
lando composer install --verbose 2>&1 | grep -i "patch\|applying"

# 5. Commit
git add composer.patches.json patches/
git commit -m "fix(): add patch for drupal/layout_paragraphs TypeError #3467793"
```
