Background

Domain 3.x replaced the in-form domain selector (the old SwitchForm from domain_config_ui 2.x) with a negotiation-based model: per-domain configuration overrides live in config collections and always apply to the active (negotiated) domain — the hostname currently being browsed. See the related Domain support request #3592797.

This is deterministic and clean, but it drops a workflow some sites relied on: administering every domain's configuration from a single (main) domain. Today an admin must browse to each domain to edit its overrides.

Proposal

Add an opt-in domain_extras submodule (working name domain_config_switcher) that reintroduces a domain (and language) selector for domain-specific configuration, without changing the deterministic default Domain 3.x ships. It pairs naturally with domain_sso for a fully centralized admin experience.

Feasibility

This can be built entirely against Domain's public service interfaces — no dependency on @internal classes, and no changes required in domain or domain_config_ui:

  • DomainConfigFactoryOverrideInterface: getOverride($domain_id, $name) and getOverrideEditable($domain_id, $name) already read and write any domain's collection.
  • DomainConfigUIManagerInterface: addConfigurationsToDomain($domain_id, ...) and friends manage the per-domain overridable registry.
  • DomainNegotiationContext: setDomain() can set the active domain for a request.

All three are public, service-aliased and autowireable.

Two possible approaches

  • Option A (session re-negotiation): a request subscriber scoped to the config-UI admin routes reads a session-selected domain and calls DomainNegotiationContext::setDomain(). The whole config form then reflects the selected domain — display, save, access and cache suffix all follow the single negotiation lever. Closest to the 2.x "page reloads and values change" behaviour; must be admin-route-scoped to avoid switching theme/blocks and to keep the domain cache context contained.
  • Option B (form-only): leave negotiation untouched; populate the config form's display values from the selected domain via getOverride() and route the save through getOverrideEditable(). More surgical and cache-safe (admin config forms carry form tokens and are not page-cached), at the cost of per-form value interception.

Scope and open questions

  • Option A vs Option B (leaning B for cache-safety, A for fidelity to 2.x).
  • Language dimension: the selector should carry a language alongside the domain, matching the 2.x switcher.
  • Access: checks should key on the selected domain rather than the active one.
  • UI: a selector block/form storing the choice in session, plus a permission to use it.

Feedback welcome on the preferred approach before implementation.

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

Pushed a first implementation to the issue fork branch 3592820-new-submodule-domain (the new submodule lives at domain_config_switcher).

Approach (Option A): a per-user selection (private tempstore) is applied as the active domain on administrative routes via DomainNegotiationContext::setDomain(). The browser stays on the current host; only the negotiated domain used by the configuration override layer changes, so the Domain Config UI forms read and write the selected domain's overrides. Selecting "- Current domain -" clears it.

Config translation needs no special handling: it is reached through the standard Translate tab, and domain_config_language_ui already resolves getDomainOverride(activeDomain, langcode), so switching the domain scopes translations to the selected domain automatically. The switcher stays domain-only.

Public API only: built against DomainConfigFactoryOverrideInterface, DomainConfigUIManagerInterface and DomainNegotiationContext. No dependency on @internal classes and no changes to domain or domain_config_ui.

Test coverage:

  • Kernel: permission gating, assigned-domain restriction for non-super users, selection round trip, rejection of unknown ids, and stale-selection drop (4 tests).
  • Functional: the block honours the permission, switching then saving the basic site settings writes the override to the selected domain and not the default domain, and clearing returns to the current domain (2 tests).

PHPCS, PHPStan and cspell are clean.

Feedback welcome on Option A versus a form-only variant before this is proposed for a release. I can open a merge request against 3.x if the approach looks right.

mably’s picture

Status: Active » Needs review

  • mably committed bc0e21e0 on 3.x
    feat: #3592820 New submodule: Domain Config Switcher — restore the 2.x...
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.

hanoii’s picture

Hi! Thanks for all the amazing and speedy work you are doing. I just tried this module. Some feedback:

- ON the project I am testing for now I only overrode two forms: System information and Appearance for a single theme.

#1. The system information works really good. The only thing I wonder is the fact that if that by choosing a domain, as this is stored in a temp store now every form will respect that. I know this is like the remember setting of the 2.x behavior but I have the feeling this is dangerous. I would either keep that as an opt in or go back to something more url-like with the query string. So if you navigate away you lose the domain configuration context. For a configuration of every config along the way, maybe you can go the switch to domain route.

#2. The appearance form doesn't work. I thought because this one was overridden only for some domains, even if I override the current domain it's also not picked up. Not sure if it has to do with not being #config_target-driven.

mably’s picture

Thanks a lot for testing, and for the detailed feedback!

#1 (sticky selection): agreed, the cross-form persistence was a footgun. Fixed in #3592980 - the selected domain now lives in a URL query argument instead of the tempstore, so it is scoped to the form you switched on and dropped as soon as you navigate away. That is the url-like behaviour you suggested. Switching still discards unsaved edits, and Save still targets the selected domain.

#2 (Appearance form): good catch, and your hunch was right - it is not #config_target-driven. The theme settings form exposes its configuration through getEditableConfigNames() and reads it in buildForm(), before the switcher's hook_form_alter() runs, so the switcher cannot retarget the values it displays; that is why the selector is intentionally not shown on it. This is now documented as a known limitation (see #3592980). Supporting such forms would require the global, pre-build approach that broke routing and was deliberately abandoned.

Thanks again!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.