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

Tenancy Laravel Package

stancl/tenancy

Automatic multi-tenancy for Laravel with minimal code changes. Supports tenant identification by hostname (including second-level domains) and avoids swapping core classes or adding model traits. Ideal for SaaS apps needing seamless tenant isolation.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require stancl/tenancy
    

    Publish the package assets:

    php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider"
    
  2. Configure Tenant Model: Extend your Tenant model (e.g., App\Models\Tenant) with Stancl\Tenancy\Database\Models\Concerns\HasTenancy:

    use Stancl\Tenancy\Database\Models\Concerns\HasTenancy;
    
    class Tenant extends Model
    {
        use HasTenancy;
    }
    
  3. Run Migrations:

    php artisan migrate
    
  4. Configure Tenant Identification: Update config/tenancy.php to define how tenants are identified (e.g., by hostname):

    'identification' => [
        'resolver' => \Stancl\Tenancy\Resolvers\DomainTenantResolver::class,
        'domain' => env('TENANCY_DOMAIN', 'tenant.app'),
    ],
    
  5. First Tenant Creation: Use the tenancy:create command to create your first tenant:

    php artisan tenancy:create first-tenant --email=admin@first-tenant.com --password=password
    
  6. Test Tenant Isolation: Access your app via first-tenant.tenant.app to verify tenant isolation.


First Use Case: Hostname-Based Tenant Resolution

  • Scenario: Automatically resolve tenants based on the request's hostname.
  • Implementation:
    • Configure DomainTenantResolver in tenancy.php.
    • Access your app via tenant-name.tenant.app to trigger tenant resolution.
    • No additional code changes required; the package handles routing and database context switching automatically.

Implementation Patterns

Core Workflows

1. Tenant Resolution and Switching

  • Pattern: Use middleware or facade to resolve and switch tenants dynamically.

  • Example:

    use Stancl\Tenancy\Resolvers\DomainTenantResolver;
    
    // Resolve tenant from request (e.g., in middleware)
    $tenant = app(DomainTenantResolver::class)->resolve();
    
    // Switch to tenant's database context
    $tenant->switch();
    
  • Middleware Integration:

    use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
    
    protected $middleware = [
        InitializeTenancyByDomain::class,
    ];
    

2. Universal Routes for Central Tenant

  • Pattern: Define routes that bypass tenant isolation (e.g., admin or landing pages).
  • Implementation:
    Route::middleware(['web', 'tenancy'])->group(function () {
        // Tenant-specific routes
    });
    
    Route::middleware(['web', 'tenancy:central'])->group(function () {
        // Central tenant routes (e.g., /admin)
    });
    

3. Queue Tenancy

  • Pattern: Ensure jobs run in the correct tenant context.
  • Implementation:
    • Use QueueTenancyBootstrapper to automatically switch tenant context for jobs:
      use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
      
      QueueTenancyBootstrapper::boot();
      
    • Dispatch jobs with tenant-aware models:
      YourJob::dispatch($tenant->user); // Works if the job uses `find()` for model resolution
      

4. Filesystem and Storage Tenancy

  • Pattern: Isolate tenant-specific files (e.g., uploads, assets).
  • Implementation:
    • Configure tenant-aware filesystem disks in tenancy.php:
      'filesystems' => [
          'disks' => [
              'tenant-assets' => [
                  'driver' => 'local',
                  'root' => storage_path('app/tenants'),
              ],
          ],
      ],
      
    • Use the TenantAssets facade to interact with tenant-specific storage:
      use Stancl\Tenancy\Facades\TenantAssets;
      
      $path = TenantAssets::path('uploads/image.jpg');
      

5. Vite and Asset Compilation

  • Pattern: Compile tenant-specific assets (e.g., CSS/JS).
  • Implementation:
    • Configure Vite in vite.config.js to use tenant-specific paths:
      import { defineConfig } from 'vite';
      import laravel from 'laravel-vite-plugin';
      import tenancy from 'laravel-tenancy/vite';
      
      export default defineConfig({
          plugins: [
              laravel({
                  input: ['resources/css/app.css', 'resources/js/app.js'],
                  refresh: true,
              }),
              tenancy(),
          ],
      });
      
    • Use the TenantAssets facade to generate tenant-specific asset URLs:
      $assetUrl = TenantAssets::url('css/app.css');
      

Integration Tips

Database Migrations

  • Pattern: Run migrations for a specific tenant.
  • Implementation:
    php artisan tenant:migrate --tenant=tenant-id
    
    Or use the migrate-fresh command:
    php artisan tenant:migrate-fresh --tenant=tenant-id
    

Seeding Tenants

  • Pattern: Seed data for a specific tenant.
  • Implementation:
    php artisan tenant:seed --tenant=tenant-id
    
    Use --force to overwrite existing data:
    php artisan tenant:seed --tenant=tenant-id --force
    

Tenant Impersonation

  • Pattern: Test or debug as a tenant user.
  • Implementation:
    use Stancl\Tenancy\Facades\Tenancy;
    
    Tenancy::impersonate($tenant->user);
    
    Or via Artisan:
    php artisan tenancy:impersonate --user=user-id --tenant=tenant-id
    

Custom Tenant Resolvers

  • Pattern: Extend or replace the default tenant resolver.
  • Implementation:
    use Stancl\Tenancy\Resolvers\TenantResolver;
    
    class CustomTenantResolver implements TenantResolver
    {
        public function resolve(): ?\Stancl\Tenancy\Database\Models\Tenant
        {
            // Custom logic (e.g., resolve from API key, subdomain, etc.)
            return Tenant::where('api_key', request()->header('X-API-KEY'))->first();
        }
    }
    
    Register the resolver in tenancy.php:
    'identification' => [
        'resolver' => \App\Resolvers\CustomTenantResolver::class,
    ],
    

Gotchas and Tips

Pitfalls and Debugging

1. Tenant Resolution Failures

  • Issue: Tenant not resolved or incorrect tenant switched.
  • Debugging:
    • Check tenancy.php for correct resolver configuration.
    • Verify the request's domain/subdomain matches the tenant's identifier.
    • Log the resolved tenant:
      \Stancl\Tenancy\Facades\Tenancy::resolve();
      
    • Ensure the InitializeTenancyByDomain middleware is registered.

2. Database Connection Issues

  • Issue: Jobs or queries fail with "Connection not found" errors.
  • Debugging:
    • Ensure the QueueTenancyBootstrapper is registered for queue workers.
    • Verify the tenant's database exists and is accessible.
    • Check for misconfigured .env variables (e.g., DB_DATABASE for tenants).
    • Use Tenancy::getTenant() to confirm the active tenant.

3. Cache Invalidation

  • Issue: Tenant changes (e.g., deletion) not reflected due to caching.
  • Debugging:
    • Clear the tenant resolver cache:
      php artisan cache:clear
      
    • Manually invalidate the resolver cache:
      \Stancl\Tenancy\Facades\Tenancy::forgetResolvedTenant();
      

4. Queue Worker Tenancy

  • Issue: Queue jobs run in the central tenant context.
  • Debugging:
    • Ensure QueueTenancyBootstrapper::boot() is called in AppServiceProvider.
    • Verify the queue worker process has access to the tenant's database.
    • Test with:
      php artisan queue:work --tenant=tenant-id
      

5. Filesystem Permissions

  • Issue: Tenant-specific files not writable or missing.
  • Debugging:
    • Check storage_path('app/tenants') permissions.
    • Ensure the tenant-assets disk is configured correctly in tenancy.php.
    • Verify the tenant's directory exists:
      TenantAssets::path(); // Should return the tenant's storage path
      

6. Vite Asset Paths

  • Issue: Tenant-specific assets (e.g., CSS/JS) not loading.
  • Debugging:
    • Ensure laravel-tenancy/vite is installed and configured in vite.config.js.
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.
boundwize/jsonrecast
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
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata