Drupal is a registered trademark of Dries Buytaert
Release: Leaflet 10.4.11 Minor update available for module leaflet (10.4.11). Release: Session Inspector 1.0.8 Minor update available for module session_inspector (1.0.8). Release: Migrate QA 2.0.4 Minor update available for module migrate_qa (2.0.4). Release: CKEditor Description List 3.0.0 Major update available for module ckeditor_descriptionlist (3.0.0). Release: FlowDrop 2.4.0 Minor update available for module flowdrop (2.4.0). Release: JWT Token Refresh 1.0.4 Minor update available for module jwt_token_refresh (1.0.4). Release: ConReg 1.0.0-beta1 First beta version released for module conreg (1.0.0-beta1). Release: AI Image Studio 1.0.0-beta8 New beta version released for module ai_image_studio (1.0.0-beta8). Usage Milestone: Statistics Counter Module statistics_counter crossed 1,000 active installs. Module Revived: Decoupled Router 2.0.7 Module decoupled_router updated after 11 months of inactivity (2.0.7).

This module prevents configuration exports when required modules are not enabled, addressing a common Drupal issue where dependencies are added but never activated. It provides a status report and blocks exports until the problem is resolved, offering clearer error messages than core Drupal.

Extension Guard finds modules your site requires but never turned on — a silent problem that normally surfaces months later as a failed deployment. It blocks configuration exports until the problem is fixed, and reports it on the status report in the meantime.

Here is how the problem happens. Every Drupal module lists the other modules it needs in order to work. Drupal checks that list once, at the moment you turn the module on, and never again. So when you update a module and the new version needs something extra, Drupal downloads that extra module but does not turn it on, and does not tell you. Your site keeps working perfectly.

Months later, someone installs or removes an unrelated module, Drupal finally re-checks, and a deployment fails with an error naming a module that has nothing to do with the problem. Nothing you ran in between could have warned you:

What you ran Why it did not notice composer install Composer did its job correctly. The module is downloaded and on disk. It just was never turned on. drush status Never looks at module dependencies at all. drush config:status The module is missing from your site and from your exported configuration, in exactly the same way. So there is no difference to report. drush config:import Only validates modules it is currently installing. If it is installing nothing, it checks nothing. drush core:requirements Only runs checks belonging to modules that are turned on. drush config:export then git diff The gap is part of your site's real state, so it exports cleanly and there is nothing unusual to review.

This is core issue #799314, a Major bug report that has been open since 2010. Drupal core ran into it itself in #3099746.

Features

  • Blocks drush config:export when a module your site needs is not turned on. This is the moment that matters: it is the command you already run after composer update, so there is no new step to remember and no new habit to build. The broken state never reaches your repository.
  • A read-only check, drush extension-guard:check, which reports the same thing and exits non-zero. Use it in CI, or to inspect a production site without exporting anything.
  • A status report entry that shows the problem continuously, so an existing inconsistency is visible without running any command.
  • Names the module actually at fault. Drupal's own error message names whichever module happened to trigger the check, which can be several steps removed from the real cause. This module names the module that declares the missing dependency, and lists everything that depends on it.
  • Detects malformed dependency declarations. An info.yml file whose dependency list is written without the leading hyphen is parsed as plain text and silently discarded by Drupal, so that dependency is never enforced at all. That is a bug in the module itself, so it is reported as a warning rather than blocking you.

When would I use this? Any site where configuration is exported to code and imported on deployment, which is essentially any site with a staging or production environment. The risk grows with the number of contributed modules you have and the frequency of composer update.

Typical use cases: catching the problem on a developer machine right after a dependency update; failing a CI build before a release is tagged; and auditing an existing production site, where this class of problem has often been sitting unnoticed for months.

Post-Installation

There is nothing to configure and no page to visit. The module starts working the moment you install it.

Two things happen immediately:

  1. Visit Reports, then Status report. You will see a Module dependency closure entry: green if everything your site needs is turned on, red if it is not.
  2. Run drush cex. If something is missing, the export is refused and you are told exactly what to do.
$ drush cex
 [error] The module dependency graph is not closed.

         Required by an installed module, but not installed:

           svg_image  (required by focal_point_extras)
           paragraphs_library  (required by my_layouts, my_blocks)

         This usually means a package update added a dependency to a module that
         was already installed. Drupal does not enable it for you, and no other
         command reports it.

         Fix:  drush pm:install svg_image,paragraphs_library
         Then: drush config:export

Run the suggested command, export again, and commit the resulting core.extension.yml change along with your composer.lock.

Recording deliberate exceptions. Occasionally an unmet dependency is intentional, usually an upstream packaging mistake where a module declares a dependency that duplicates something your site already has. Record those instead of switching the check off:

drush config:set extension_guard.settings ignored_modules.0 some_module

Guarding deployments as well (optional). drush config:import is deliberately not guarded by default, because it runs inside drush deploy, and a site that already has this problem would find its deployments blocked with no warning. Turn it on once you know your production site is clean:

drush config:set extension_guard.settings enforce_on_config_import 1

Bypassing once. If you need an export before you have finished resolving things:

EXTENSION_GUARD_SKIP=1 drush cex

An environment variable is used deliberately, rather than a yes/no prompt. Exports and deployments normally run with the -y flag, and a prompt would answer itself and silently disable the check.

All settings live in extension_guard.settings. There is no administrative form yet. See the issue queue if you would like one.

Additional Requirements

  • Drupal ^11.2. The module uses hook_runtime_requirements() and the RequirementSeverity enum, both introduced in Drupal 11.
  • Drush ^13. Required, not optional: the config:export guard is implemented as a Drush command validator. Sites that do not use Drush will get the status report entry only.

No external libraries, APIs or services are needed.

Nothing is required to get full functionality. These are complementary rather than enhancing:

  • Module Missing Message Fixer handles the opposite problem: modules recorded as installed that are missing from disk. Between the two, both directions of the inconsistency are covered.
  • Site Audit provides broad site auditing, including unmaintained and unused modules. Useful alongside this, and it does not overlap.

Similar projects

As far as I could determine, nothing else performs this specific check. The closest projects, and how they differ:

  • Module Missing Message Fixer solves the mirror image: modules listed in core.extension that are absent from the filesystem. It explicitly does not check whether an installed module's dependencies are turned on.
  • Site Audit inspects the installed module landscape, flagging unmaintained, dev-only and apparently unused modules, but not unsatisfied ones.
  • Configuration Update Manager compares individual configuration items against the defaults their modules ship. It works one level below this: it assumes the module list itself is correct.
  • Configuration Inspector validates configuration against its schema. A different question entirely.
  • Config Enforce changes which copy of your configuration is authoritative. It does not examine module dependencies.

If you know of a project that does cover this, please open an issue. I would rather contribute there than duplicate it.

Supporting this Module

There is no funding link. The most useful support is a bug report: this module reads dependency data from every extension on your site, and real sites contain declarations that are stranger than anything I could invent. The malformed-declaration detection exists because a widely used contributed module turned out to have one.

The other way to help is upstream. The runtime check here is what core issue #799314 asked for in 2010 and never got. Support on that issue is worth more in the long run than this module is.

Community Documentation

None yet. If you write a walkthrough or record a video, please open an issue and it will be linked here.

Background reading on the underlying behaviour:

  • #799314 Checking/resolving new dependencies for updated modules. Major, open since 2010.
  • #3099746 When importing configuration, enable modules' dependencies even if they are not specified.
  • #3432353 Add validation constraints to core.extension. Added a constraint checking that each listed module exists on disk, but not that the list is complete.

Design notes

Only directly declared dependencies are examined, not Drupal's computed transitive dependency list. The latter is the full closure, and using it would report a single missing module once for every installed module that can reach it, dozens of times on a large site, with the actual culprit buried. A module list that is complete with respect to declared dependencies is automatically complete with respect to transitive ones, so this is both more precise and equally thorough.

drush deploy is not hooked, and does not need to be. It runs without bootstrapping Drupal, so module-supplied commands are never discovered there, and it performs no work of its own: it runs each step as a separate Drush process. Guarding config:import covers the deployment path precisely.

Activity

Tracked releases
1
Tracked since
Aug 2026
Latest release
1 week ago
Releases (12 mo)
1 ▲ from 0
Maintenance
Active

Releases

Version Type Release date
1.0.0-alpha1 Pre-release Aug 13, 2026