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.php) with:

    use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
    
    class Tenant extends BaseTenant
    {
        // Customize as needed
    }
    
  3. Run Migrations:

    php artisan migrate
    
  4. Configure Hostname Resolution: In config/tenancy.php, set your preferred resolver (default: hostname):

    'resolver' => [
        'class' => \Stancl\Tenancy\Resolvers\HostnameTenantResolver::class,
        'configuration' => [
            'domain' => 'yourdomain.com',
        ],
    ],
    
  5. First Tenant Creation: Use the tenancy:create command:

    php artisan tenancy:create example.com --owner=1
    
  6. Test Tenancy: Access your app via https://example.com to verify tenant isolation.


First Use Case: Tenant-Specific Routes

Create a tenant-specific route in routes/web.php:

use Stancl\Tenancy\Routes\TenantMiddleware;

Route::middleware([TenantMiddleware::class])->group(function () {
    Route::get('/', function () {
        return 'Hello, Tenant!';
    });
});

Implementation Patterns

Core Workflows

1. Tenant Identification

  • Hostname Resolution: Automatically resolves tenants via subdomains (e.g., tenant1.yourdomain.com). Customize in config/tenancy.php:

    'resolver' => [
        'class' => \Stancl\Tenancy\Resolvers\HostnameTenantResolver::class,
        'configuration' => [
            'domain' => 'yourdomain.com',
            'wildcard' => true, // Supports `*.yourdomain.com`
        ],
    ],
    
  • Path-Based Resolution: Use PathTenantResolver for path-based routing (e.g., /tenant1/*). Configure in tenancy.php:

    'resolver' => \Stancl\Tenancy\Resolvers\PathTenantResolver::class,
    

2. Database Tenant Switching

  • Automatic Switching: Tenancy automatically switches the database connection per request. No manual connection changes required in models or queries.

  • Central Database Access: Use tenant() helper to access the central database:

    $centralData = \DB::connection('mysql')->table('tenants')->get();
    

3. Queue Tenancy

  • Job Handling: Jobs inherit the tenant context of the request that dispatched them. Example job (app/Jobs/SendEmail.php):

    use Stancl\Tenancy\Contracts\Tenancy;
    
    public function handle(Tenancy $tenancy)
    {
        $tenancy->initialize(); // Ensures tenant context
        Mail::to('user@example.com')->send(new WelcomeEmail());
    }
    
  • Testing Queues: Use queue:work in tests with tenant context:

    $this->artisan('queue:work', ['--once' => true])
         ->expectsQuestion('Do you want to enable tenancy?', 'yes')
         ->expectsQuestion('Which tenant?', 'example.com');
    

4. Filesystem Tenancy

  • Tenant-Specific Storage: Configure in tenancy.php:
    'filesystem' => [
        'disks' => ['local', 's3'], // Supported disks
        'tenant_path' => 'tenants/{tenant_id}', // Custom path structure
    ],
    
    Access tenant files:
    Storage::disk('local')->put('tenants/1/file.txt', 'content');
    

5. Middleware Integration

  • Tenant Middleware: Use TenantMiddleware to enforce tenancy:

    Route::middleware([TenantMiddleware::class])->group(function () {
        // Tenant-specific routes
    });
    
  • Universal Routes: Bypass tenancy for admin routes:

    Route::middleware(['web', 'auth', 'tenancy:disable'])->group(function () {
        // Admin routes (central DB access)
    });
    

6. Seeding Tenants

  • Seed Command: Use tenancy:seed to seed tenant-specific data:

    php artisan tenancy:seed --tenant=example.com
    
  • Central Seeding: Seed central database first, then tenants:

    php artisan migrate --seed --force
    php artisan tenancy:seed --all
    

Integration Tips

Laravel Features

  • Caching: Tenancy-aware cache tags:

    Cache::tags(['tenant:1'])->put('key', 'value', now()->addHour());
    
  • Events: Dispatch tenant-specific events:

    event(new TenantCreated($tenant));
    
  • Notifications: Send tenant-scoped notifications:

    $user->notify(new WelcomeNotification());
    

Testing

  • Tenant Context: Use Tenancy::initialize() in tests:

    public function testTenantRoutes()
    {
        Tenancy::initialize(Tenant::find(1));
        $response = $this->get('/');
        $response->assertSee('Hello, Tenant!');
    }
    
  • Mocking Tenants: Override the resolver for testing:

    $this->app->instance(
        \Stancl\Tenancy\Contracts\TenantResolver::class,
        new class implements \Stancl\Tenancy\Contracts\TenantResolver {
            public function resolve(): ?\Stancl\Tenancy\Database\Models\Tenant
            {
                return Tenant::find(1);
            }
        }
    );
    

Gotchas and Tips

Pitfalls

1. Queue Worker Tenancy

  • Issue: Queue workers may not inherit tenant context. Fix: Use queue:work with --tenant flag or ensure the bootstrapper is initialized:
    php artisan queue:work --tenant=example.com
    
    Or configure in .env:
    QUEUE_TENANT=example.com
    

2. Route Caching

  • Issue: Cached routes may break tenant resolution. Fix: Clear route cache after tenant changes:
    php artisan route:clear
    
    Or disable caching for tenant routes:
    Route::middleware([TenantMiddleware::class, 'cache.disable'])->group(...);
    

3. Database Transactions

  • Issue: Transactions may span tenant contexts. Fix: Avoid cross-tenant transactions. Use explicit tenant switching:
    Tenancy::initialize($tenant);
    DB::transaction(...);
    

4. Vite Asset Paths

  • Issue: Vite paths may not resolve correctly in multi-tenant setups. Fix: Configure tenant-aware asset paths in vite.config.js:
    import { defineConfig } from 'vite';
    import laravel from 'laravel-vite-plugin';
    import tenancy from 'laravel-tenancy/vite';
    
    export default defineConfig({
        plugins: [
            laravel({
                input: ['resources/sass/app.scss', 'resources/js/app.js'],
                refresh: true,
            }),
            tenancy(),
        ],
    });
    

5. Model Observers

  • Issue: Observers may not trigger in tenant context. Fix: Ensure observers are registered in the tenant's database:
    Tenancy::initialize($tenant);
    Model::observe(ModelObserver::class);
    

6. Schema Migrations

  • Issue: Schema migrations may fail if tables exist in the central DB. Fix: Use migrate-fresh with --tenant:
    php artisan migrate:fresh --tenant=example.com
    

Debugging Tips

1. Tenant Resolution Logs

  • Enable debug logging in tenancy.php:
    'debug' => env('TENANCY_DEBUG', false),
    
    Check logs for resolution issues:
    tail -f storage/logs/laravel.log | grep tenancy
    

2. Connection Switching

  • Verify active connection:
    \DB::connection()->getDatabaseName(); // Should return tenant DB
    

3. Resolver Testing

  • Test resolvers manually:
    $resolver = app(\Stancl\Tenancy\Contracts\TenantResolver::class);
    $tenant = $resolver->resolve();
    dd($tenant
    
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope