Skip to content

Site plugins and site types

Declare a site in code, and find the plugin, the site entity and the site type behind every site.

Where sites come from

Every site is a plugin of plugin.manager.site (Drupal\sites\Plugin\SitePluginManagerInterface) and implements Drupal\sites\SiteInterface. The plugin ID is the site ID: a machine name of lowercase letters, digits and underscores.

flowchart LR
  type["site_type (configuration)<br/>base_plugin, default_settings"] --> entity["site entity (content)<br/>site_id, settings"]
  entity --> alter["hook_site_info_alter()<br/>SitesSiteDefinitionHooks"]
  base["#[Site] content_site<br/>ContentSite"] -. class .-> alter
  alter --> defs["plugin.manager.site<br/>definitions"]
  code["#[Site] plugin<br/>in your module"] --> defs
  defs --> site["SiteInterface<br/>getSite('acme')"]
  • Sites in code. A class in src/Plugin/Site/ with the #[Site] attribute is a site.
  • Sites as content. For each site entity, SitesSiteDefinitionHooks adds a definition in hook_site_info_alter(): the class of the site type's base plugin, the entity's settings, settings['id'] (the machine name) and settings['site_content_id'] (the entity ID). A disabled site stays defined, with status FALSE.
  • The hub. DefaultSite, plugin ID default (SiteInterface::HUB_ID), is hidden from the definitions and answers every request no site claims.

getSite($site_id) loads one site, NULL for an unknown ID. getSites() instantiates every site, for lists. getSubSiteIds($main_site_id) reads sub-sites from the definitions without instantiating them.

Declare a site in code

namespace Drupal\my_module\Plugin\Site;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\sites\Attribute\Site;
use Drupal\sites\Plugin\Site\SitePluginBase;

#[Site(
  id: 'brand_portal',
  title: new TranslatableMarkup('Brand portal'),
  settings: [
    'hostname' => 'brand.ddev.site',
    'theme' => 'olivero',
    'weight' => 10,
  ],
  environment_settings: [
    'production' => ['hostname' => 'brand.example.com'],
  ],
)]
final class BrandPortal extends SitePluginBase {}

settings takes the keys of any setting plugin: the hostname and aliases, the weight that orders the site in every list, the theme. A key with a config target becomes a configuration override of the site, here the default theme. environment_settings holds values per environment machine name. status: FALSE disables the site. Rebuild caches after changing a plugin.

SitePluginBase answers the rest:

  • applies() returns NULL: the hostname decides. Return TRUE to claim a route, FALSE to refuse it with a 404.
  • access() turns applies() into the site gate, refuses a disabled site, and invokes hook_site_access_alter() and hook_site_SITE_ID_access_alter().
  • getConfigOverrides() collects the overrides of the setting plugins. Override it to add your own; the site's stored overrides and settings.php win over it. See Config overrides.
  • getCacheTags() carries site:{id}.

A code site has no site entity. Its values come from the attribute and from settings.php.

A code site that holds content

Extend SiteWithContentBase (QueryableSiteInterface, SiteWithContentInterface) and list the entity types the site may hold in the content_entity_types setting. Store membership in a service tagged sites.membership_storage that implements Drupal\sites\Membership\MembershipStorageInterface. The plugin reads it through sites.membership in applies(), scopes queries in queryAlter(), and returns a tag the storage invalidates from appliesCacheTags(). The test module sites/tests/modules/sites_example does this with the State API (ContentInStateSite, StateMembershipStorage). See Site-aware entity types.

The site entity

The site content entity (Drupal\sites\SiteEntityInterface) is revisionable and translatable; its bundle is the site type. Its base fields:

  • site_id: the machine name, the plugin ID (getSiteId()).
  • label: derived from the title setting on save.
  • settings: a site_settings field. getSettings() returns the values for the current environment keyed by setting key, getSettings('default') the values every environment inherits, getEnvironmentSettings() the values per environment.
  • status: enabled or disabled.

Settings with a config target are stored as per-site configuration override rows, not in the field. The edit-form link is the Identity section. ManagedSiteInterface::getSiteEntity() loads the entity of a site; getSite($entity->getSiteId()) the site of an entity.

Create, enable and delete sites through sites.lifecycle: it checks the ID, the hostname and the place, and a new site starts disabled.

$basics = ['id' => 'acme', 'label' => 'Acme', 'hostname' => 'acme.example.com'];
// The site type 'site'; errors are keyed by the value they concern.
if ($this->lifecycle->createErrors($basics, 'site') === []) {
  $site = $this->lifecycle->create($basics, 'site');
  $this->lifecycle->setEnabled($site, TRUE);
}

Site types

A site type is a site_type configuration entity (Drupal\sites\SiteTypeInterface), the bundle of the site entity. It exports with your configuration; sites are content on each environment. See Site types for what a type decides.

  • getBasePlugin(): the Site plugin whose class the type's sites run as. The Base site plugin select offers every plugin that implements ManagedSiteInterface and has no site entity. Sites content ships content_site (ContentSite); a type without a base plugin uses it.
  • getDefaultSettings(): the settings matrix, keyed by setting plugin ID: which keys the type offers, their defaults, and which may differ per environment or language.

Alter site definitions

hook_site_info_alter(array &$sites) runs after discovery, without the hidden hub. A module that keeps sites in its own storage adds one definition per site there (id, class, provider, title, status, settings, environment_settings), as SitesSiteDefinitionHooks::siteInfoAlter() does. When the storage cannot be read, call markDefinitionsIncomplete() on the manager so the definitions are not cached. Do not ask the manager for sites there: its definitions are being built. A site's order is its weight setting (SiteInterface::weight()), so change settings['weight'].