---
title: Magento composer update: Stop Unexpected Payment Module Version Jumps
url: https://ilogr.com/magento-composer-update-payment-module-version-jump/
published: 2026-10-28
updated: 2026-11-03
author: iLogr
categories: Magento
reading_time: 9 min
description: A Magento upgrade's full composer update can move extensions too. Compare dry runs and cap open-ended dependencies to stop surprise version jumps.
---

# Magento composer update: Stop Unexpected Payment Module Version Jumps

**In short:** A Magento upgrade’s full composer update can move extensions too: on one store, a caret constraint let a payment module jump several minor releases and add an uncached request to every page. Before you deploy, compare a normal and a minimal-changes dry run, and cap open-ended dependencies.

A Magento upgrade is planned around Magento's own packages, and the extensions come along for the ride. Composer does not treat them differently: when the upgrade runs a full update, every extension can move as far as its version constraint allows. This guide gives the checks first, then the field note they come from, why the constraint allowed the jump, why capping the module alone was not enough, and how to spot a new per-page request after a deploy. Facts were checked against Composer 2.10.3, Adobe's upgrade guide and the Magento source, and the Composer behaviour was confirmed in a small test.

## How do you stop composer update moving a Magento extension further than you meant?

**Before you change anything:** the commands and constraint changes in this guide alter which code your store runs. Try them on a local or staging copy of the store first, never directly on a live site, take a backup of the code, the lock file and the database before you deploy, and make every change at your own risk. iLogr cannot see your store and accepts no responsibility for damage, data loss or downtime.

1. **Dry-run the update before you run it.** After changing the Magento version in `composer.json`, run `composer update --dry-run` [1]. Read every "Upgrading" line for a package that is not one of Magento's own, and note any extension whose first or second version number changes.
2. **Compare it with a minimal-changes dry run.** `composer update --minimal-changes --dry-run` will "Only perform absolutely necessary changes to dependencies" [1]. A full update in this mode needs Composer 2.9 or later [2]. Adobe lists Composer 2.9.3 or later for Magento 2.4.7-p9 and Composer 2.10 for 2.4.7-p10 [3]. Any extension that moves in the normal dry run but not in this one is moving because its constraint allows it, not because the upgrade needs it.
3. **Decide for each of those extensions.** Either accept the new version on purpose (read its release notes and test it on staging), or hold it back: run the minimal-changes update instead, or tighten its constraint in `composer.json` to the line you have tested, for example a tilde constraint such as `~100.2.10`.
4. **Cap what the extension brings with it.** If the extension's own `composer.json` requires another package with an open-ended `>=` constraint, tightening the extension alone does not hold that package (see the lab below). Add a capped requirement for it in your root `composer.json` too. Review and adjust that cap when you next upgrade the extension intentionally; remove it only when the remaining constraints enforce the dependency range you have tested.
5. **Read the lock file diff before deploying.** List every package whose version changed between the old and new `composer.lock`, grouped into Magento's own packages and everything else, and commit the new lock file so production installs exactly what you tested [4].
6. **Check what each moved extension adds to every page.** Compare the old and new versions of its `view/frontend/layout/default.xml`, along with any JavaScript it loads. Anything new in that layout file reaches storefront pages broadly (see below).
7. **Count requests by path after the deploy.** Compare a day before with a day after. A path that did not exist before and now appears once for every page view needs an owner and an explanation.

This is not an argument for pinning every package to an exact version. Exact pins also block the security and bug fixes you want. The aim is to know which extensions move, and why, before the deploy rather than after it.

## What happened when a Magento upgrade moved a payment module?

This comes from a Magento store. The payment module update arrived as part of the store's Magento upgrade, and it added load of its own. This is how.

The store's `composer.json` required the payment module's frontend package with a caret constraint. Nobody had touched that constraint during the upgrade. The upgrade refreshed the whole lock file: hundreds of locked versions changed, more than a hundred of them outside Magento's own packages. Among them, the payment module and its core package moved across several minor releases. The core package was not in the store's `composer.json` at all. The module required it with an open-ended minimum version.

After the deploy, a request that had never appeared before showed up tens of thousands of times a day, one for every page view, and none of them were answered from a cache. Counting requests by path, the day before the deploy against the day after, is what made it visible: a path with a count of zero on one day and tens of thousands on the next.

What we did not record: the load the request added in server time, how much of it came from real visitors, and what the store did about the constraint afterwards. We have not inspected the newer source, so we have not traced which release added the request.

## Why does a caret constraint let a Magento extension jump minor versions?

For versions from 1.0.0 onwards, Composer's caret operator allows later releases below the next major version, subject to the project's other constraints. Its documentation gives `^1.2.3` as equivalent to `>=1.2.3 <2.0.0`, "as none of the releases until 2.0 should break backwards compatibility" [5]. In the dummy example below, a constraint such as `^100.2` accepts every 100.x release from 100.2 upwards. Composer's own `outdated` command agrees: in our lab it marked a jump from 100.2 to 100.5 with `!`, its sign for an update that is "semver-compatible and you should upgrade it" [1].

That is correct by the rules of semantic versioning, where a minor version is for when you "add functionality in a backward compatible manner" [6]. Backward compatible means the code you call still works. It does not mean the release does no new work on your pages. A payment module can add a new check, script or request in a minor release and keep every rule. Treat "semver-compatible" as a statement about the version number, not as an answer to whether the release is safe to deploy.

The upgrade is what lets it move. Adobe's upgrade guide changes the Magento version with `composer require-commerce` and the `--no-update` flag, then runs a plain `composer update` [7]. Composer's documentation says that command "will resolve all dependencies of the project and write the exact versions into `composer.lock`" [1]. Every extension constraint is re-resolved, so a constraint nobody has looked at since the extension was installed is suddenly in play. Our Magento 2.4.9 upgrade guide covers building the extension inventory for an upgrade [8]; this is the check to add to it.

## Why did capping the extension not hold its dependency?

We tested this in a small lab with Composer 2.10.3 and three dummy packages with made-up names and version numbers: a platform package moving from 2.4.6 to 2.4.7, a payment frontend package required as `^100.2` and locked at 100.2.10, and its core package locked at 100.2.4. Each frontend release required the core package with an open-ended `>=`. The model also includes a later core release that replaces and conflicts with the frontend package.

![A playful chart of a three-package lab model with made-up version numbers, under the heading Capping the module alone did not hold and a corner ribbon reading Lab results. Three chips show the same starting lock: platform 2.4.6, frontend 100.2.10, core 100.2.4. Three numbered cards follow, each with a lilac platform tile and two cardboard parcel characters, frontend and core. Card 1, a caret constraint and a plain composer update: all three move; the platform to 2.4.7 as intended, the frontend to 100.5.3 and the core to 100.5.1, with both parcels leaping. Card 2, a tilde on the frontend only: the frontend parcel stays on 100.2.10 wearing a green lid, but the core still moves to 100.5.1, a pair never released together. Card 3, the core capped in the root, or minimal changes: only the platform moves; the frontend stays on 100.2.10 and the core on 100.2.4, both wearing green lids. At the foot a green shield-shaped bouncer in sunglasses and an orange flat cap stands beside the note: Cap what the module brings with it. An open-ended requirement inside a vendor's package can only be capped in your root composer.json.](https://ilogr.com/wp-content/uploads/2026/10/composer-constraint-lab-results-v2.webp)

*In the lab model, capping only the module left its core package free to move. Capping the core package too, or a minimal-changes update, held both.*

What happened:

- **Updating only the platform package** by name moved the platform and nothing else.
- **A plain `composer update`** moved all three: the frontend from 100.2.10 to 100.5.3 and the core from 100.2.4 to 100.5.1.
- **A tilde constraint on the frontend** (`~100.2.10`) kept the frontend on 100.2.10, but the same update still moved the core package to 100.5.1. The result was a frontend and core pair that were never released together.
- **Capping the core package in the root as well** (`>=100.2.4 <100.3`) held both.
- **`composer update --minimal-changes`** without any constraint changes moved only the platform.

In the lab, only one thing kept the core package below 101: that release line's own conflict with the frontend package. Composer's `why-not` command, which "tells you which packages are blocking a given package from being installed" [1], reported exactly that conflict. Composer's documentation warns about this kind of constraint: "Be careful when using unbounded ranges as you might end up unexpectedly installing versions that break backwards compatibility" [5]. A requirement with no upper bound inside a vendor's package is one you cannot edit, so your root `composer.json` is the only place to cap it.

The lab is a model with three packages, not a Magento upgrade. A real upgrade moves hundreds of packages, and `--minimal-changes` will still move an extension when the new Magento version needs a newer release of it. Use it as a second dry run to compare against, not as a guarantee.

## How do you spot a new per-page request after a Magento deploy?

Start with the layout file that applies across the storefront. A module's `view/frontend/layout/default.xml` adds its instructions to the `default` layout handle, and Magento's page result adds that handle when it sets up the default handles for a storefront page [9]. Diff that file between the old and new release of each extension that moved. A new block or script there is a candidate for a new request on every page.

Then measure. Count requests by URL path for a full day before the deploy and a full day after, and sort by the difference. A path that is new, appears at about the rate of your page views and is never served from a cache is the pattern from the field note.

Keep the before figures. Without a baseline, a new request just looks like part of normal traffic.

## Which tools show a Magento extension's version jump before it ships?

You can run the checks above by hand at every upgrade, or let an update bot do part of the reading between upgrades. Dependabot, built into GitHub, supports Composer and raises a pull request when a dependency is outdated, with the changelog and release notes in its summary [10][11]. Renovate creates pull requests the same way on GitHub, GitLab, Bitbucket and several other platforms, and keeps `composer.lock` updated along with `composer.json` [12][13].

Choose by three things: where your code lives, whether the tool can reach every repository your `composer.json` uses (Magento's own and each extension vendor's), and how many pull requests your team will really review. When updates arrive as separate pull requests, a jump in one package is easy to spot. That does not replace the two dry runs and the per-page check before a platform upgrade, when everything moves at once.

If a deploy leaves Composer saying the lock file is not up to date, [Magento composer.lock Not Up to Date: Repair It Without Upgrading Other Packages](https://ilogr.com/magento-composer-lock-not-up-to-date/) shows how to fix it while changing only the package you meant to.

**Next step:** On a staging copy, run a plain dry run and a minimal-changes dry run of your next Magento upgrade, and list every extension that moves in one but not the other.

## References

1. [Composer documentation: Command-line interface (update, outdated, prohibits)](https://getcomposer.org/doc/03-cli.md). Used for: the update, dry-run and minimal-changes options, the outdated legend and why-not.
2. [Composer changelog: 2.9.0-RC1, minimal-changes full updates](https://github.com/composer/composer/blob/2.10.3/CHANGELOG.md). Used for: the Composer version that added minimal-changes full updates.
3. [Adobe Commerce: System requirements](https://experienceleague.adobe.com/en/docs/commerce-operations/installation-guide/system-requirements). Used for: the Composer versions Adobe lists for Magento 2.4.7-p9 and p10.
4. [Composer documentation: Basic usage (commit your composer.lock file)](https://getcomposer.org/doc/01-basic-usage.md). Used for: committing the lock file so production installs what you tested.
5. [Composer documentation: Versions and constraints](https://getcomposer.org/doc/articles/versions.md). Used for: caret and tilde ranges and the warning about unbounded ranges.
6. [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). Used for: what a minor version promises.
7. [Adobe Commerce upgrade guide: Perform an upgrade](https://experienceleague.adobe.com/en/docs/commerce-operations/upgrade-guide/implementation/perform-upgrade). Used for: the upgrade steps that end with a plain composer update.
8. [iLogr: Magento 2.4.9 upgrade, requirements and extension compatibility](https://ilogr.com/magento-2-4-9-upgrade-extensions/). Used for: building an extension inventory before an upgrade.
9. [Magento 2.4.7-p10 source: lib/internal/Magento/Framework/View/Result/Page.php](https://github.com/magento/magento2/blob/2.4.7-p10/lib/internal/Magento/Framework/View/Result/Page.php). Used for: where Magento adds the default layout handle to a storefront page.
10. [GitHub Docs: Dependabot supported ecosystems and repositories](https://docs.github.com/en/code-security/dependabot/ecosystems-supported-by-dependabot/supported-ecosystems-and-repositories). Used for: Dependabot's Composer support.
11. [GitHub Docs: About Dependabot version updates](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates). Used for: pull requests with changelog and release notes.
12. [Renovate: readme](https://github.com/renovatebot/renovate/blob/main/readme.md). Used for: Renovate's pull requests and the platforms it supports.
13. [Renovate: Composer manager](https://docs.renovatebot.com/modules/manager/composer/). Used for: keeping composer.lock updated with composer.json.
