Skip to content

Multi-tenancy

One Drupal instance can host several isolated workflow realms, called tenants. Each tenant runs its own processes: instances, tokens and variables partition per tenant, so a query, a listing or a purge in one tenant never reaches another's rows.

Every install starts with one tenant out of the box, the default tenant, and that is all a single-tenant site ever needs. Multi-tenancy stays invisible until you create a second tenant, so a single-tenant site behaves as if tenancy were absent.

How isolation works

Each partitioned row records the tenant it belongs to in a tenant field. The value is stamped at create time by the engine, from the active tenant, and defaults to the default tenant so a row is never tenantless. A process workflow is either shared across every tenant (the default) or scoped to a single tenant.

A delegation is likewise a single tenant's: someone covering for a person in one realm is a stranger in another, and every question the resolver answers names its tenant.

Managing tenants

The Tenants list at /admin/config/workflow/orchestra/tenants holds every realm the site has. Add tenant creates one from a label, the machine name that every row in the realm is stamped with, and a weight that orders the listings. The machine name is offered once and fixed afterwards: renaming it would leave the instances, tokens, variables and workflows it stamped behind under the old name.

Each row reaches Edit for the label and the weight, Retention and Reading for what the realm keeps and who may read its runs, and Delete. Deleting a tenant is not a tidy-up. It cancels whatever is still running in the realm, then removes every process instance scoped to it, with their tokens, variables and incidents, and the workflows scoped to it; the confirmation counts those rows before you agree to it. The default tenant cannot be deleted at all, because every request that resolves to no tenant falls back to it.

Removing a tenant with drush config:delete does none of that. It writes to the configuration directly, and nothing carries a raw config write back to the entity system, so the realm's rows are left behind with no tenant to belong to. Use the form, or a config import, which deletes through the entity system as the form does.

Resolving the active tenant

Which tenant a request acts in is decided by tenant resolvers: services tagged orchestra.tenant_resolver implementing TenantResolverInterface. Each resolver returns a tenant machine name or NULL to defer; the first non-null answer wins, and if none answer the default tenant is used. With no resolver installed every request resolves to the default tenant.

That fallback is what keeps a single-tenant site simple, and it is also what a site can back itself into: a second tenant, added on the Tenants list above or arriving with a config import, cannot be reached at all while no resolver is installed. No request ever acts in it, so nothing can be created there, and the listings and access checks, scoped to the tenant in effect, hide anything that is already there. The site looks multi-tenant and is not. Orchestra says so on the status report, with a warning naming how many tenants are out of reach, so install a connector (orchestra_domain below, or orchestra_server_api for remote consumers), tag a resolver of your own, or delete the tenants you are not using.

To resolve tenants your own way, implement the interface and tag the service:

services:
  my_module.tenant_resolver:
    class: Drupal\my_module\MyTenantResolver
    tags:
      - { name: orchestra.tenant_resolver }

Mapping a domain to a tenant

The optional orchestra_domain submodule maps the active domain (from the Domain module) to a tenant, so each domain runs its own isolated set of processes on one Drupal instance. Bind a domain to a tenant on the domain add/edit form (the binding is a third-party setting on the domain record); a request served on that domain then acts in the bound tenant, falling back to the default when a domain has no binding. It is just another tenant resolver, so the kernel itself stays domain-agnostic.

Tenant-scoped operators

The process instances UI (provided by orchestra_ui) lives under Structure at /admin/structure/orchestra/instances, with a menu link and a canonical link template per instance. Two permissions gate it:

  • Access Orchestra process instances (access orchestra instances): a tenant-scoped operator who sees and drives only their own tenant's instances. The list and the per-instance page resolve the active tenant the usual way and show only that tenant's rows.
  • Administer Orchestra (administer orchestra): the cross-tenant super-permission. A holder reaches every tenant's instances and is not narrowed to the active one.

The list's filter follows the same split: a tenant-scoped operator narrows by workflow (their tenant's plus the shared ones) and status, while the tenant filter itself is offered only to an administrator, whose list spans every tenant.

So you can give an operator day-to-day control of one realm's processes without handing them the keys to every other tenant on the site.

Who reads a realm's runs

Separately from those operator permissions, a tenant decides how much of a run's own audience may read it: its Reading operation on the Tenants list sets the read access for every workflow in the realm, overriding the site setting, and any workflow of the realm may override the tenant in turn. That is about reading a run without administrative rights, not about driving it, and it never crosses tenants: a run is readable only from inside its own tenant, whatever the scope says.