Skip to content

Domain Config UI

The Domain Config UI module provides a lightweight way to enable per-domain configuration overrides directly from existing configuration forms. When viewing a supported configuration form within a domain context, privileged users will see an inline toggle to enable (or remove) a domain-specific override for that configuration object.

How it works

  • When a user with the appropriate permissions visits an admin configuration form on a domain host (e.g., https://one.example.com), the module inspects the form to identify the underlying configuration object(s).
  • If the configuration is allowed to be overridden per domain, an inline action link appears at the top of the form:
    • Enable domain configuration
    • Remove domain configuration (after it has been enabled)
  • Once enabled, changes submitted on that form are stored as per-domain overrides, without altering the global configuration.

Disallowed configurations

You can explicitly prevent certain configuration objects from being overridden per domain. When a configuration name is disallowed, the inline toggle link is hidden on the corresponding form.

Where to configure

  • UI: Administration > Configuration > Domain > Domain Config UI (/admin/config/domain/config-ui)
  • Config key: domain_config_ui.settings: disallowed_configurations

Example

To forbid domain-level overrides for the Site Information form (system.site) and the Theme settings (system.theme), set the following in your configuration (YAML), or via the settings form:

domain_config_ui.settings:
  disallowed_configurations:
    - system.site
    - system.theme

With the above, the "Enable domain configuration" link will no longer appear on:

  • Site Information: /admin/config/system/site-information (system.site)
  • Appearance and theme settings pages that map to system.theme

Notes

  • The module also prevents overriding its own settings by default.
  • The check is enforced server-side via the configuration factory, not just hidden in the UI.

Programmatic control

Two alter hooks allow advanced control over where the toggle is shown and which configuration objects are eligible for per-domain overrides.

Disallowed configurations alter

Prevent domain overrides for specific configuration names globally:

/**
 * Implements hook_domain_config_ui_disallowed_configurations_alter().
 */
function mymodule_domain_config_ui_disallowed_configurations_alter(array &$disallowed): void {
  // Disallow domain overrides for image toolkit settings site-wide.
  $disallowed[] = 'system.image';
}

Disallowed routes alter

Hide the toggle entirely on specific routes (even if the underlying config would normally be allowed):

/**
 * Implements hook_domain_config_ui_disallowed_routes_alter().
 */
function mymodule_domain_config_ui_disallowed_routes_alter(array &$routes): void {
  // Do not show the toggle on the account settings page.
  $routes[] = 'entity.user.admin_form';
}

Config editing domain (developer API)

By default the configuration forms read from and write to the negotiated domain — the one resolved from the current request (hostname, path prefix, and so on). That same negotiated domain also drives routing, language negotiation and path-prefix handling, so it must reflect the real request and cannot simply be swapped to edit another domain.

To let code edit another domain's overrides without changing the negotiated domain, the module provides the DomainConfigEditContext service (Drupal\domain_config_ui\DomainConfigEditContext). It holds an optional config-editing domain, scoped to a set of configuration names. The configuration factory and DomainConfigUIManager consult it in preference to the negotiated domain, falling back to negotiation when nothing is set.

/** @var \Drupal\domain_config_ui\DomainConfigEditContext $edit_context */
$edit_context = \Drupal::service(DomainConfigEditContext::class);

// Edit one_example_com's system.site overrides only; everything else (and
// routing, language and path prefixes) keeps using the negotiated domain.
$edit_context->setEditingDomain('one_example_com', ['system.site']);

Key properties:

  • Name-scoped: only the configuration names you pass are retargeted; any other configuration still resolves to the negotiated domain.
  • Negotiation is untouched: routing, language and path-prefix handling always use the real request domain, so the current URL keeps working — including on sites using language path prefixes or path-prefix domain mode.
  • Backward compatible: when no editing domain is set, every resolution falls back to the negotiated domain, so behavior is identical to not using the service at all.
  • Access still applies: writes remain gated by the per-domain registration and the user's domain permissions, evaluated against the editing domain — a user can only write a domain they may administer.

This API powers the Domain Configuration Switcher submodule in the domain_extras project, which adds an in-form domain selector.

Editing the base (default) configuration

Pass DomainConfigEditContextInterface::BASE as the domain id to edit the base configuration — the default values every domain inherits unless it has its own per-domain override — instead of a specific domain's override. getDomainId() resolves the sentinel to NULL, so the configuration factory writes the base configuration rather than a domain collection, even when the negotiated domain has a registered override.

use Drupal\domain_config_ui\DomainConfigEditContextInterface;

// Edit the base system.site configuration, regardless of the negotiated
// domain. Saving writes the default that non-overriding domains inherit.
$edit_context->setEditingDomain(
  DomainConfigEditContextInterface::BASE,
  ['system.site'],
);

Because getDomainId() resolves to NULL in base mode, domain_config_ui's own hook_form_alter() treats the request as having no editing domain and does not run: it adds neither the enable/disable toggler nor the permission validators. The consumer therefore owns the in-form UI and must gate base editing behind the set default domain configuration permission itself.

Permissions overview

Common permissions used by this module include:

  • use domain config ui — see and use the inline toggle on allowed forms
  • administer domain config ui — manage settings
  • set default domain configuration — manage default vs. domain-specific values

Ensure users operate within a domain context (i.e., visiting the site on a domain's host) for the toggle to be available.

The optional Domain Config Language UI submodule contributes its own translate domain configuration permission for managing per-language overrides on top of per-domain ones.

Testing references

The repository includes functional and JavaScript tests that illustrate expected behavior:

  • DomainConfigUISettingsTest — enabling/removing overrides from common forms
  • DomainConfigUIDisallowedConfigurationsTest — verifies that adding system.site to disallowed_configurations hides the toggle on Site Information
  • DomainConfigUIOptionsTest and DomainConfigUIPermissionsTest — permissions and options coverage

These tests can serve as examples when integrating the feature in custom modules.

Per-domain configuration can affect rendering that depends on language-keyed plugin definitions cached by Drupal core (local tasks, local actions, contextual links). Without a cache key that also varies by domain, the first domain to warm the cache wins, and subsequent domains render stale tabs and actions. The companion submodule domain_menu_extras (in the domain_extras project) replaces the affected plugin managers with domain-aware variants. Enable it if you're affected.