A major version jump — Drupal 10 to 11 today, 11 to 12 when it lands in December 2026 — is not a big security update. It’s a portage: you carry everything you own across before the water opens back up. The good news is that almost all the work happens before the jump, while your site still runs fine. You’ll need a local copy in DDEV, comfort with the normal update routine, and a real afternoon. Note the deadline that’s driving many of these: Drupal 10 reaches end of life on December 9, 2026.
Run the readiness scan
The Upgrade Status module reads your whole codebase and tells you what would break on the next major. Install it on your local copy:
composer require --dev drupal/upgrade_status
drush en upgrade_status -y
Then visit Reports → Upgrade status. It checks the environment too — Drupal 11 wants PHP 8.3 or newer, so if your host is still serving 8.1, that conversation starts now, not on upgrade day.
Check every contrib module’s story
Every module on the report has one of three stories: a compatible release exists (update it), a compatible release is coming (watch the issue queue), or the project is abandoned (find a replacement or uninstall). Also check the report for core modules that got removed in the new major — Drupal 11 dropped Book, Forum, Statistics, Tour, Actions UI, and Activity Tracker. Each needs its contrib version installed, or a decision to let it go, before the jump. This step is the whole game; the composer command later just enforces whatever you decided here.
Fix deprecations while still on the old version
Custom modules and themes accumulate calls to APIs the new major removed. Upgrade Status lists them; Drupal Rector fixes most automatically:
composer require --dev palantirnet/drupal-rector
cp vendor/palantirnet/drupal-rector/rector.php .
vendor/bin/rector process web/modules/custom
Review the diff, run the site, commit. Because deprecated APIs still work on the old major, you can ship these fixes to live now and shrink the risky part of the jump to nearly nothing.
Do the jump on your local copy
Back up the local database first, then ask composer for the new core:
drush sql:dump --gzip --result-file=../backups/pre-major-$(date +%Y-%m-%d).sql
composer require 'drupal/core-recommended:^11' 'drupal/core-composer-scaffold:^11' --update-with-all-dependencies
(Adjust ^11 to whatever major you’re jumping to, and include
drupal/core-dev if you have it.) If composer refuses, that’s a feature —
the error names exactly which package is holding the door shut.
Work the error list to zero
Composer refusals mean a contrib module still lacks a compatible release: update it, replace it, or remove it, then re-run the require. Once composer succeeds, finish the database side:
drush updatedb -y
drush cache:rebuild
Loop until both commands run clean. Two or three loops is normal on a site with real history; it’s not a sign you did anything wrong.
Test, then repeat the jump on staging and live
Click through the front page, log in, edit content, run a checkout if you
sell things. Then commit composer.json and composer.lock, deploy to
staging, and run drush updatedb -y && drush cache:rebuild there — live
gets the exact code your local copy just proved, never a fresh resolve.
You’ll know you’re done when drush status shows the new major on live,
the status report at Reports → Status report has no new errors, and
the money paths click through the same as they did this morning.