# Summary

My experience upgrading my first production site from Drupal 9 to 10. Had run quite a few test Drupal 10 sites, and upgraded some tests but this was the first live upgrade of a real site with all the warts and contrib modules that usually entails. Despite it being almost 3 weeks until Drupal 10's launch, it was fairly smooth - certainly smoother than most D8->9 upgrades even a couple weeks post D9 launch.

What production site you might ask? You're reading this on Drupal 10 right now. Not a massive site, but decent functionality especially on the backend/currently unpublished sections. Given timeline pre-launch was a good practice run for my larger sites/work sites and got a few patches out and also published one new module version with Drupal 10 support. By next weekend I should have a bunch more of my modules with published versions as well as I continue to test some backend features.

# Starting conversion

Drupal 10 has removed two modules that I was using - RDF and CKeditor 4 support. RDF I wasn't using seriously on this site, so I simply uninstalled it. I then added the contrib ckeditor 4 module, because I decided not to convert to CKEditor 5 today - instead using the contrib space module provided to ease that transition.

Before making further changes, I deployed this - that way I don't have to worry about the missing module in the production environment making uninstall a problem later. Having the duplicate ckeditor 4 module wouldn't cause any problems - duplicate modules are simply ignored and modules in core take precedence. Long term I dislike duplicate modules and they a very minor performance negative but short term for this process was very much not a concern.

Additionally, the core theme classy was also deprecated in Drupal 9 and has been removed in Drupal 10 - like ckeditor, I decided not to fully migrate away from that and used the contrib replacement theme. As a bunch of the theme functionality in this site is shared with some other sites I will update my internal base theme and all the custom themes after that.

Next I updated my custom modules and theme functions - basically nothing to do other than update the version specs in the .info.yml file as I had been keeping up with deprecations fairly well. Had to update a couple of get_path functions and that was done - didn't even bother running these through rector because I was confident they were good to go.

# An unexpected early hurdle

Next came the steps that I expected would take a bit of work but should be straight forward. I knew I was using some modules that weren't yet up to Drupal 10 with any releases. On the face of it with the beloved (by me anyways) composer patches plug-in that isn't a problem especially with the automatic readiness patches the DA has running. However, there is one big flaw there. Composer patch plug-in runs after composer checks dependency versions - so you can't require the modules.

Luckily,  Matt Glaman has stepped up with another great tool - Composer Drupal Lenient. This is a composer plugin that let's you specify Drupal packages to ignore the core dependency conflict on. That was processing easily until suddenly hit a module that was an indirect dependency. My understanding was that would work but it wasn't! So I tried adding it to my direct dependencies. That did nothing. Removed it and went digging into my first challenge. 

Turned on xdebug. Ran it again and started walking through modules as each dependency was analyzed. Got to the module that had been failing drupal/calendar_datetime and then it made total sense. That's not a full drupal project. You can have multiple modules within a single Drupal project - like Views Slideshow has the submodule Views Slideshow Cycle or in this case drupal/calendar has the submodule Calendar DateTime. The Drupal composer facade exposes each submodule as a composer package specifically of the type metapackage. However, the Composer Drupal Lenient plugin only affects packages of type drupal-*. There are definitely good reasons to require the metapackages for dependencies, however in this case I believe that drupal/calendar is misusing the feature. Regardless, though it is a possible package type that should be updated with the module.

So my first patch and pull request of the day happened. In the boring side of things, I then added my patch to my composer.json and ran it. At which point it failed hard. Because the way composer patches works, is it removes the thing to be patched before composer dependency resolution takes place. So if the install ran the right order, that could work, but any deploy situation would be at best fragile and in most real cases would break. That though was a very easy fix that needed no debugging for me. Instead of using my patch, I used my entire branch as the version of Composer Drupal Lenient to install.

Finally! Smoothly from there on finding out what modules didn't have a compatible release. I ended up ignoring core dependencies on about 15 contrib modules.

# Patches Galore!

Just having the dependencies installed however doesn't actually make them work, so it was time to look into the modules that weren't compatible. Mostly just going to the issue queue for each one and applying patches - often the automatic rector patch, sometimes manual patches.

Once I had patches for all of them added, it was time to start testing. Totally expected a few still didn't work - in some cases even for modules that were reporting Drupal 10 compatibility. Ended up creating, a few patches for different projects.

Additionally, marked a few patches after testing as good to go (RTBC). More importantly from the standpoint of other sites being easier to upgrade:

  • I released a new version of Fitvids
  • Worked on a new version of Color Field
  • Worked on a new version of a couple other modules that I maintain.

I expect to release at least 4 new module versions by the end of the week.

Partial patches file

  "patches": {
    "drupal/calendar": {
      "Twig Update - 2022-11-27 - Created Patch [NickDickinsonWilde] - Needs Review": "https://git.drupalcode.org/project/calendar/-/merge_requests/9.diff"
    "drupal/easy_breadcrumb": {
      "D10 support - 2022-11-27 - Created Patch [NickDickinsonWilde] - Needs Review": "https://git.drupalcode.org/project/easy_breadcrumb/-/merge_requests/68.diff"
    "drupal/exif": {
      "D10 support/wrong type to entity query - https://www.drupal.org/project/exif/issues/3324007 - 2022-11-27 - Created Patch [NickDickinsonWilde] - Needs Review": "https://git.drupalcode.org/project/exif/-/merge_requests/8.diff"
    "drupal/facets": {
      "D10 support": "https://www.drupal.org/files/issues/2022-11-11/support_d10.patch"
    "drupal/facets_block": {
      "D10 Support": "https://www.drupal.org/files/issues/2022-06-15/facets_block.1.x-dev.rector.patch"
    "drupal/imagick": {
      "D10 - RTBC": "https://www.drupal.org/files/issues/2022-06-15/imagick.1.x-dev.rector.patch"
    "drupal/imageapi_optimize_binaries": {
      "D10 Support - RTBC": "https://www.drupal.org/files/issues/2022-07-23/imageapi_optimize_binaries.1.x-dev.rector.patch"
    "drupal/layout_paragraphs": {
      "D10 Support - 2022-11-27 - Marked as RTBC [NickDickinsonWilde] - RTBC": "https://www.drupal.org/files/issues/2022-11-08/layout_paragraphs-n3288247-9.patch"
    "drupal/subpathauto": {
      "D10 - RTBC": "https://www.drupal.org/files/issues/2022-06-16/subpathauto.1.x-dev.rector.patch"
    "drupal/svg_image": {
      "D10 - RTBC": "https://www.drupal.org/files/issues/2022-10-15/3299276-7_0.patch"
    "drupal/redirect": {
      "Already encoded URLs are double-encoded, creating broken redirects": "https://www.drupal.org/files/issues/2021-01-29/redirect-doubleEncoded-3194561-3.patch"
    "drupal/views_templates": {
      "D10": "https://www.drupal.org/files/issues/2022-07-23/views_templates.1.x-dev.rector.patch"
    "drupal/webform": {
      "D10 - NW": "https://www.drupal.org/files/issues/2022-11-18/webform-d10-compatibility-3262067-19.patch"

# Conclusion

In significantly under a day and with only one real challenge I had done a full upgraded from Drupal 9 to 10. The three patches I created for modules that I don't maintain were pretty simple. After I finish releasing updates to the rest of my modules, later this week I'll be upgrading another one of my own sites - Sooke Karate. I expect that that one will take in the range of 1-2 hrs given my experience here and being able to copy/paste half of my composer patches definitions. I'm quite frankly ecstatic about that timeline and expect to have all my personal/small client sites on Drupal 10 the day after Drupal 10's launch.

Update - 2022-12-03

Upgraded to CKEditor 5 too. Extremely simple enable ckeditor 5 module,  edit each text format, select ckeditor5, follow any changes noted/review log and maybe make adjustments, save. Took less than 20 minutes for this site - and that was mostly qa.

Also, one of my patches has been merged and a new major version is available for Stage File Proxy, and a new release of Webform Spam Words has been released, so will have significant reduction in patches/lenient projects by the time Drupal 10 is actually stable released

Less story! More code!

Here's a summary of the commands and steps without story and slightly adjusted for efficiency/what I would do next time).

composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true
composer require drupal/rdf drupal/ckeditor mglaman/composer-drupal-lenient
drush pmu rdf -y
drush cex -y
git commit -a -m "Prep for Drupal 10"
git push [and your testing/deploy workflow]

Update custom code - depending on complexity/volume using my own knowledge or getting started with using Drupal Rector and then post cleanup by hand. The vast majority of changes are backwards compatible between 9.3+ and 10.x unless they involve symphony/dependency updates.

 Edit composer.json to have ^10.0@beta as version constraint to all drupal/core-* packages.

Run composer outdated and update version constraints for any Drupal contrib projects having new major versions (with looking at/knowing that is a good version etc)

Run composer update - probably repeatedly and adding packages to the lenient list until it installs. My lenient configuration for this project

        "drupal-lenient": {
            "allowed-list": [

 Going through each lenient allowed package and looking for patches. If no patches, making patches.

drush updb -y

 Testing if everything works! If yes:

drush cex -y
composer remove drupal/rdf
git commit -a -m "Upgrade to Drupal 10"
git push [and your testing/deploy process]