Maggie 3.0Meet the newly updated AI tutor

See what's new
How to Apply a GitHub Commit to Magento Core Code

How to Apply a GitHub Commit to Magento Core Code

Read about how the process of applying a specific commit from the GitHub version control system to the core of Magento code.

To ship a Magento core fix from GitHub before the next release, download the commit or PR as a .diff, point its app/code/Magento/... paths at the vendor/magento/module-* packages, split it into one file per module, and register each file under extra.patches with cweagans/composer-patches. Check Adobe's Quality Patches Tool first, because Adobe may already ship that fix.

Magento has a bug, and the fix is already merged on GitHub. Waiting for the next release can take months, and this is an important fix that you'd really like to push out in the next couple of days, or even today. You can ship that exact commit as a Composer patch with cweagans/composer-patches.

Check the Quality Patches Tool first

Before you hand-roll a diff, check whether Adobe has already packaged the fix. The Quality Patches Tool bundles individual fixes for released versions, and applying one is a single command:

composer require magento/quality-patches
vendor/bin/magento-patches status
vendor/bin/magento-patches apply MDVA-XXXXX
bin/magento cache:clean

If your fix is in that list, use it. If it isn't (or it's a community PR that hasn't landed in a release yet), keep reading.

On Adobe Commerce Cloud, there's one more option: drop custom patches into the m2-hotfixes/ directory, and ece-tools applies them at build time. Everywhere else, cweagans/composer-patches is the way to go.

Get the diff

Let's say I have a bug with Magento where it isn't translating salutations, such as prefixes or suffixes in a name. Then, I head to GitHub to see if there isn't already a fix for it. Lo and behold, here's a closed issue that describes my exact situation. And it's closed, because there's already a merged PR about how to fix this.

(This exact fix has been in core since 2.4.6, so don't apply it to a current install. It's just a good, small example to walk through.)

Let's go to the PR, and since it's already merged into the current release branch, magento:2.4-develop, we know that Magento has already code-reviewed this PR, and it's already merged into the core.

Merged GitHub commit

When we go to the files changed, we can see that it's not a huge update, but it's more than a couple lines of code that we want to type. There's a really easy way to grab the diff for this PR: add a .diff extension to the end of the PR URL, and the diff file will load. This is pretty neat, because we can now take this diff and apply it to our Magento store.

Git diff

The same trick works on a single commit, which is what the title promises, and it's the more stable option: https://github.com/magento/magento2/commit/<sha>.diff (or .patch). A PR's diff changes every time someone pushes another commit to it, while a commit's diff never changes.

Fix module & filesystem references

Now that we have the diff file, we need to update the file structure of the patched file. This isn't an official patch file, which makes it a bit more difficult to apply. Note how the file is referencing the locations of Magento modules in app/code/Magento -- we need this location to be the Composer location in the vendor directory instead.

First, create a patches/composer directory in the project root. This is where all of these diff files live, and it's the one directory used for every patch in this article.

Then download the diff into it:

mkdir -p patches/composer
curl -fsSL https://patch-diff.githubusercontent.com/raw/magento/magento2/pull/30413.diff -o patches/composer/PR-30413.diff

Always download the diff and commit it to your repo, rather than pointing Composer at the GitHub URL. The composer-patches docs warn against PR URLs for exactly the reason above: anyone who pushes to that PR changes what you deploy.

Then we will need to rename all of these file locations. Let's do a global search for app/code/Magento/Customer, and replace it with vendor/magento/module-customer.

We'll need to also do the same for app/code/Magento/Sales, renaming the references to vendor/magento/module-sales.

We'll also need to separate this file out into two different patch files, one for each module. This is because Composer applies each patch to one specific package, so these diffs need to relate 1:1 to a module.

Let's move the Sales-module-specific code to PR-30413-sales.diff, and keep the Customer-module-specific code in the file and rename it to PR-30413-customer.diff. Then, each file will only contain references to the related module, one for the customer module, and one for the sales module.

patches/composer/PR-30413-customer.diff
# Contains code related to vendor/magento/module-customer
 
patches/composer/PR-30413-sales.diff
# Contains code related to vendor/magento/module-sales

Test the patches

Now we are going to test applying the patch, but only as a dry run. From the project root, run:

git apply --check -v patches/composer/PR-30413-customer.diff

(patch -p1 --dry-run < patches/composer/PR-30413-customer.diff does the same job if you prefer patch.)

And, we'll see there's actually an error when applying one of the hunks on the patch:

Patch hunk error

Each of these groups of changes in a diff is called a hunk. So if we look at the one applying to vendor/magento/module-customer/Helper/View.php, it has two hunks, and they are each separated by one of these @@ lines in the changeset.

We need to find out exactly how and where this error is occurring. A patch only fails to apply when its changesets don't exactly match the original file.

First we will check out the getCustomerName function in the vendor/magento/module-customer/Helper/View.php file. Let's start with the first line of the patch, which looks like it replaces the brackets in the @inheritdoc docblock with text that doesn't have the brackets. Checking this out in the View class, we'll see that it already doesn't have brackets.

Inheritdoc diff View file

Usually we'd need to re-generate the diff, but this is overly complex. What we can do instead is just change the removal line to the same line as the addition line, so that part of the update doesn't happen at all.

Updated diff

Be clear about what you just did, though: you've dropped part of the upstream change. Here it's a docblock, so it doesn't matter. If a hunk only touches lines you don't need, deleting that hunk entirely is the cleaner fix, and it's easier to spot in review.

Now we re-run the dry run, and we can see that all patches will now succeed. This is why you should always dry-run rather than assuming a patch will succeed, because once you get this all set up in your automated deployments, a patch that bombs out can kill that deployment and prevent it from being pushed out in your pipeline.

We'll do the same with the sales module, running git apply --check -v patches/composer/PR-30413-sales.diff, and since everything is all good there as well, we are ready to automate this process.

Patch dry-run succeeds

Automate applying patches when deploying code

Now we could just apply the patch for real, and this will patch our files. But we want our patches to be automatically applied when we deploy our code. cweagans/composer-patches applies patches on every composer install, which is exactly what we need.

First, we will need to install it with Composer:

composer require cweagans/composer-patches

This installs composer-patches 2.x, which went stable in October 2025. It's a Composer plugin that applies the diff with git or patch whenever a patched package is installed. It looks for specific properties of the composer.json file to determine what patches to apply.

Let's open up the composer.json and go all the way to the bottom. Magento usually has an extra property already containing the magento-force property, but the cweagans module looks for a property at extra.patches, so let's create that:

"extra": {
    "magento-force": "override",
    "patches": {
    }
}

Next comes the package we wish to apply the patch to. Since we want to apply our first patch to the customer module, this will be magento/module-customer. Within it goes a list of patches, each with a description and a url (a local path relative to the project root works fine). I like to prefix the description with the patch filename, which is the PR and number, and then a description of what it actually does. This will help us in the future if this starts failing during an upgrade or something else. We'll then do the same thing with the sales module.

There's one more field we need: depth.

Set the patch depth

composer-patches 2.x applies each patch from inside the package's own directory (vendor/magento/module-customer), and it uses one fixed depth, which defaults to 1 (the same as patch -p1). Version 1.x tried several depths until one worked, which is why vendor-relative paths used to "just work". On 2.x they don't, and the install fails with "No available patcher was able to apply patch".

Our diff paths look like a/vendor/magento/module-customer/Helper/View.php. Stripping four segments (a/, vendor/, magento/, module-customer/) leaves Helper/View.php, which is the path inside the package. So we set "depth": 4:

"extra": {
    "magento-force": "override",
    "patches": {
        "magento/module-customer": [
            {
                "description": "PR-30413-customer: Prefix and Suffix Translation",
                "url": "patches/composer/PR-30413-customer.diff",
                "depth": 4
            }
        ],
        "magento/module-sales": [
            {
                "description": "PR-30413-sales: Prefix and Suffix Translation",
                "url": "patches/composer/PR-30413-sales.diff",
                "depth": 4
            }
        ]
    }
}

If every patch you keep for Magento modules uses vendor paths, you can set the depth once for a package with extra.composer-patches.package-depths instead. The other option is to rewrite the diff paths to be package-relative (a/Helper/View.php) and skip depth entirely, but then you can't dry-run the file from the project root like we did above.

composer-patches 2.x: relock and repatch

2.x keeps a patches.lock.json file next to composer.lock, so adding a patch takes a few commands rather than just composer install:

composer patches-relock
composer patches-repatch
composer update --lock

patches-relock regenerates patches.lock.json with the new patch. patches-repatch deletes the patched packages, reinstalls them fresh, and applies the patches on top, so you always start from a clean base (and it will throw away any edits you made directly in those vendor folders). composer update --lock then updates the content hash in composer.lock, since we changed composer.json.

Commit composer.json, composer.lock, patches.lock.json and the patches/composer directory. From then on, every composer install on every environment applies the patches, and we'll see it detected a patch for both the customer and sales modules:

Composer install

Now that it has applied these updates, we can confirm it worked by opening up one of the files that had changes in this diff.

For example, the vendor/magento/module-sales/Model/Order/Address.php file. If we go to the getName() method, we'll see that the translation function is now wrapping the getPrefix() and getSuffix() function calls of the name:

View file updated

Now you know how to apply Magento code changes from GitHub, and have them automatically applied in your deployment pipeline. For a real-world example of the same workflow on a security fix, see how Adobe's StyleSmuggler patch APSB26-146 ships as a set of composer patches. And if you've hit a patch that refuses to apply even with the right depth, I'd like to hear what was in the diff.

Something here out of date?

Magento moves, and so does this site. If a step no longer works, tell me and I'll fix it.

Get the next one by email

Every other week I send a short newsletter on how PHP + Magento actually work: releases, security alerts, and one thing explained under the hood. Enroll in the Library and it's yours. No credit card needed.

The University includes every PHP + Magento course available, and every new M.academy course as it ships.