glimmer / tenancy
Opinionated and extended Spatie Multitenancy
Requires
- php: ^8.3
- illuminate/console: ^11.0|^12.0
- illuminate/container: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/queue: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- league/flysystem-path-prefixing: ^3.28
- spatie/laravel-multitenancy: ^4.0.1
Requires (Dev)
- laravel/pint: ^1.0
- laravel/scout: ^10.11
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.7
- pestphp/pest-plugin-laravel: ^3.0
- spatie/laravel-permission: ^6.10
This package is auto-updated.
Last update: 2025-04-18 04:37:34 UTC
README
An opinionated Spatie Multitenancy extension package for Laravel
This package extends spatie/laravel-multitenancy with additional opinionated features. For example, it defaults to multi-database tenancy but takes a different approach than Spatie's implementation.
Since this package builds upon Spatie’s, it retains most original concepts and functionality. It’s recommended to review Spatie's official documentation before using this package.
What makes it opinionated?
- It uses a multi-database approach by default, creating a new connection for each tenant.
- Different landlord/tenant migrations structure.
- Auto-routing files with predefined middlewares.
- Additional tasks and tenant finders that expect the model to have certain columns.
- Allows queue jobs to run in both tenant and landlord contexts.
Installation
composer require glimmer/tenancy
All files (routes, config, migrations) can be published at once by running:
php artisan vendor:publish --provider="Glimmer\Tenancy\TenancyServiceProvider"
Features
Additional Tenant Switching Tasks:
PrefixFilesystemTask
– Prefixes filesystem disk paths with the tenant key (ID) usingleague/flysystem-path-prefixing
.PrefixScoutTask
– Prefixes Laravel Scout index names inconfig/scout.php
with the tenant key.PrefixSpatiePermissionTask
– Adds a tenant-specific prefix to Spatie Permission cache keys.
Enhanced Tenant Switching Tasks:
SwitchDatabaseConnectionTask
(Replaces Spatie’sSwitchTenantDatabaseTask
) – Multi-database approach that creates a connection for each tenant using the landlord’s default connection (.env
setting) and dynamically switches between them.- A different connection can be specified per tenant by setting the
database_connection
column in the tenant model. - Individual connection configurations can be overridden using the
connection_config
column. - The database name can be changed within the
connection_config
array by defining'database' => 'name'
. - This approach improves performance by avoiding frequent reconnections but slightly increases memory usage, especially in Laravel Octane environments.
- A different connection can be specified per tenant by setting the
Tenant Finders
DomainTenantFinder
– Finds the tenant by matching the request domain with the entries in the tenant model’shosts
array.SubdomainTenantFinder
– Identifies the tenant using the subdomain from the request, based on thehosts
array in the model.PathTenantFinder
– Determines the tenant by extracting its ID from the request path.DomainAndSubdomainTenantFinder
– Matches tenants using either the request’s domain or subdomain.
The
hosts
array in the tenant model can contain a list of either domains or subdomains.
Queue Enhancements:
MakeQueueMaybeTenantAwareAction
– A modifiedMakeQueueTenantAwareAction
allowing jobs to run in both tenant and landlord contexts if the job implementsMaybeTenantAware
.MaybeTenantAware
– An interface that allows jobs to be dispatched with/without tenant context depending on the job implementation.tenant events
- Allows executing certain jobs when a tenant event is fired. (Job must extendTenantEventQueue
) (Jobs must be defined onmultitenancy.php
config file with its respective event)
Tenant events:
For these events to work, the HasTenantEvents
trait must be used in the tenant model (which is already included in
Glimmer's Tenant
model).
CreateDatabase
– Triggered when a tenant is created.MigrateDatabase
– Fired after tenant creation to apply migrations (expects migrations to be at:database/migrations/tenant
).SeedDatabase
– Runs database seeders upon tenant creation.- Defaults to
DatabaseSeeder.php
but will usetenant/DatabaseSeeder.php
if it exists.
- Defaults to
Command Enhancements:
IsTenantAware
|TenantAware
– A modification toSpatie/Commands/Concerns/TenantAware
trait enabling SQLite usage without lock issues.MaybeTenantAware
– A trait for commands that can be executed with/without tenant context.tenants:artisan
– A modification that uses Glimmer'sIsTenantAware
trait to allow SQLite databases to be used without blocking commands.
Middlewares:
EnsureNoTenantSession
- Prevents tenant session to be used on landlord routes.ForbidsTenant
- Prevents tenant to access landlord routes.
Extended functionality:
- Tenant Model –
Glimmer/Tenancy/Models/Tenant
extendsSpatie\Multitenancy\Models\Tenant
with additional functionality as running events and determining the database name as expected. - Separated Route Files – Landlord (landlord.php) and tenant (tenant.php) routes are managed separately, while
web.php
remains shared. IsSharedModel
trait – Enables models to be shared between tenants and the landlord, ensuring synchronization across instances.- Automatic Route Registration – Routes are automatically registered and assigned appropriate middlewares to prevent
unauthorized access (this can be disabled in
multitenancy.php
config).- Tenant routes includes
NeedsTenant
andEnsureValidTenantSession
middleware by default. - Landlord routes includes
ForbidsTenant
EnsureNoTenantSession
middleware by default. - Tenant routes names are prefixed with
tenant.
and landlord routes withlandlord.
, if not using auto-registration, you can override this by callingname()
method. - If auto-registration is disabled, use
TenancyRoutes::landlord()
and/orTenancyRoutes::tenant()
to register them by hand and group the routes you need.
TenancyRoutes::landlord()->group(function () { Route::get('/dashboard', function () { return 'Landlord dashboard'; })->name('dashboard'); // Named route: landlord.dashboard }); TenancyRoutes::tenant()->group(function () { Route::get('/home', function () { return 'Tenant home'; })->name('home'); // Named route: tenant.home });
- Tenant routes includes
Notes:
-
To avoid showing
500
error onNoCurrentTenant
orTenantIsForbidden
exception and instead redirect to a route, you can add the following to yourbootstrap/app.php
in to the exception handler:use Glimmer\Tenancy\Facades\LandlordTenantException; return Application::configure(basePath: dirname(__DIR__)) .... ->withExceptions(function (Exceptions $exceptions) { $exceptions->render(LandlordTenantException::redirect('your-route')); })->create();
-
As disclosed before, major structural changes are migration paths for landlord and tenant, as landlord migrations are at
database/migrations
root and tenant migrations are atdatabase/migrations/tenant
, and so for migrating the landlord you need to run:php artisan migrate
For migrating the tenant, you need to run:
php artisan tenants:artisan "migrate --path=database/migrations/tenant"
Or instead, use Glimmer's tenant events, which will automatically migrate the tenant after creation.
-
The
SwitchRouteCacheTask
should be considered hardly if needed. In many cases, middleware/controller checks can enforce route access restrictions without needing this task.