atwx / silverstripe-tenancy
Multi-tenancy for Silverstripe apps: tenant resolution by host, automatic query scoping and tenant membership.
Package info
github.com/atwx/silverstripe-tenancy
Type:silverstripe-vendormodule
pkg:composer/atwx/silverstripe-tenancy
Requires
- php: ^8.3
- silverstripe/framework: ^6
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-01 19:54:17 UTC
README
Multi-tenancy for Silverstripe 6 applications (e.g. frontdesk-kit apps):
Tenantwith severalTenantDomains andTenantMemberships (member + role)TenantResolverwith pluggable strategies (host, back-office session, default tenant)TenantScopedExtension: automatic query scoping viaaugmentSQL, fail-closed (no current tenant → no records), tenant assigned on write, records can't move between tenantsTenantScopeto opt out explicitly for platform code, tasks and jobs
Installation
composer require atwx/silverstripe-tenancy
Usage
Scope a model (apply to the base class of a hierarchy):
App\Model\Form: extensions: - Atwx\Tenancy\Extension\TenantScopedExtension
Form::get(); // only records of the current tenant TenantResolver::singleton()->current(); // ?Tenant TenantResolver::singleton()->withTenant($t, fn () => Form::get()->count()); TenantScope::withoutScope(fn () => Form::get()->count()); // all tenants TenantScope::unscoped(Form::get()); // single list without filter
Templates: $CurrentTenant.
Resolution
Strategies are configured on Atwx\Tenancy\TenantResolver.strategies and asked in order:
| Strategy | Behaviour |
|---|---|
HostStrategy |
exact match of the request host against TenantDomain.Domain (active tenants only) |
SessionStrategy |
tenant chosen in a back-office switcher (SessionStrategy::remember()), only on hosts that belong to no tenant and only if the member may access the tenant |
DefaultTenantStrategy |
tenant flagged IsDefault (not enabled by default) |
Custom strategies implement ResolverStrategy.
Access
Tenant::canAccess($member) is true for members of the tenant and for members with
ADMIN or TENANCY_ACCESS_ALL. Member::AccessibleTenants() lists the tenants a
member may switch to. Roles are configurable via TenantMembership.roles.
Roles, members, default tenant
TenantAccess: role of a member in the current tenant (platformfor staff),canView(),canEdit()(edit roles configurable:TenantAccess.edit_roles),isTenantMember(),memberOptions().MembershipService: add members by email (existing login reused), change roles, remove; a tenant always keeps one member withowner_role.DefaultTenant+Extension\DefaultTenantExtension(opt-in onTenant): creates the configured default tenant ondb:buildwith the default admin as owner.sake tasks:tenant-access --tenant=<slug> --email=… --role=… --domain=…
Caveats
- Versioned models: the filter is added to the base query; test scoping on
_Livetables if you scope versioned models. - Raw SQL (
DB::query,SQLSelect) is not scoped.
Tests
From a project root with this module installed (the DB user needs CREATE DATABASE):
vendor/bin/phpunit vendor/atwx/silverstripe-tenancy/tests