Problem / Motivation

domain_config_ui supports per-domain configuration overrides through ConfigFormBase forms (system.site, etc.). It does not currently surface those overrides on EntityForm-based config-entity admin flows (block, view modes, search pages, views, …):

  1. The parent module's form_alter only injects the "Enable domain configuration" toggle on ConfigFormBase and ConfigTranslationFormBase; EntityForm-based edit pages never see it.
  2. Drupal core's AdminPathConfigEntityConverter loads config entities override-free on every admin route, so the edit form for a domain-overridden block renders the base values.
  3. ConfigEntityListBuilder::load() calls loadMultipleOverrideFree() on the entity storage handler, so admin list pages render base values too. The same call path bites BlockListBuilder::submitForm(), where saving region or weight on a block whose label is overridden silently overwrote the existing override via the diff bridge in DomainConfigOverrideEditable::save().

Proposed resolution

A new experimental submodule domain_config_entity_ui in this project, layering five coordinated pieces on top of domain_config_ui:

  • DomainAwareConfigEntityStorageTrait overrides doLoadMultiple() to fold the active domain's override on top of the override-free read path. Regular loads (saves, runtime, drush, …) flow through unchanged: the trait short-circuits when $this->overrideFree is FALSE.
  • DomainAwareConfigEntityStorageInterface is an empty marker. The form_alter and ParamConverter both gate on $storage instanceof DomainAwareConfigEntityStorageInterface — capability discovery is runtime introspection rather than a hardcoded entity-type list.
  • DomainAwareConfigEntityStorage is a thin shell that uses the trait, implements the interface, and extends core's ConfigEntityStorage. DomainAwareSwapRegistry::computeSwaps() auto-discovers every config entity type whose default storage_class is ConfigEntityStorage (block, view modes, search pages, views, …) and registers a swap to this class. hook_entity_type_alter() applies it.
  • DomainConfigEntityUiFormHooks::formAlter() exposes the parent module's "Enable domain configuration" toggle on EntityForm-based config-entity edit pages, gated on the marker interface so types we do not cover never see the toggle.
  • DomainOverrideConfigEntityConverter runs at higher priority than core's AdminPathConfigEntityConverter. Same capability gate; loads override-merged for registered configs on the active domain, otherwise defers to core's behavior.

Two-stage opt-in

  1. Installing the submodulelifecycle: experimental plus Drupal's install confirmation prompt is the gross gate.
  2. Per-entity-type checkboxes on a SettingsForm at /admin/config/domain/config-entity-ui. Default install: empty. The user explicitly checks each entity type they want covered. The settings subscriber clears entity-type definitions on save so the swap takes effect on the next request without drush cr. The form opens with a yellow warning that only block has been validated end-to-end; the other auto-discovered types are best-effort and should be tested on a non-production environment first.

Extensibility — hook_domain_config_entity_ui_swaps_alter

Documented in domain_config_entity_ui.api.php. Contrib modules ship a sibling DomainAware*Storage subclass (extending the entity type's existing storage handler, using the trait, implementing the interface) and register it via the alter hook for entity types whose default storage_class is a custom subclass (image_styleImageStyleStorage, user_roleRoleStorage, menuMenuStorage, …). Modules can also REMOVE auto-discovered entries from $swaps to opt a vanilla-storage entity type out of coverage entirely.

Tests

Kernel (3 files, 11 tests / 49 assertions):

  • DomainAwareConfigEntityStorageTest — storage swap on install, override-merged read on registered configs, BlockListBuilder save flow regression, mid-request override re-read, non-curated entity type stays on its own handler, disabling un-checks the swap.
  • DomainOverrideConfigEntityConverterTest — converter capability gate (registered → override-merged, unregistered → defer to parent).
  • DomainAwareSwapRegistryAlterTest — alter-registered entries surface in the registry, coexist with auto-discovery, strict-equality guard rejects mismatched contrib registrations.

Functional (DomainConfigEntityUiToggleTest):

  • Toggle present on Configure block; absent on Place block; absent on uncovered user_role.
  • SettingsForm flips coverage on the next request (full UX path: empty → check block → toggle appears).
  • Save round-trip: toggle → register → edit label → save → per-domain override holds the new value, base config untouched, form re-renders override-merged. Skipped on environments where the parent diff bridge is missing (property_exists($baseData) probe).

Module info

  • name: Domain Configuration Entity UI
  • package: Domain
  • lifecycle: experimental, lifecycle_link points at this issue
  • core_version_requirement: ^10.3 || ^11 (uses \Drupal\Core\Form\ConfigTarget from 10.3)
  • configure: domain_config_entity_ui.settings_form (Configure link on the Modules admin page)
  • menu link: under Configuration → Domain → Domain config entity types
  • permission: administer domain config entity ui for the SettingsForm
  • dependencies: domain:domain_config_ui (no hard block dep — runtime is generic)

Related

  • domain #3587744 — parent issue, write-side correctness in domain_config; merged.
  • #3588057 / #3588108 — parallel "menu plugin manager cache leaks across domains" pair.
Command icon 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

mably created an issue. See original summary.

mably’s picture

Latest state on the MR (!32):

  • New submodule domain_config_ui_extraslifecycle: experimental. Depends on domain:domain_config_ui and drupal:block.
  • StorageDomainAwareConfigEntityStorage extends ConfigEntityStorage. Override doLoadMultiple() folds the active domain's override on top of the override-free read path for configurations registered on that domain. Stamps the entity with the domain cache context. Static cache trio is no-op (setStaticCache / getFromStaticCache / resetCache) so a mid-request override save isn't served stale; ConfigFactory's own cache still holds the underlying ImmutableConfig.
  • List builderDomainAwareBlockListBuilder extends BlockListBuilder is a thin createInstance adapter that hands a DomainAwareConfigEntityStorage instance to the parent constructor in place of the regular storage. load() and submitForm() inherit unchanged through the parallel storage. The block entity type's storage_class is left untouched, so saves, drush and any third-party API code keep seeing the regular ConfigEntityStorage.
  • Co-gated on the parent module's experimental flag domain_config_ui.settings.enable_config_entity_support (introduced in #3587744). Installing this submodule alone does not bypass the opt-in.
  • UX fixDomainConfigUiSettingsSubscriber listens to ConfigEvents::SAVE and clears entity-type definitions when the flag flips, so toggling the gate at runtime takes effect on the next handler resolution without a manual drush cr.
  • Latent block-list bug fixed for free — BlockListBuilder::submitForm now reads through the domain-aware storage, so saving region/weight on a block whose label is overridden no longer overwrites the existing override via the diff bridge.
  • Tests — kernel test DomainAwareConfigEntityStorageTest covers all four behaviour promises (co-gating round-trip, storage isolation, override-free read semantics, static-cache no-op). 4 tests / 26 assertions. strictConfigSchema is relaxed locally because the parent module's enable_config_entity_support schema key only ships from #3587744 on; can come back out once that lands and a tagged domain release picks the schema up.
  • PHPStan clean — handlers receive their domain services through the createInstance factory pattern (proper DI, no \Drupal::service()).
mably’s picture

Status: Active » Needs review
mably’s picture

Title: Add domain_config_ui_extras submodule with domain-aware list builders » Add domain_config_entity_ui submodule — per-domain support for config-entity admin pages
Category: Task » Feature request
Issue summary: View changes

  • mably committed 071cf17b on 3.x
    feat: #3588091 Add domain_config_entity_ui submodule — per-domain...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • mably committed 205d671c on 3.x
    fix: #3588091 Add procedural .module file with #[LegacyHook] delegators...