"Disabling" modules in Drupal 8

 

Drupal 8 has a lot of differences compared to Drupal 7. One of the most perplexing to site builders is that you cannot disable modules any longer. Still, disabling modules was handy! If you suspected a module was the cause of a bug or a performance issue, the solution was simple: disable it. No longer needed a feature, but didn't want to commit just yet? Disable! 

Why was this feature removed -- and more importantly -- what can we do to get it back?

From a module developer's perspective, disabling a module leaves things in an odd and precarious state. A module's code is now denied from being run, yet configuration and content related and reliant upon the module remains in site. Depending on the nature of the module, these effects can be safe and ignorable, to the dreaded White Screen of Death. 

After a lot of internal debate, the developers involved early on in Drupal 8 decided to remove the ability to disable modules entirely. You now only have two choices, install or uninstall. This eliminated a lot of strange edge cases and closed a lot of potential loops when working with a Drupal powered website.

One of the common ones for site builders was the "upgraded disabled module" problem. Imagine first disabling a module. Later when you understandably forgot disabling the module, you go through your site and update all of the modules. When you try to Re-enable the module later, the module code is expecting a completely different set of configuration and data compared to what it has in the database. This happens easily when working independently or with fully manual processes that are common with Drupal 7 sites. To solve the problem, you need to find out the version of the module when it was last enabled, downgrade the module, re-enable it, then upgrade. This may be impossible though without a database rollback. 

This problem alone is a enough to give us night-sweats, but dumping disable was necessary for Drupal 8 to have "Features in Core" (CMI) without weirdness and hacks.

The problem remains, however, that disabling a module was really, really useful. Uninstalling a module means we lose all the data associated with that module. In Drupal 8, that includes both configuration, and content. Depending on the complexity of the module, re-configuring everything can be a tedious and error prone process.

If our site is managed with a version control system such as git, we could go back in history and extract the necessary configuration files. If we were experienced with git, we may even have created a tag to identify the last commit before the module was uninstalled. That would work, but git can be a monster for non-developers.

While it's arguable that any Drupal professional should be familiar with git, that alone isn't a good enough reason. Outside of emergency disabling of modules, there are often circumstances in Drupal 8 where we still want to remove the module operationally, but maintain it's data for later use. The problem is uninstalling a Drupal 8 module on a live site would leave the config directory out of sync. If git deploy was used, the git directory itself is now dirty and requires cleanup later. 

So what can we do to temporarily remove a module from a site, but keep its data?

An essential part of a modern Drupal 8 site is to use the Configuration Split module. This module allows a site's configuration to be segmented into one or more "splits". This can be as granular as individual config sets, or as blunt as entire modules. Its often used to segment "developer" modules such as devel, Twig XDebug, and Stage File Proxy. This split can then be enabled when working on the site locally or in a shared, non-production environment. 

A configuration split doesn't have to be just by environment. You can segment your configuration in any way that works for you. In the case of disabling Drupal 8 modules, we can create a module-specific split. Once set up, we then only need to change a single line in our settings.local.php file, and re-import the configuration!

To set things up, we need to first export our configuration sync directory. By default, Drupal 8 maintains this internally in the sites/default/files directory. The best practice is to first externalize this directory and have it be outside of the web root. 

First, create a directory outside of your web root. Create a directory named config/, and inside of it, a single subdirectory named sync/.

/path/to/site
├── config
│   └── sync
└── docroot
    ├── core
    ├── index.php
    ├── libraries
    ├── modules
    ├── profiles
    ├── sites
    └── themes

While we're at it, let's also create a directory we'll need for the split. If we want to disable, say, Google Analytics, we create a google_analytics/ subdirectory under config/:

/path/to/site
├── config
│   ├── google_analytics
│   └── sync 
└── docroot
    ├── core
    ├── index.php
    ├── libraries
    ├── modules
    ├── profiles
    ├── sites
    └── themes

Next, we'll change our settings file to externalize the configuration:

$config_directories[CONFIG_SYNC_DIRECTORY] = '../config/sync';

The above intentionally uses a relative path to the Drupal docroot. This way, it's portable no matter what server the site is running on.

Once that's done, we can export the directory using the drush command line utility:

cd path/to/site
drush config-export

You will be prompted to confirm the export operation. Do so, then your site configuration should be properly externalized. 

Next, we can set up Config Split. Config Split depends on another module, Config Filter. We install both using Composer:

cd path/to/site
composer require drupal/config_filter
...
composer require drupal/config_split
...
drush en config_filter config_split

Now we can split off the module. 

  1. Login to the site with administrator privileges.
  2. Navigate to Admin > Config > Development > Configuration Split Settings.
  3. Click Add Configuration Split Setting.
  4. Enter a Label of your choosing.
  5. For the Folder, enter the relative path to the module-specific split directory you created earlier. For our Google Analytics example above, we use: ../config/google_analytics
  6. Assure the Active checkbox is checked.
  7. Under the Complete Split section, select the Modules to move to the split.
  8. Click Save.

Now we can do a configuration export to "disable" the module:

$ cd path/to/site
$ drush cex

Differences of the active config to the export directory:

 Collection  Config                                      Operation 
             config_split.config_split.google_analytics  create    
The .yml files in your export directory (../config/sync) will be deleted and replaced with the active config. (y/n): y
Configuration successfully exported to ../config/sync.

$

Huh? Google Analytics was already installed, why is it creating the configuration? Before the configuration was in our primary sync/ directory, now that we're moving it to our module-specific google_analytics/ directory, Drupal needs to re-create the configuration. Since we're just moving the configuration to the split, and we selected Active earlier when creating the split, no data was lost.

If we look at our google_analytics/ directory, we can see it's now populated with a config file:

/path/to/site
├── config
│   ├── google_analytics
│   │   ├── .htaccess
│   │   └── google_analytics.settings.yml
│   └── sync 
└── docroot
    ├── core
    ├── index.php
    ├── libraries
    ├── modules
    ├── profiles
    ├── sites
    └── themes

Drupal creates the .htaccess file for us as a security precaution.

If our site is set up as a git repository, we can see several other changes:

$ git status
On branch 8.0
Your branch is up to date with 'origin/8.0'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   config/sync/core.extension.yml
	deleted:    config/sync/google_analytics.settings.yml

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	config/google_analytics/
	config/sync/config_split.config_split.google_analytics.yml

no changes added to commit (use "git add" and/or "git commit -a")

The module configuration we selected was removed from our primary config/sync/ directory and into our module-specific config/google_analytics/ directory. The core.extension.yml file was also modified to uninstall the target module. Yet, when we go to Admin > Extend, the module is still there and functioning!

At this point you should commit your changes to your repository, thus preserving the new split and the moved configuration:

$ git add -A 
$ git commit -m "Moved Google Analytics to a module-specific split."
[8.0 3bf638a1] Moved Google Analytics to a module-specific split.
 5 files changed, 40 insertions(+), 3 deletions(-)
 create mode 100644 config/google_analytics/.htaccess
 rename config/{sync => google_analytics}/google_analytics.settings.yml (100%)
 create mode 100644 config/sync/config_split.config_split.google_analytics.yml

With the module-specific split set up, we can now disable the module whenever we want. There are two ways to do this:

  • By clearing the Active checkbox by editing the module-specific split.
  • By adding a line to settings.local.php.

The first does work, but it changes the configuration state of the site, marking your config directory as out of sync. If you're tracking your config directory in git -- and you should! -- it's also now in a dirty state. This is no better than uninstalling the module completely and using git to restore it later.

To keep the config sync and git happy, we need to leverage another piece of Drupal's configuration system, overrides. Config overrides allow you to add a line of code to the settings.php file to change configuration on the fly without affecting your sync status. Ideally, you should have a settings.local.php file set up for your site. This file is then listed in .gitignore so git does not attempt to track the file. 

With the settings.local.php file set up, you can add the following line to disable our target module:

$config['config_split.config_split.google_analytics']['status'] = FALSE;

Note that the $config variable key:

config_split.config_split.theSplitName

Where:

  • theSplitName is the machine name of the split we created.

The next time we do a configuration import using Drush, the target module is uninstalled:

$drush cim
 Collection  Config                                      Operation 
             config_split.config_split.google_analytics  create    
             core.extension                              update    
             google_analytics.settings                   delete
Import the listed configuration changes? (y/n): y
Synchronized extensions: uninstall google_analytics.                                                                                                                        [ok]
Synchronized configuration: create config_split.config_split.google_analytics.                                                                                              [ok]
Finalizing configuration synchronization.                                                                                                                                   [ok]
The configuration was imported successfully.

We can just as easily re-enable the module by changing the value to TRUE, saving the file, and then re-importing:

$ drush cim
 Collection  Config                     Operation 
             google_analytics.settings  create    
             core.extension             update
Import the listed configuration changes? (y/n): y
Synchronized extensions: install google_analytics.                                                                                                                          [ok]
Finalizing configuration synchronization.                                                                                                                                   [ok]
The configuration was imported successfully.

While this works, there are a few problems with it that can make it a but cumbersome to use.

  • While no git is involved, you do need to use drush, edit config files, and other developer-y things.
  • Getting the split to work the first time can be tricky, and is best done on a local dev environment first.
  • For best granularity, you have to create a split for each module individually. 
  • Config is preserved, but content in the database is still lost.

That last one is can be a deal breaker depending on the module. A module which relies mostly on configuration, like Google Analytics, gets the most advantage from this approach. A content providing module like Flag wouldn't be so lucky as Flaggings are stored as content. 

Despite the numerous downsides, using creating module-specific configuration splits can be an effective means to "disable" a Drupal 8 module. It preserves your existing module configuration by moving it to the split. By leveraging a configuration override to enable the split, you can keep your configuration directory in sync and your git directory clean. 

This post was created with the support of my wonderful supporters on Patreon.

If you like this post, consider becoming a supporter at:

Thank you!!!