Skip to content

Site select element

Add a site picker to your own forms, and limit an entity reference to the content of one site.

Entity forms of site content need no code: Sites adds their Sites section through Form decorator (SitesSelectionContentEntityFormDecorator, SitesSelectionEntityFormDecorator). The elements on this page are for forms you build yourself.

site_select

Drupal\sites\Element\SiteSelect is core's select with sites as options. Without #options, it offers the sites the current user may work on, from UserSiteAccessInterface::getSiteOptions() (service sites.user_access), sorted by label.

Property Default Meaning
#operation manage What the user may do with content on the site: add, remove, or manage (either).
#entity NULL The entity the operation is about. Sites that do not accept its bundle are left out.
#context [] entity_type_id and bundle, when there is no entity yet.
#excluded_sites [] Site IDs to leave out.

The other properties are core's select ones, like #multiple, #empty_option and #required. The value is a site ID, or an array of site IDs with #multiple.

$form['site'] = [
  '#type' => 'site_select',
  '#title' => $this->t('Site'),
  '#operation' => 'add',
  '#context' => ['entity_type_id' => 'node', 'bundle' => 'article'],
  '#required' => TRUE,
];

The options authorize nothing: a client can post any site ID. Check the submitted site before you write, as Drupal\sites\Form\AddContentToSite does:

$site = $this->siteManager->getSite((string) $form_state->getValue('site'));
if (!$site instanceof SiteWithContentInterface
  || !$this->contentAssignment->addAccess($entity, $site, $this->currentUser())->isAllowed()) {
  throw new AccessDeniedHttpException();
}
$this->membership->add($site->id(), $entity);

$this->contentAssignment is the sites.content_assignment service, $this->membership is sites.membership.

sites_selection

Drupal\sites\Element\SitesSelection is the composite behind the Sites section: a canonical site and further sites. Its value lists every site, the canonical one included:

$value = [
  'sites' => ['acme', 'globex'],
  'canonical_site' => 'acme',
];
Property Default Meaning
#mode inline inline renders the canonical site, then the further sites. section renders the collapsible section with the dialog button of entity forms; only the Sites form decorators read its value.
#entity NULL The entity the options are computed for. Without one, #entity_type_id, #bundle and #entity_id name it.
#options computed Site ID => label. Computed with getSiteOptions() when left out.
#canonical_locked NULL NULL locks a stored canonical site, a site ID fixes it, FALSE keeps it editable.
#canonical_required FALSE Without it, an empty canonical site becomes the first selected site.
#canonical_title, #canonical_description, #select_title, #select_description Labels of the two parts.

A locked canonical site changes only through the Change site operation of the content, because the canonical site decides whose permissions gate the entity form. Sites the user cannot offer stay in the value unchanged. Content of a Single site type (Content on sites) gets one select and no separate canonical site.

Apply the value with SiteContentAssignmentInterface::reconcile(): it adds and removes the memberships the account may change, and sets the canonical site only for content that has none.

$form['sites_selection'] = [
  '#type' => 'sites_selection',
  '#entity' => $entity,
  '#default_value' => SitesSelection::membershipSeed($entity),
];

// In the submit handler, after the entity is saved:
$this->contentAssignment->reconcile(
  $entity,
  SitesSelection::normalizeSelection((array) $form_state->getValue('sites_selection')),
  $this->currentUser(),
  $is_new,
);

Drupal\sites_webform\EntitySettings\WebformEntitySettingsSitesForm is a complete example.

A part of an entity form that depends on the chosen sites, like a per-site alias widget, calls SitesSelection::registerDependent($subtree, $key) and builds from SitesSelection::currentSelection($form_state, $entity). The Sites dialog then renders it again when the user applies a new selection.

Tagify widget

With Sites Tagify enabled, every site_select renders as site_select_tagify, and the further sites of sites_selection as a select_tagify. Your form keeps asking for site_select, and the value keeps the shape of a select. Sites Tagify adds:

  • #add_all and #remove_all on site_select: TRUE or a translated label for links that select or clear all sites. A plain select ignores them, so set them whether or not the module is on.
  • #tagify_match_limit on sites_selection: how many matches the widget suggests while typing. Without it, the Fallback match limit on /admin/sites/configuration/tagify applies; 0 shows all.

Entity reference selection per site

Two selection handlers limit what an entity reference offers:

Handler Settings Offers
site (SiteContentSelection) site_id, operation The content of the site. With operation add, the content the site does not hold yet; with any, all content.
site_reference (SiteReferenceSelection) site_id (default: the active site), sites_filter_mode The content of the site, when the mode (enabled, default, disabled) filters.

Both handlers build their query in the target type's own core handler, so its rules stay (unpublished content, blocked users, reusable blocks only). On top, SitesReferenceHooks applies a read scope to every reference selection in an HTTP request: items of the sites the account reads (SiteEntityAccessInterface::participatedSites(), plus the current site on a site host), memberless items only with a real global view unassigned content, everything with a real global administer sites. Drush and other CLI runs are not scoped. The site handler offers only the bundles the site accepts and never the anonymous user; for users it also needs "Add existing users to a site" on the target site. Use it in your own forms:

$form['page'] = [
  '#type' => 'entity_autocomplete',
  '#target_type' => 'node',
  '#selection_handler' => 'site',
  '#selection_settings' => ['site_id' => $site->id(), 'operation' => 'add'],
];

A field gets site_reference from the Filter by site option on its settings form, described in Blocks and views. The mode default follows the site's filter_references setting, and so does a field with a core default:* handler, unless it references users. On a site's hostname, a field that filters also hides references to other sites' content when it is displayed.