Managing composer updates for Drupal core

Last updated on
25 March 2026

Any time a contributor is making a change that is going to affect the composer dependencies that Drupal core relies on, there are some policies and special composer steps that they should be aware of to ensure that the patch can be committed safely to core.

First, if this is a *new* dependency being proposed, that dependency should first evaluated for the criteria listed on the Core dependency evaluation criteria page. That page should be updated with information regarding the new dependency before it is added to Drupal core.

Development packages are tracked in the require-dev section of the ROOT composer.json.
Packages that are a dependency of core itself are managed in the core/composer.json file.

Standard (non-dev) Dependencies:

  1. If you are adding or removing a package or changing existing constraints, make the change to core/composer.json
  2. Take note of the package names that you are adding/modifying - e.g. vendorname/package-1 and vendorname/package-2
  3. In the root directory, run, changing the version in this example to the version of Drupal core on the current branch, as noted below.
    COMPOSER_ROOT_VERSION=9.4.x-dev composer update drupal/core vendorname/package-1 vendorname/package-2
  4. Composer will update the root composer.lock file with the latest changes and additionally this will trigger the metapackage generator to re-generate the files under composer/Metapackage/CoreRecommended/composer.json.
  5. You should then be able to add core/composer.json, composer.lock, and composer/Metapackage/CoreRecommended/composer.json to the patch for review, along with whatever other changes are needed for your patch.

You must set COMPOSER_ROOT_VERSION to the branch this patch is intended for, plus -dev. This keeps composer from trying to 'guess' the version from your branchname, which can cause unexpected results and may even end up deleting your local core folder. Also be aware that Composer may delete your local core folder if you edit core/composer.json and add a dependency that cannot be resolved (e.g. if the version constraint does not match any published version).

You must include the dependencies in your composer update command because otherwise Composer will not attempt to update them. (This behavior changed with the removal of the wikimedia/composer-merge-plugin; dependencies of drupal/core are now considered to be 'dependency of a dependency' for the root-level drupal/drupal project). Wildcards do work, but must be escaped from your shell e.g. COMPOSER_ROOT_VERSION=9.4.x-dev composer update "drupal/core symfony/*"

You should only attempt to use --with-dependencies if you are definitely trying to update *all* of drupal/core's dependencies at once. e.g COMPOSER_ROOT_VERSION=9.4.x-dev composer update drupal/core --with-dependencies. This normally is not done.

Example walkthrough, updating the constraints for twig/twig in drupal/core:

$ git checkout 9.4.x
$ composer install
$ nano core/composer.json
# Edit core/composer.json twig/twig from ^1.38.2 to ^1.38.3
$ COMPOSER_ROOT_VERSION=9.4.x-dev composer update drupal/core twig/twig
# Updates twig/twig, with this message:
[…]
> Drupal\Composer\Composer::generateMetapackages
Updated metapackage file composer/Metapackage/CoreRecommended/composer.json.
If you make a patch, ensure that the files above are included.
[…]
$ git status
On branch 9.4.x
Your branch is up to date with 'origin/9.4.x'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   composer.lock
	modified:   composer/Metapackage/CoreRecommended/composer.json
	modified:   core/composer.json
# Always add changes to composer/Metapackage when composer.lock has changed.
$ git add core/composer.json composer.lock composer/Metapackage/
$ git commit -m ‘Issue #12345: Updated twig/twig’

Updating Guzzle in Drupal 9.4 and 9.5 (or other packages with constraints allowing multiple major versions)

Unlike most core dependencies, Guzzle's version constraint allows both Guzzle 6 and 7 in Drupal 9, because there are compatibility issues between Guzzle 6 and PHP 8.1. However, we must ensure the version of Guzzle in core's lockfile remains on Guzzle 6 to avoid breaking changes for sites that are not compatible with Guzzle 7. Special steps are required to update Guzzle for this reason.

  1. If needed, increase the version constraint in core/composer.json. Sample diff:
    diff --git a/core/composer.json b/core/composer.json
    index 1ec8bde27e..55c9d0f40d 100644
    --- a/core/composer.json
    +++ b/core/composer.json
    @@ -36,7 +36,7 @@
             "doctrine/annotations": "^1.12",
    -        "guzzlehttp/guzzle": "^6.5.2 || ^7.4.1",
    +        "guzzlehttp/guzzle": "^6.5.6 || ^7.4.3",
             "symfony-cmf/routing": "^2.1",
           
  2. Add a temporary top-level requirement in the root composer.json for the Guzzle 6 version to use, e.g.: composer require guzzlehttp/guzzle:6.5.6. Sample diff:
    diff --git a/composer.json b/composer.json
    index e9c3c231eb..4446b128b8 100644
    --- a/composer.json
    +++ b/composer.json
    @@ -12,7 +12,8 @@
             "drupal/core-project-message": "self.version",
    -        "drupal/core-vendor-hardening": "self.version"
    +        "drupal/core-vendor-hardening": "self.version",
    +        "guzzlehttp/guzzle": "6.5.6"
         },
            
  3. Update core and Guzzle: COMPOSER_ROOT_VERSION=9.4.x-dev composer update drupal/core guzzlehttp/guzzle
  4. Remove the top-level Guzzle requirement by checking out the previous version of the root composer.json: git checkout 9.4.x -- composer.json
  5. Run composer update --lock to fix the composer hash

Development Dependencies

(or, also, composer remove)

  1. If you are adding or removing a dev package, use composer require with your constraint:
    COMPOSER_ROOT_VERSION=9.4.x-dev composer require vendorname/package-1:^1.0
  2. If you are changing existing constraints, make the change to the ROOT composer.json
  3. In the root directory, run, changing the version in this example to the version of Drupal core on the current branch.
    COMPOSER_ROOT_VERSION=9.4.x-dev composer update vendorname/package-1 vendorname/package-2
    
  4. Composer will update the root composer.lock file with the latest changes and additionally this will trigger the metapackage generator to re-generate the files under composer/Metapackage/DevDependencies/composer.json and composer/Metapackage/PinnedDevDependencies/composer.json with your changes.
  5. Then you should be able to add core/composer.json, composer.lock, and composer/Metapackage/DevDependencies/composer.json and composer/Metapackage/PinnedDevDependencies/composer.json to the patch for review, along with whatever other changes needed to be made to support the change

Note that you might also need to update your composer.lock file if you make non-dependency related changes to the composer.json or core/composer.json file. In this instance, run the following command.

COMPOSER_ROOT_VERSION=9.4.x-dev composer update --lock

Sometimes either of these processes may have an overlap in dependencies between dev and standard, in which case, some unexpected metapackages may be updated. As long as you add all generated metapackages to the patch you are creating, everything will stay in sync.

If you do not add these additional metapackage composer.json files, the core/tests/Drupal/Tests/Composer/Metapackage/MetapackageUpdateTest will fail, letting you know that the generated metapackages are out of sync with the generated lockfile.

If the core/tests/Drupal/Tests/ComposerIntegrationTest fails the content-hash in composer.lock, then regenerate the content-hash by running:

composer update nothing

Example walkthrough, updating the constraints for phpspec/prophecy in drupal/drupal's development requirements:

$ git checkout 9.4.x
$ composer install
# Update phpspec/prophecy's minimum constraint:
$ COMPOSER_ROOT_VERSION=9.4.x-dev composer require --dev phpspec/prophecy:^1.8
./composer.json has been updated
[…]
> Drupal\Composer\Composer::generateMetapackages
Updated metapackage file composer/Metapackage/DevDependencies/composer.json.
If you make a patch, ensure that the files above are included.
[…]
# Notice that the metapackage has been modified:
$ git status
On branch 9.4.x
Your branch is up to date with 'origin/9.4.x'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   composer.json
	modified:   composer.lock
	modified:   composer/Metapackage/DevDependencies/composer.json
# Always add changes to composer/Metapackage when composer.lock has changed.
$ git add composer.json composer.lock composer/Metapackage/
$ git commit -m ‘Issue #4321: Updated phpspec/prophecy’

Evaluating dependency changes with composer-lock-diff

Dependency changes in a patch or release can be evaluated with the composer-lock-diff tool. Usage:

  1. Install the tool globally using:

    composer global require davidrjonas/composer-lock-diff:^1.0@dev
    
  2. To get the changes since the last tag (8.9.0-beta2 in this example), run the following command from inside your Drupal repository:

    composer-lock-diff --from=8.9.0-beta2 --no-links
    

    You can replace 8.9.0-beta2 with any git reference. For example, use HEAD instead to show uncommitted dependency updates.

PHPStan

When updating PHPStan, you will usually have to update the PHP baseline too.

TOP_LEVEL=$(git rev-parse --show-toplevel) &&
php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist"

Help improve this page

Page status: No known problems

You can: