Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Multitenancy Laravel Package

spatie/laravel-multitenancy

Unopinionated multitenancy for Laravel. Detect the current tenant per request and run configurable tasks when switching tenants. Supports single or multiple databases, tenant-aware queued jobs, per-tenant Artisan commands, and easy model connection handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation
    composer require spatie/laravel-multitenancy
    php artisan vendor:publish --provider="Spatie\Multitenancy\MultitenancyServiceProvider" --tag="multitenancy-config"
    
  2. Configure Tenant Model Update config/multitenancy.php with your tenant model (e.g., App\Models\Tenant). Ensure your model implements Spatie\Multitenancy\Contracts\IsTenant:
    use Spatie\Multitenancy\Contracts\IsTenant;
    
    class Tenant implements IsTenant
    {
        use \Spatie\Multitenancy\Models\Concerns\HasTenants;
        // ...
    }
    
  3. Define Tenant Finder Use the default DomainTenantFinder (for subdomains) or create a custom finder:
    'tenant_finder' => \Spatie\Multitenancy\TenantFinder\DomainTenantFinder::class,
    
  4. Migrate & Seed Run migrations for your tenant model and seed initial tenants (e.g., domains, databases).

First Use Case: Domain-Based Tenancy

  • Access http://tenant1.example.com to auto-resolve the tenant via DomainTenantFinder.
  • Verify the current tenant:
    $tenant = Tenant::current(); // Returns Tenant model or null
    

Implementation Patterns

Core Workflow: Request Handling

  1. Tenant Resolution The package hooks into Laravel’s middleware (HandleTenancy) to resolve the tenant at the start of each request.

    // Middleware auto-runs `findForRequest()` on your tenant finder.
    
  2. Task Execution Configure tasks (e.g., database switching, facade clearing) in switch_tenant_tasks:

    'switch_tenant_tasks' => [
        \Spatie\Multitenancy\Tasks\SwitchDatabaseTask::class,
        \App\Tenancy\SwitchTasks\ClearFacadeInstancesTask::class,
    ],
    

    Tasks run after tenant resolution but before the request is processed.

  3. Database Context Use SwitchDatabaseTask to dynamically set the tenant’s database connection:

    class SwitchDatabaseTask implements SwitchTenantTask
    {
        public function makeCurrent(IsTenant $tenant): void
        {
            config(['database.connections.tenant' => $tenant->database_connection]);
        }
    }
    

Common Patterns

1. Tenant-Aware Jobs

  • Global Tenant Awareness (all jobs):
    'queues_are_tenant_aware_by_default' => true,
    
  • Opt-In/Out:
    // Opt-in: Implement `TenantAware` interface
    class SendWelcomeEmail implements ShouldQueue, TenantAware { ... }
    
    // Opt-out: Implement `NotTenantAware` or list in config
    'not_tenant_aware_jobs' => [\App\Jobs\SystemJob::class],
    
  • Closure Jobs:
    Tenant::current()->execute(function () {
        // Tenant context preserved.
    });
    

2. Artisan Commands for Tenants

  • Run commands per tenant using Tenant::forAll():
    Tenant::forAll(function (Tenant $tenant) {
        Artisan::call('db:seed', ['--tenant' => $tenant->id]);
    });
    
  • Tenant-Specific Commands:
    Tenant::current()->runCommand('migrate');
    

3. Tenant-Specific Facades

  • Clear facade instances on tenant switch (see Gotchas).
  • Use tenant() helper for tenant-specific facades:
    $tenant->tenant()->cache->remember(...);
    

4. Middleware for Tenant Logic

  • Extend HandleTenancy or create custom middleware:
    class TenantMiddleware extends HandleTenancy
    {
        protected function determineCurrentTenant(Request $request)
        {
            return Tenant::where('api_key', $request->header('X-Tenant-Key'))->first();
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Facade Singleton Behavior

    • Issue: Facades (e.g., Cache, Mail) retain state across tenants.
    • Fix: Clear instances on tenant switch:
      class ClearFacadeInstancesTask implements SwitchTenantTask
      {
          public function makeCurrent(IsTenant $tenant): void
          {
              Facade::clearResolvedInstances();
          }
      }
      
    • Alternative: Use tenant-specific facades via tenant() helper.
  2. Database Connection Leaks

    • Issue: Forgetting to switch databases can cause queries to run on the wrong connection.
    • Fix: Always use SwitchDatabaseTask or manually set the connection:
      $tenant->setConnection();
      
  3. Job Tenant Resolution Failures

    • Issue: Jobs may fail if the tenant is deleted before execution.
    • Fix: Handle CurrentTenantCouldNotBeDeterminedInTenantAwareJob:
      try {
          Tenant::current()->execute(fn() => $job->handle());
      } catch (\Spatie\Multitenancy\Exceptions\CurrentTenantCouldNotBeDeterminedInTenantAwareJob $e) {
          Log::error("Tenant not found for job: {$job->jobId}");
      }
      
  4. Caching Tenant Resolution

    • Issue: Caching app('currentTenant') can lead to stale data.
    • Fix: Avoid caching tenant resolution; recompute per request.
  5. Migration Conflicts

    • Issue: Running migrations without tenant context may fail.
    • Fix: Use Tenant::forAll() or Tenant::current()->runCommand('migrate').

Debugging Tips

  • Log Tenant Switches:
    Tenant::current()->wasRecentlySwitched(); // Check if tenant changed in the request.
    
  • Inspect Tenant Finder:
    $finder = app(\Spatie\Multitenancy\TenantFinder\DomainTenantFinder::class);
    $tenant = $finder->findForRequest(request());
    
  • Test with Tenant::fake():
    Tenant::fake([$fakeTenant]); // Override tenant for testing.
    

Extension Points

  1. Custom Tenant Finders Extend TenantFinder for logic like:

    • API key-based resolution.
    • Header-based tenant selection.
    • Fallback to a default tenant.
  2. Dynamic Database Switching Override SwitchDatabaseTask to support:

    • Multi-database setups (e.g., PostgreSQL schemas).
    • Read replicas per tenant.
  3. Tenant-Specific Config Use tenant() helper or middleware to load tenant-specific configs:

    $tenant->tenant()->config(['key' => 'value']);
    
  4. Event Listeners Listen to TenantSwitched events for side effects:

    event(new TenantSwitched($oldTenant, $newTenant));
    

Configuration Quirks

  • tenant_model: Must implement IsTenant (not just HasTenants).
  • switch_tenant_tasks: Order matters (e.g., switch database before running queries).
  • Queue Awareness: Set queues_are_tenant_aware_by_default before dispatching jobs.

Performance

  • Avoid N+1 Queries: Use with() or eager load tenant relations.
  • Cache Tenant Lookups: Cache results of findForRequest() if resolution is expensive.
    $tenant = cache()->remember("tenant:{$host}", now()->addHours(1), fn() => $finder->findForRequest($request));
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony