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
siteentity,SitesSiteDefinitionHooksadds a definition inhook_site_info_alter(): the class of the site type's base plugin, the entity's settings,settings['id'](the machine name) andsettings['site_content_id'](the entity ID). A disabled site stays defined, withstatusFALSE. - The hub.
DefaultSite, plugin IDdefault(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()returnsNULL: the hostname decides. ReturnTRUEto claim a route,FALSEto refuse it with a 404.access()turnsapplies()into the site gate, refuses a disabled site, and invokeshook_site_access_alter()andhook_site_SITE_ID_access_alter().getConfigOverrides()collects the overrides of the setting plugins. Override it to add your own; the site's stored overrides andsettings.phpwin over it. See Config overrides.getCacheTags()carriessite:{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 thetitlesetting on save.settings: asite_settingsfield.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 implementsManagedSiteInterfaceand has no site entity. Sites content shipscontent_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'].