spatie/laravel-multitenancy
Unopinionated multitenancy for Laravel. Detect the current tenant per request and define what happens when switching tenants. Supports single or multiple databases, tenant-aware queued jobs, commands that run per tenant, and easy per-model connection setup.
composer require spatie/laravel-multitenancyphp artisan vendor:publish --provider="Spatie\Multitenancy\MultitenancyServiceProvider" --tag="multitenancy-config"tenant_model in config/multitenancy.php (typically App\Models\Tenant)Spatie\Multitenancy\Contracts\IsTenant contract on your Tenant model and add the id, domain, and database attributes (or equivalent)tenant_finder (e.g., DomainTenantFinder for domain-based isolation)switch_tenant_task in config/multitenancy.php — most commonly SwitchDatabaseConnectionTask for per-tenant database switchingStart with the official documentation — particularly the Basic Usage and Tasks sections — and watch the linked LaraCon talk and video for foundational context on multitenancy patterns.
Per-tenant database switching: Use SwitchDatabaseConnectionTask to dynamically bind a tenant-specific DB connection at runtime. Define connection patterns in config/database.php using tenant_{$tenantId} or dynamic host/schema resolution.
Route caching per tenant: When using tenant-specific routes, leverage SwitchRouteCacheTask and the tenant:artisan wrapper commands to generate and cache routes per tenant (e.g., php artisan tenant:artisan route:cache).
Tenant-aware queues & jobs: Use the CanBeUsedByTenant trait on jobs. Jobs dispatched during a tenant context will automatically execute in that tenant context using Spatie\Multitenancy\Jobs\TenantAwareJob.
Artisan commands across tenants: Run commands for all tenants using php artisan tenant:artisan db:seed, or for specific tenants with --tenants=1,2,3.
Caching isolation: Implement PrefixCacheTask or SwitchRedisPrefixTask to segment cache keys per tenant (e.g., tenant:1:key), avoiding cross-tenant data leakage.
Model connection delegation: Use Model::usingTenantConnection($tenant) or Model::onTenant($tenant) to manually set connections for non-request contexts (e.g., seeders, console jobs).
Testing tenants: Write tests using usesTenant($tenant) or actingAsTenant($tenant) helpers to simulate tenant context without mocking currentTenant.
Singleton tasks: Your SwitchTenantTask implementations are resolved as singletons — always store original values (e.g., config, DB name) on instance properties in makeCurrent() for restoration in forgetCurrent().
Configuration hot-swapping: Be cautious when caching config (config:cache) — SwitchTenantTask modifies config at runtime, but cached config may override dynamic changes. Disable config cache during development or ensure your tasks re-evaluate dynamically.
Route cache naming: When shared_routes_cache is true, all tenants must share the same routes — otherwise route mismatches will occur silently. Use tenant:artisan route:clear --tenant=X to clear per-tenant caches.
Non-web contexts: Manually set the current tenant with app()->forgetChild('currentTenant'); app()->singleton('currentTenant', fn() => $tenant); when invoking tenant logic outside HTTP requests (e.g., tinker, batch jobs).
Validation in tasks: Skip expensive DB lookups in makeCurrent() if the same tenant is already current (check app('currentTenant')).
Upgrade gotcha (v4+): Ensure all custom tasks and finders use IsTenant typehints (not Tenant). The old UsesTenantModel trait is deprecated.
Performance tip: Use php artisan optimize:clear + tenant:artisan commands to keep route/model caches fresh in multi-database setups.
Debug tip: Enable debug in multitenancy.php to log which tasks are executed and in what order — invaluable for troubleshooting silent failures.
How can I help you explore Laravel packages today?