Drupal teams have a narrow but manageable upgrade window in late 2026. Drupal 10 reaches end of life on 9 December 2026, and Drupal 12 is scheduled for release during the week beginning 7 December. That places the two events almost on top of one another: organisations still running Drupal 10 need to make a supported-platform decision now, rather than waiting for Drupal 12 to arrive.
The safest route is not a rushed Drupal 10-to-12 jump. Drupal’s supported upgrade path is sequential: Drupal 10 sites should move to Drupal 11 first, then adopt Drupal 12 after the site has reached a suitable Drupal 11 minor release. The practical work is therefore compatibility engineering, dependency management and deployment rehearsal—not simply changing a version constraint in composer.json.
The dates that should drive your upgrade plan
Drupal 10.6 is the final minor branch of Drupal 10. Security support for Drupal 10 ends on 9 December 2026. Drupal 12.0.0 and Drupal 11.5.0 are scheduled for the week beginning 7 December 2026. Drupal’s own release guidance also states that Drupal 10 sites must reach at least Drupal 11.3 before moving to Drupal 12, because older update paths are no longer included in Drupal 12. ([drupal.org](https://www.drupal.org/about/core/policies/core-release-cycles/schedule?showAll=true&utm_source=openai))
That creates three sensible strategies:
- Upgrade to Drupal 11 now, then remain on a supported Drupal 11 minor branch while you assess Drupal 12.
- Upgrade to Drupal 11 and test Drupal 12 in parallel, using a disposable environment and a production-like database copy.
- Remain on Drupal 10 temporarily only with a documented exception, such as a vendor dependency or platform constraint, and obtain an explicit extended-support or risk-acceptance plan.
For most organisations, the first option is the lowest-risk choice. Drupal 11 is the bridge release: it gives teams a supported base, exposes deprecated-code problems before Drupal 12 removes the relevant APIs, and avoids combining two major-version migrations with an end-of-life deadline.
Start with an inventory, not Composer
Before changing dependencies, record what the site actually contains. A Drupal upgrade can fail because of a contributed module, a custom service, a theme preprocess function, a deployment assumption or a server-level extension that is not represented clearly in the project documentation.
From the Drupal project root, capture the current platform and dependency state:
php --version
composer --version
composer show drupal/core-recommended
composer show --direct
drush status
drush core:status
drush pm:list --type=module --status=enabled
drush pm:list --type=theme --status=enabled
Keep the output with the upgrade ticket or branch. It provides a baseline for comparing the test environment with production and makes it easier to identify whether a later failure came from PHP, Composer, Drupal configuration or application code.
Also export configuration and record the current database schema state:
drush config:export --destination=../config/snapshot-before-drupal-11
drush updatedb:status
drush config:status
drush watchdog:show --count=100
Do not treat configuration export as a database backup. Take a real database backup as well, and verify that it can be restored. A rollback plan that has never been tested is only an assumption.
Check the platform before checking modules
Drupal 11 requires PHP 8.3 or later according to the upgrade documentation. Drupal 12 is expected to require PHP 8.5, and its announced database requirements include MySQL 8.0, MariaDB 10.11, PostgreSQL 18 and SQLite 3.45. These requirements matter because a site can be perfectly compatible at the PHP-code level while still being blocked by its hosting platform. ([drupal.org](https://www.drupal.org/docs/upgrading-drupal/upgrading-drupal/how-to-upgrade-from-drupal-10-to-drupal-11?utm_source=openai))
Check the whole runtime, not just the PHP version shown by the command line:
php -m
php -i | grep -E 'Loaded Configuration File|memory_limit|upload_max_filesize|post_max_size'
php -r 'echo PHP_VERSION, PHP_EOL;'
mysql --version
In containerised or managed hosting environments, the PHP binary used by Composer may differ from the binary used by PHP-FPM. Confirm both:
composer check-platform-reqs
ps aux | grep '[p]hp-fpm'
php-fpm --version
Do not upgrade the production PHP runtime and Drupal core in the same untested change unless there is a strong operational reason. A better sequence is to create a platform-compatible test environment first, run the application there, and then promote the platform change separately or as a deliberately tested release.
Use Upgrade Status while the old APIs still exist
Upgrade Status is particularly valuable before a major-version upgrade because it scans the codebase while deprecated APIs are still available. Once Drupal 12 has removed an API, running the same scan on the upgraded codebase cannot identify every place where the old API was used.
The current Upgrade Status project provides checks for the next major version and supports Drupal 10, 11 and 12 compatibility work. Its current development line includes improved Drupal 11.4 and Drupal 12 compatibility checks, although teams should select a release appropriate for their stability policy rather than automatically installing an alpha version in production. ([drupal.org](https://www.drupal.org/project/upgrade_status?utm_source=openai))
Install it in a development environment:
composer require --dev 'drupal/upgrade_status:^5.0@alpha'
drush en upgrade_status -y
drush cr
The exact package constraint should be reviewed against the project’s release page and your site’s Drupal version before use. If your organisation does not permit alpha development dependencies, use the latest stable branch that supports your current core version and run the checks locally or in CI.
Run the report from the administrative interface and address findings in this order:
- Removed or obsolete core modules and themes.
- Contributed modules without a compatible Drupal 11 release.
- Custom code using deprecated services, functions, hooks or plugin APIs.
- Theme code relying on deprecated preprocess variables or obsolete libraries.
- Test code and build scripts that assume the old core structure.
Use the report as a prioritisation tool, not as a substitute for tests. A static scan cannot prove that a route works, a form validates correctly, a queue finishes, or a multilingual access rule still behaves as intended.
Deal with removed core extensions early
Drupal 11 already removed or moved several extensions that older Drupal 10 sites may still use, including Actions UI, Activity Tracker, Book, Forum, Statistics and Tour. The upgrade documentation recommends replacing or removing these extensions before the core upgrade rather than attempting to solve all of the changes during the major-version update. ([drupal.org](https://www.drupal.org/docs/upgrading-drupal/upgrading-drupal/how-to-upgrade-from-drupal-10-to-drupal-11?utm_source=openai))
For each affected extension, decide which of these applies:
- The feature is no longer used and can be uninstalled.
- The feature is still required and has a contributed replacement.
- The feature is implemented in custom code and needs a migration or rewrite.
- The extension is installed but not enabled, and can be removed from the codebase.
Check both the configuration and the codebase. An extension can be disabled while leaving configuration, field definitions, permissions or custom integrations that still refer to it.
drush pm:list --type=module --status=both
drush config:get core.extension module
composer show --direct | grep '^drupal/'
grep -R "statistics\|book\|forum\|tour" web/modules/custom web/themes/custom -n
Do not remove an extension merely because a text search finds no obvious reference. First inspect configuration dependencies, entity types, Views, permissions and routes. Export configuration after each deliberate cleanup so that the change is visible in version control.
Prepare contributed dependencies with Composer
Drupal’s recommended update mechanism for Composer-managed sites is Composer. Avoid copying core files over an existing installation or editing the lock file manually. The lock file is part of the deployment artefact and should be regenerated by Composer, reviewed, committed and installed consistently in each environment. ([drupal.org](https://www.drupal.org/docs/updating-drupal/updating-drupal-core-manually?utm_source=openai))
Start by identifying direct dependencies and outdated packages:
composer outdated "drupal/*"
composer outdated --direct
composer audit
composer prohibits drupal/core-recommended ^11
The final command is useful when another package prevents the core constraint from moving. Run it with the exact target constraint relevant to the project. For example:
composer prohibits drupal/core-recommended ^11
composer why-not drupal/core-recommended ^11
Upgrade contributed modules before changing the major core constraint where possible. This reduces the number of variables in the core upgrade and often brings forward compatibility fixes that maintainers have already released.
composer update 'drupal/*' --with-all-dependencies --dry-run
composer update 'drupal/*' --with-all-dependencies
drush updatedb
drush cache:rebuild
Review the resulting dependency changes carefully. A broad update may include unrelated third-party libraries, so many teams prefer targeted updates followed by a deliberate dependency refresh. The correct balance depends on the age of the lock file and the project’s test coverage.
Move from Drupal 10 to Drupal 11 in a disposable environment
Drupal’s documented Drupal 10-to-11 process requires Drupal 10.3 or later, Composer, Drush and PHP 8.3 or later. The site should be updated to the latest suitable Drupal 10 release before the major-version change. ([drupal.org](https://www.drupal.org/docs/upgrading-drupal/upgrading-drupal/how-to-upgrade-from-drupal-10-to-drupal-11?utm_source=openai))
Once the codebase and contributed projects are ready, update the core packages together. The following pattern assumes the project uses the standard Composer package set; compare it with the existing composer.json before running it:
composer require \
'drupal/core-recommended:^11' \
'drupal/core-composer-scaffold:^11' \
'drupal/core-project-message:^11' \
--no-update
composer require 'drush/drush:^13' --no-update
composer update --dry-run
composer update --with-all-dependencies
If the project explicitly requires drupal/core as well as drupal/core-recommended, follow the project’s existing template and avoid creating conflicting package constraints. If Composer reports a problem, do not delete composer.lock as a first response. Identify the blocking package with composer prohibits, then update or replace that dependency deliberately.
After the code update, run database and configuration checks:
composer install --no-dev
drush updatedb:status
drush updatedb -y
drush config:import -y
drush cache:rebuild
drush status
Whether --no-dev is appropriate depends on the deployment process. Production installations normally omit development packages, while CI and test environments need the test tooling. The important principle is consistency: build the artefact in the same way every time and do not run Composer directly on production if your deployment process is designed to promote immutable artefacts.
Test behaviour, not just installation
A successful Composer update proves that dependencies can be resolved. It does not prove that the site is operational. At minimum, test:
- Anonymous and authenticated page delivery.
- Login, password reset, account cancellation and role permissions.
- Content creation, revisioning, moderation and scheduled publishing.
- Media uploads, image styles and private file delivery.
- Search, exposed Views filters and pagination.
- Multilingual negotiation, translated entities and language fallbacks.
- Webforms, email delivery, payment integrations and external APIs.
- Cron, queues, migrations, imports and scheduled jobs.
- Cache tags, reverse-proxy behaviour and authenticated-page exclusions.
For a custom module, run the project’s existing automated tests and add regression coverage around upgrade-sensitive services. A simple PHPUnit test that confirms a service can be constructed is useful, but an integration test that exercises the real route, entity query or form is more valuable.
vendor/bin/phpunit -c web/core/phpunit.xml.dist \
--testsuite unit \
web/modules/custom
vendor/bin/phpunit -c web/core/phpunit.xml.dist \
--testsuite kernel \
web/modules/custom
drush cron
drush watchdog:show --count=100
Run browser tests or Behat scenarios where the upgrade affects editorial workflows. Pay particular attention to administrative themes and toolbar-related customisations, because these areas often contain assumptions that are not covered by public-facing smoke tests.
Plan specifically for Drupal 12
Drupal 12 is not simply a newer Drupal 11 patch release. It removes deprecated code, updates dependencies and raises platform requirements. The Drupal project has also published change records covering removals and deprecations affecting themes, search, toolbar functionality, database APIs and other development areas. ([drupal.org](https://www.drupal.org/list-changes/drupal?utm_source=openai))
Drupal’s published Drupal 12 requirements state that PHP 8.5 is required. The project also announced that Migrate Drupal and Migrate Drupal UI will not be part of Drupal 12, although the Migrate API remains in core. Sites that still need to migrate from Drupal 6 or Drupal 7 should therefore complete that migration through Drupal 11 before moving to Drupal 12. ([drupal.org](https://www.drupal.org/project/drupal/releases/12.0.0-alpha1?utm_source=openai))
Review these areas before testing Drupal 12:
- Custom themes: confirm that the active theme does not depend on Claro or Olivero being present in core.
- Search: identify whether the site depends on the core Search module and select a supported replacement if necessary.
- Toolbar: audit administrative navigation customisations and contributed replacements.
- Migration: finish legacy Drupal migrations before the Drupal 12 move.
- Database: validate the target database engine and version against the announced requirements.
- PHP extensions: confirm that the PHP 8.5 runtime contains every extension required by Drupal and contributed packages.
- Security: test password creation and authentication flows when adopting Drupal 12’s Argon2id default password hashing behaviour.
Use a separate Composer branch for Drupal 12 testing. Never test an unreleased or release-candidate core branch by changing the production branch constraint in place.
git switch -c upgrade/drupal-12-readiness
composer validate --strict
composer audit
composer outdated --direct
drush updatedb:status
Keep Drupal 11 as the supported fallback until the Drupal 12 test environment has passed functional, performance and deployment checks. This is particularly important for sites with long content-editing sessions, large media libraries, complex access rules or high-volume queues.
Make the upgrade observable and reversible
Operational safeguards are as important as code compatibility. Define the deployment sequence before the change window:
- Put the application into an appropriate maintenance or read-only mode.
- Take and verify a database backup.
- Deploy the tested code and Composer lock file.
- Run database updates and configuration imports.
- Rebuild caches.
- Run smoke tests and inspect logs.
- Monitor queue workers, cron, PHP errors and response times.
- Keep the previous application artefact available for rollback.
Rollback planning must distinguish code rollback from database rollback. Reverting files does not automatically reverse database schema updates or configuration changes. If the upgrade includes irreversible schema changes, the rollback plan may require restoring the database backup and replaying any writes made after the backup. For a busy site, that may mean a planned content freeze or a carefully designed forward-fix strategy instead.
Use deployment-time checks to catch an incorrect runtime early:
composer check-platform-reqs
composer validate --strict
drush status --fields=drupal-version,uri,db-status
drush updatedb:status
drush config:status
A realistic schedule for the remaining window
Immediately
Confirm the current Drupal version, PHP version, database engine, hosting constraints and deployment method. Export configuration, create a tested backup and install Upgrade Status in a development environment.
Within the next sprint
Update Drupal 10 to its latest supported patch level, update contributed projects, remove obsolete extensions and resolve custom-code deprecations. Create a Drupal 11 branch and deploy it to a production-like environment.
Before the Drupal 10 deadline
Run the Drupal 11 upgrade in staging, complete editorial and integration testing, and promote Drupal 11 to production. Do not wait for the Drupal 12 release week if the organisation’s change-control process has a November freeze.
After Drupal 11 is stable
Build a separate Drupal 12 readiness branch, upgrade the platform to PHP 8.5 where required, test removed extensions and validate database compatibility. Treat Drupal 12 as a planned follow-up release rather than an emergency response to Drupal 10 reaching end of life.
Conclusion
The important deadline is not the day Drupal 12 ships. It is the point at which your organisation can no longer safely defer the Drupal 10 decision. With Drupal 10 support ending on 9 December 2026, the responsible path is to establish Drupal 11 compatibility now, remove deprecated code while it is still diagnosable, and leave Drupal 12 as a controlled second-stage upgrade.
A well-managed migration produces more than a new core version. It produces a reproducible Composer build, a tested deployment process, a documented platform baseline, automated compatibility checks and a rollback plan that the operations team understands. Those improvements reduce the cost of every future Drupal release—not just the one arriving in December.