| name | drupal-contrib-mgmt |
| description | Comprehensive guide for managing Drupal contributed modules via Composer, including updates, patches, version compatibility, and Drupal 11 upgrades. Use when updating modules or resolving dependency issues. |
Drupal Contrib Module Management
Core Update Workflow
Standard Module Update
composer require drupal/module_name --with-all-dependencies
composer require drupal/module_name:^3.0 --with-all-dependencies
composer require drupal/module_a drupal/module_b --with-all-dependencies
drush updb -y
drush cr
Major Version Upgrades
When upgrading to a new major version (e.g., 2.x → 3.x):
- Check compatibility: Ensure module supports your Drupal core version
- Search issue queue for patches:
https://www.drupal.org/project/issues/MODULE_NAME?categories=All
- Use Drupal Lenient for version requirement issues (see below)
- Apply patches via composer.json (see Patch Management section)
- Run upgrade_status to check for deprecations
Checking Drupal 11 Compatibility
Three methods to check if a module is D11 compatible (in order of preference):
Method 1: Check .info.yml File (Fastest, Most Reliable)
cat docroot/modules/contrib/MODULE_NAME/MODULE_NAME.info.yml | grep core_version_requirement
What to look for:
core_version_requirement: ^9.5 || ^10 || ^11
core_version_requirement: ^8 || ^9 || ^10 || ^11
core_version_requirement: ^9 || ^10
Example:
$ cat docroot/modules/contrib/admin_toolbar/admin_toolbar.info.yml | grep core_version
core_version_requirement: ^9.5 || ^10 || ^11
Method 2: Use Composer Commands (Works Before Installing)
composer show drupal/MODULE_NAME --all | grep -A5 "^versions"
composer show drupal/MODULE_NAME | grep versions
What to look for:
- Version number (e.g., 3.6.2)
- Check Drupal.org for release notes mentioning D11
Method 3: Check Drupal.org Project Page
Only use as fallback when above methods aren't conclusive.
https://www.drupal.org/project/MODULE_NAME
Look for:
- Latest release notes mentioning "Drupal 11"
- Module page header showing D11 compatibility badge
- Issue queue for D11 compatibility issues
Important Notes:
- ⚠️ Module may declare D11 support but still have deprecation warnings
- ⚠️ upgrade_status warnings don't mean module is incompatible
- ⚠️ "Check manually" status often means runtime version checks (false positive)
- ✅ If .info.yml declares
^11 support, module maintainer says it works
Real-World Examples:
$ cat docroot/modules/contrib/admin_toolbar/admin_toolbar.info.yml | grep core_version
core_version_requirement: ^9.5 || ^10 || ^11
$ cat docroot/modules/contrib/audiofield/audiofield.info.yml | grep core_version
core_version_requirement: ^8 || ^9 || ^10 || ^11
Drupal Lenient Plugin
The mglaman/composer-drupal-lenient plugin allows installing modules that haven't updated their version requirements yet.
Setup
{
"require": {
"mglaman/composer-drupal-lenient": "^1.0"
},
"config": {
"allow-plugins": {
"mglaman/composer-drupal-lenient": true
}
},
"extra": {
"drupal-lenient": {
"allowed-list": [
"drupal/module_name",
"drupal/another_module"
]
}
}
}
Usage
composer require drupal/module_name --with-all-dependencies
Patch Management (cweagans/composer-patches)
Patch Configuration
{
"require": {
"cweagans/composer-patches": "^1.7"
},
"config": {
"allow-plugins": {
"cweagans/composer-patches": true
}
},
"extra": {
"patches": {
"drupal/module_name": {
"Description of patch": "https://www.drupal.org/files/issues/2024-01-15/module-issue-1234567-8.patch",
"Local patch": "patches/custom-fix.patch"
}
},
"enable-patching": true,
"patchLevel": {
"drupal/core": "-p2"
}
}
}
Finding Patches
Issue Queue Search: https://www.drupal.org/project/issues/MODULE_NAME?categories=All
Patch Naming Convention:
- Format:
module-issue-NODEID-COMMENT.patch
- Example:
audiofield-d11-3432063-12.patch
- Node ID is the issue number (visit
drupal.org/node/NODEID)
When Existing Patches Fail After Update:
- Extract node ID from patch filename (e.g.,
3432063 from above)
- Visit
https://www.drupal.org/node/3432063
- Look for updated patch in latest comments
- Update composer.json with new patch URL
Creating Local Patches
cd docroot/modules/contrib/module_name
git diff > /path/to/project/patches/module-custom-fix.patch
diff -Naur original/file.php modified/file.php > patches/module-fix.patch
{
"extra": {
"patches": {
"drupal/module_name": {
"Custom fix description": "patches/module-custom-fix.patch"
}
}
}
}
Patch Application
composer install
composer install
composer update drupal/module_name --with-all-dependencies
Drupal 11 Compatibility Workflow
Step 1: Analyze Readiness
drush upgrade_status:analyze --all
drush upgrade_status:analyze module1 module2 module3
drush upgrade_status:analyze --all --format=json > d11-report.json
drush upgrade_status:analyze --all --format=codeclimate > d11-report-ci.json
drush upgrade_status:analyze --all --ignore-contrib
drush upgrade_status:analyze --all --ignore-custom
Step 2: Identify Issues
Major Issues (blocking):
REQUEST_TIME constant → Use \Drupal::time()->getRequestTime()
user_roles() → Use \Drupal\user\Entity\Role::loadMultiple()
file_validate_extensions() → Use file.validator service
system_retrieve_file() → No replacement (refactor required)
_drupal_flush_css_js() → Use AssetQueryStringInterface::reset()
Info.yml Issues:
- Update
core_version_requirement to include ^11
- Example:
core_version_requirement: ^9 || ^10 || ^11
Step 3: Fix Custom Code
Example: Inject Time Service
use Drupal\Core\Datetime\TimeInterface;
class MyController extends ControllerBase {
protected $time;
public function __construct(TimeInterface $time) {
$this->time = $time;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('datetime.time')
);
}
public function myMethod() {
$timestamp = $this->time->getRequestTime();
}
}
Example: Replace user_roles()
$roles = user_roles(TRUE);
use Drupal\user\Entity\Role;
$roles = Role::loadMultiple();
$role_options = [];
foreach ($roles as $role_id => $role) {
if ($role_id !== 'anonymous') {
$role_options[$role_id] = $role->label();
}
}
Step 4: Create .info.yml Patches
cd docroot/modules/contrib/module_name
git diff module.info.yml > /path/to/patches/module-d11-info.patch
--- a/module.info.yml
+++ b/module.info.yml
@@ -2,7 +2,7 @@
name: Module Name
type: module
description: Module description
-core_version_requirement: ^9 || ^10
+core_version_requirement: ^9 || ^10 || ^11
Step 5: Apply Patches & Update Lenient List
{
"extra": {
"patches": {
"drupal/module_name": {
"Drupal 11 .info.yml support": "patches/module-d11-info.patch"
}
},
"drupal-lenient": {
"allowed-list": [
"drupal/module_name"
]
}
}
}
composer install
drush updb -y
drush cr
Step 6: Verify Fixes
drush upgrade_status:analyze module_name
Complete Update Checklist
Troubleshooting
Patch Won't Apply
composer show drupal/module_name
Version Conflict
Patch Already Applied
Database Update Fails
drush pm:uninstall module_name
composer require drupal/module_name --with-all-dependencies
drush pm:enable module_name
drush updb -y
Best Practices
- Always use
--with-all-dependencies for module updates
- Always run
drush updb after composer updates
- Test immediately after updates (visit pages, check logs)
- Keep patches organized in a
patches/ directory
- Document patches with descriptive names and comments
- Check issue queues first before creating custom patches
- Use upgrade_status to validate D11 compatibility
- Commit atomically: one module update per commit
- Use descriptive commit messages with patch references
- Keep drupal-lenient list minimal (only when necessary)
Developing Contrib Modules Locally
When actively developing a contrib module for drupal.org, use this workflow to avoid constantly updating via composer:
Symlink Development Workflow
cd /tmp
git clone git@git.drupal.org:project/module_name.git
cd module_name
cd /path/to/project
rm -rf docroot/modules/contrib/module_name
ln -s /tmp/module_name docroot/modules/contrib/module_name
drush cr
cd /tmp/module_name
git add -A
git commit -m "Your changes"
git push origin 1.0.x
cd /path/to/project
rm docroot/modules/contrib/module_name
composer install
Benefits:
- Test changes immediately without composer update cycles
- Keep git history in the module's own repo
- Easy to commit and push changes
- No risk of accidentally committing module code to main project
Important Notes:
- Don't forget to remove the symlink before committing project changes
- Clear Drupal cache after changes:
drush cr
- When done developing, always reinstall via composer to ensure clean state
- Useful for fixing autoloader issues, adding features, or troubleshooting
Example: Fixing recurly_commerce_api autoloader issue
cd /tmp/recurly_commerce_api
git commit -m "Add PSR-4 autoload configuration"
git push origin 1.0.x
rm docroot/modules/contrib/recurly_commerce_api
composer install
drush cr
Common Patterns
Pattern: Update Module with Known Patch
composer require drupal/module_name:^3.0 --with-all-dependencies
drush updb -y
drush cr
git add composer.json composer.lock patches/
git commit -m "Update module_name to 3.0 with D11 compatibility patch"
Pattern: Fix Contrib D11 Issue
drush upgrade_status:analyze module_name
cd docroot/modules/contrib/module_name
git diff module.info.yml > ../../../patches/module-d11-info.patch
composer install
drush cr
drush upgrade_status:analyze module_name
Pattern: Major Version Upgrade with Breaking Changes
drush sql:dump > backup-before-update.sql
composer require drupal/module_name:^3.0 --with-all-dependencies
drush updb -y
drush watchdog:show --severity=Error --count=20
Reference Links