Problem/Motivation
The decoupled_config module currently uses a @ConfigFilter plugin (DecoupledConfigFilter) to merge YAML fragments during config import and strip them during config export. This operates at the FilteredStorage layer provided by the config_filter module.
However, config_split (the most widely used module for splitting configuration by environment, site, or feature) operates at a completely different layer: Drupal core's StorageTransformEvent (config.transform.import / config.transform.export). These two layers do not interact with each other during the config import/export pipeline.
As a result, decoupled config fragments are only merged into configuration items that exist in config/sync/. Any configuration item that is managed exclusively by a config_split (i.e., it only exists in a split directory and not in config/sync/) will never have decoupled fragments applied to it.
This is a significant limitation because config_split is the standard approach for managing feature-specific or environment-specific configuration. For example, a role defined in a feature split cannot receive permissions from other modules via decoupled_config fragments.
Steps to reproduce
- Install
decoupled_configandconfig_split. - Create a config split (e.g., a feature split) and place a role configuration (
user.role.example_role) exclusively in that split directory (not inconfig/sync/). - In a separate module, create
decoupled_config/user.role.example_role.ymlwith additional permissions to inject. - Run
drush cim -y. - Expected: The role has the permissions from the decoupled fragment merged in.
- Actual: The decoupled fragment is silently ignored. The role does not receive the additional permissions.
Proposed resolution
Replace the @ConfigFilter plugin with an EventSubscriber that listens to Drupal core's StorageTransformEvent. This places decoupled_config on the same layer as config_split, so it can see and modify all assembled configuration — both from config/sync/ and from any active config split.
The subscriber uses two priorities to ensure correct ordering relative to config_split:
- Import (
config.transform.import, priority -100): Runs afterconfig_split(default priority 0), so split configurations are already present in the storage when fragments are merged. - Export (
config.transform.export, priority 100): Runs beforeconfig_split(default priority 0), so injected fragment values are stripped beforeconfig_splitwrites configuration back to split directories. This prevents fragment values from "leaking" into split YAML files ondrush cex.
The merge logic (NestedArray::mergeDeep) and strip logic (DecoupledConfigArrayHelper path-based removal) are ported directly from the existing DecoupledConfigFilter::filterRead() and filterWrite() methods.
Changes:
- New:
src/EventSubscriber/DecoupledConfigTransformSubscriber.php— the core subscriber with import merge and export strip handlers. - Modified:
decoupled_config.services.yml— register the new event subscriber service. - Removed:
src/Plugin/ConfigFilter/DecoupledConfigFilter.php— the@ConfigFilterplugin is replaced entirely. Keeping both would cause double-merge forconfig/sync/items (once by the filter during storage read, again by the subscriber during transform). - Modified:
decoupled_config.info.yml— remove theconfig_filter:config_filterdependency, as the module now only uses Drupal core APIs (StorageTransformEvent,ConfigEvents).
No changes to DecoupledConfig, DecoupledConfigYamlDiscovery, DecoupledConfigArrayHelper, or the decoupled_config/ directory convention in modules. Existing module fragments continue to work without modification.
Remaining tasks
- Review and commit the patch.
- Verify backward compatibility: modules using existing
decoupled_config/fragments should work without changes. - Verify that
config_filtercan be safely uninstalled after this change if no other module depends on it.
User interface changes
None.
API changes
- The
config_filter:config_filtermodule dependency is removed.decoupled_confignow depends only on Drupal core APIs. - The
@ConfigFilterplugindecoupled_config_filteris removed. Any code referencing this plugin ID will need to be updated (unlikely, as it was an internal implementation detail). - The
DecoupledConfigservice andDecoupledConfigInterfaceremain unchanged. Thedecoupled_config/directory convention for YAML fragments remains unchanged.
Data model changes
None.
Issue fork decoupled_config-3613621
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
jansete commented