Drupal 9 will reach end of life by November 2023 and Drupal 10 was released in December 2022. Yet there are many contrib modules out there that have not released even an alpha or beta version with D10 compatibility. Some of them have many open, unresolved issues on the current version and there is discussion around this as part of a D10 release. Others are simply not as actively maintained and their maintainers have not yet responded to any support tickets (even the automated Drupal 10 migration ticket) in any way. A number of modules are somewhere in between, with active development on the D10 compatibility, whether they are just waiting for final testing before a new release or still need some work.

Some of the modules just need minor updates to the composer metadata and info files in order for them to work. Most of these modules have one or more patches or even pending issue forks with a pull request that can be used to provide a patch diff that works. But if you want to upgrade to Drupal 10, these modules will hold you back as composer will complain that they are not compatible and require an older version of Drupal.

So what to do?

Enter Custom Composer Repositories

As a short term solution, you can setup a custom repository in your composer file that will download the module directly from Gitlab so you can then apply a working patch. You also need to rename the package and update the require section accordingly.

You should see a "repositories" section in the composer.json file for a Drupal 9 site, usually with the keys "assets", "dev" and "drupal". If you don't, then you can add that section. In that section you can add custom repository definitions for the modules you want to download and patch. Here's an example of a definition for the Node View Permissions module, which is still awaiting a D10 release at the time of writing this article:

"node_view_permissions": {
    "type": "package",
    "package": {
        "name": "peter/node_view_permissions",
        "type": "drupal-module",
        "version": "1.4",
        "source": {
            "url": "https://git.drupalcode.org/project/node_view_permissions",
            "type": "git",
            "reference": "8.x-1.x"
        }
    }
}

Specifying the package type as "drupal-module" will cause composer to put it in the correct path - docroot/modules/contrib. Note the package name that's been changed from "drupal/node_view_permissions" to "peter/node_view_permissions". You can set the package name to anything you want, so long as it doesn't conflict with a real composer package name. It's best to keep the same [developer-name]/[package-name] format for consistency.

You'll want to make sure the "reference" is set to the active branch. There are a couple of ways to check what that is. One is to go to the "Version Control" tab on the drupal.org project page and click the dropdown next to "Branch to work from" to see the available branches.

Screenshot of version control tab for the node view permissions module on drupal.org

Alternatively you can go to the Gitlab repository and review the branches there. You'll probably need to go there anyway to get the repo URL.

After you've setup the repository definition you can add the patch to the "patches" section of the composer file. If you have no patches at all (which seems unlikely) then create it inside the "extra" section.

"peter/node_view_permissions": {
    "Drupal 10 support": "https://git.drupalcode.org/project/node_view_permissions/-/merge_requests/2.diff"
}

After that, simply update the "require" section and change the package name to the one in your custom repository definition. After that, run a composer update. Sometimes it might end up deleting the old one AFTER adding yours, resulting in the module not being present. If that happens, just run composer install again afterwards and it'll be restored.

"peter/node_view_permissions": "^1.4"

Once you've done that you're good to go and you can proceed to upgrade to Drupal 10. This will typically work until such time as that branch of the code in some way becomes incompatible with the next PHP version or something in another Drupal 10 core update.

It's important to check regularly for a new release so when one comes out you can remove all this and change the require back to the official package name and set the version number to the one for the D10 release.

That's it, hope this is helpful.