Upgrading from Drupal 10.6.x to 11.3.x is officially supported, but in real projects it’s rarely a single command. The friction usually comes from **Composer constraints**, not Drupal itself.
This article documents a real-world upgrade path from Drupal 10.6.1 → 11.3.2, including the specific blockers encountered and the sequence of steps that ultimately worked.
The environment:
- PHP 8.4
- Composer 2.9
- DDEV
- A site with non-trivial contrib and admin tooling (Gin, vendor hardening, core dev tools)
1. Pre-flight checks
Before starting:
- Upgrade Status module shows no blocking issues
- Database backup completed
- PHP version meets Drupal 11 requirements
- Composer is recent (2.6+)
At this point the site is theoretically ready, but Composer still needs help.
2. Upgrade contrib modules that block core changes
Color module
The Color module needed to move to a newer release line before core constraints could change cleanly.
composer require 'drupal/color:^2.0@alpha'This avoids downstream conflicts when core updates its dependency graph.
3. Prepare Drupal 11 core constraints (without solving yet)
Rather than letting Composer immediately attempt a full dependency solve, the upgrade was staged by updating constraints only using `--no-update`.
composer require \
drupal/core-recommended:^11.3 \
drupal/core-composer-scaffold:^11.3 \
drupal/core-project-message:^11.3 \
--no-updateThis updates `composer.json` without touching `composer.lock`, allowing other blockers to be resolved first.
4. Remove Gin (temporary but necessary)
Gin and Gin Toolbar were pinned to `<11.2`, which **hard-blocked Drupal 11.3**. Rather than forcing an intermediate Drupal version, they were temporarily removed.
composer remove drupal/gin drupal/gin_toolbar --no-updateCore constraints were then re-applied (defensive, but harmless):
composer require \
drupal/core-recommended:^11.3 \
drupal/core-composer-scaffold:^11.3 \
drupal/core-project-message:^11.3 \
--no-update
5. Fix hidden blockers: core dev tooling and vendor hardening
Core dev tools
The project was still pinned to Drupal 10 dev tooling, which blocks Symfony 7 upgrades.
composer require drupal/core-dev:^11.3 --dev --no-updateVendor hardening
`core-vendor-hardening` also needed to move off the Drupal 10 line.
composer require drupal/core-vendor-hardening:^11 --no-update(After the full update, this was later tightened to `^11.3`.)
6. Run the actual dependency solve
With all blockers removed or upgraded, Composer was finally allowed to do a full resolution:
composer update -WThis step:
- Upgraded Drupal core to 11.3.2
- Moved Symfony from 6.4 → 7.4
- Updated all compatible dependencies in one pass
7. Finalise vendor hardening alignment
After the successful update, vendor hardening was aligned exactly with the core version:
composer require drupal/core-vendor-hardening:^11.3 --no-update(No further solve was required.)
8. Run Drupal updates
Standard Drupal post-upgrade steps:
drush updatedb -y
drush cr
drush cex -yAt this point the site was fully running on Drupal 11.3.2.
9. Re-install Gin (Drupal 11 compatible versions)
Once core was stable, Gin and Gin Toolbar were reinstalled using versions compatible with Drupal 11.3:
ddev composer require drupal/gin:^5 drupal/gin_toolbar:^3 -W
Admin UI restored, without blocking core.
## Key lessons from this upgrade
- You do not need to upgrade to 11.1 first direct 10.6 → 11.3 is valid.
- Most failures are caused by:
- `core-dev:^10`
- `core-vendor-hardening:^10`
- Admin themes (Gin) with `<11.2` caps
- Use `--no-update` aggressively to control Composer’s solver.
- Remove blockers temporarily rather than forcing partial upgrades.
- Let one `composer update -W` do the heavy lifting.
Drupal 11 itself is not the hard part Composer hygiene is. Once the project’s constraints accurately reflect Drupal 11 expectations, the upgrade becomes predictable and repeatable.
This sequence has now been proven end-to-end and can be reused safely on similar Drupal 10.6 projects.