Skip to content

Setting plugins

Add per-site settings with a #[SiteSetting] plugin, map them to configuration, and read their values in code.

What a setting plugin is

A setting plugin declares a group of setting keys. It gets a section on each site whose type offers one of its keys, at /admin/sites/{site}/settings/{plugin_id} (permission Manage site settings), a row per key in every site type's matrix, and an entry in the settings reference. How the layers of a value combine is on Setting layers.

Keys share one namespace across all plugins; a plugin that computes its keys prefixes them, as Roles does with per_site_role:{rid}. plugin.manager.site_setting discovers the plugins in Plugin/SiteSetting; hook_site_setting_info_alter() alters them.

Declare a plugin

namespace Drupal\my_module\Plugin\SiteSetting;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\sites\Attribute\SiteSetting;
use Drupal\sites\Plugin\SiteSetting\SiteSettingPluginBase;
use Drupal\sites\SiteSetting\SiteSettingValuesInterface;

#[SiteSetting(
  id: 'my_module_support',
  label: new TranslatableMarkup('Support'),
  description: new TranslatableMarkup('Where visitors of the site get help.'),
  weight: 60,
  settings: [
    'support_phone' => [
      'label' => new TranslatableMarkup('Support phone'),
      'default' => '',
      'translatable' => TRUE,
    ],
    'maintenance_message' => [
      'label' => new TranslatableMarkup('Maintenance message'),
      'default' => '',
      'translatable' => TRUE,
      'config_target' => 'system.maintenance:message',
    ],
  ],
)]
final class Support extends SiteSettingPluginBase implements SiteSettingValuesInterface {

  public function buildSettingsForm(array $form, FormStateInterface $form_state): array {
    $form['support_phone'] = [
      '#type' => 'tel',
      '#title' => $this->t('Support phone'),
      '#default_value' => $this->settings['support_phone'],
    ];
    $form['maintenance_message'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Maintenance message'),
      '#default_value' => $this->settings['maintenance_message'],
    ];
    return $form;
  }

  public function validateValues(array $values): array {
    return $this->typeErrors($values);
  }

  public function configOverrides(): array {
    // An empty message keeps the platform's message.
    $message = $this->resolvedSetting('maintenance_message');
    return is_string($message) && trim($message) !== '' ? parent::configOverrides() : [];
  }

}

The manager reads the key definitions in settings without instantiating the plugin. Keys that depend on runtime state (bundles, roles, menus) come from defineSettings() instead; sites call it while they resolve their settings, so it must not load sites. section_of puts the form on another plugin's section, after the key section_after names, as Sites path prefix does on the identity section.

Definition keys

Key Default Effect
label Names the key in the site type's matrix and in token lists.
default The value when neither the site nor its type sets one. typeErrors() checks values against its type.
translatable FALSE The value may differ per language.
environment TRUE The value may differ per environment.
locked FALSE Every site type offers the key, and each site stores its own value.
config TRUE FALSE makes a structural key: outside the matrix, edited on the site's own layer only. A plugin of structural keys only has no section.
config_target The configuration value the key is stored as, a per-site override. See below.
sensitive FALSE Redacted in output and tokens; sites.site_settings refuses to write it.
file FALSE The value is a file URI; the site is registered as a user of the file.

The site type decides per key whether the environment and language layers exist; these flags decide whether it may.

The section form

buildSettingsForm() builds one element per key, named after the key, optionally inside details, fieldset or container groups, with values from $this->settings. The same form edits the type's defaults and every layer of a site: the section drops the elements of keys the layer does not offer and stores only values that differ from the layer below. settingsValueCallback() converts a submitted value into its stored shape.

SiteSettingValuesInterface::validateValues() receives values in their stored shape and returns error messages keyed by setting key. The base validateSettingsForm() shows them on the form; sites.site_settings applies them to every write from an agent tool or your code, and refuses plugins without the interface.

Config targets

config_target takes config.name:property.path or, in defineSettings(), a Drupal\Core\Form\ConfigTarget with fromConfig and toConfig callables. A site entity stores the value as a per-site configuration override row per language and environment, not as a setting; a code site derives it in configOverrides(). Configuration on the site's host returns the value.

Every site carries a key's default, so a default becomes an override on every site. To keep the platform's value, drop empty values in configOverrides(), as the example, SitesRobotstxt and SystemSite do. SitesAnalytics overrides it to compute overrides from several keys. Configuration read while the overrides are built comes without site overrides. See Config overrides.

Read settings

$phone = $this->currentSite->getSetting('support_phone');
$acme_phone = $this->siteManager->getSite('acme')?->getSetting('support_phone');

getSetting() returns the resolved value for the current language and environment, with inheritance and settings.php applied. sites.site_settings->read($plugin_id, SettingLayer::base($site_id)) returns one layer's values and the keys it stores itself (own); writeErrors() and write() change it. Show resolved settings only through plugin.manager.site_setting->redact($site, $settings).

settings.php

A server pins a value in settings.php, keyed by site ID, then setting key:

$settings['acme'] = [
  'support_phone' => '+49 30 1234567',
  'maintenance_message' => 'Acme is down for maintenance.',
];
  • In getSetting(), the value wins over every layer: SiteSettingsEvent::BUILD applies it at priority 0, after main-site inheritance at priority 120.
  • For a key with a config target, it wins in configuration over the site's stored override.
  • Core's $config in settings.php wins over every site's overrides.

Keep hostnames out of settings.php; see Setting layers.