Multi-tenancy lets one Laravel deployment serve many customers with isolated data. Choosing between single-database row scoping and database-per-tenant architectures affects compliance, scaling, and operational complexity.
Tenancy Models and Planning
Single-database tenancy adds tenant_id to tables. Database-per-tenant gives stronger isolation for regulated industries.
Identify tenant resolution strategy early: subdomain, custom domain, or path prefix.
Step 1: Choose single vs multi database
Single DB with global scopes is simpler to operate. Separate databases simplify backups per customer and noisy-neighbor isolation.
Hybrid approaches use single DB for billing and per-tenant DB for application data at enterprise scale.
- Single DB: shared schema + tenant_id
- Multi DB: stancl/tenancy or custom switching
Step 2: Define the Tenant model
Create a tenants table with name, slug, domain, and plan fields. Users belong to tenants via tenant_id foreign key.
Central apps may keep tenants in a landlord connection while tenant apps use dynamic connections.
php artisan make:model Tenant -m
Step 3: Resolve tenant from subdomain
Parse request()->getHost() in middleware. Look up Tenant::where('domain', $subdomain)->firstOrFail().
Bind the current tenant in the service container: app()->instance('tenant', $tenant).
$tenant = Tenant::where('slug', $subdomain)->firstOrFail();
app()->instance('tenant', $tenant);
Step 4: Register tenant middleware globally
Add IdentifyTenant middleware to the web group in bootstrap/app.php or HTTP Kernel.
Exclude landlord routes like marketing site and tenant registration from tenant resolution.
->withMiddleware(function ($middleware) {
$middleware->web(append: [IdentifyTenant::class]);
})
Data Isolation with Global Scopes
Global scopes automatically append where tenant_id = ? to Eloquent queries preventing cross-tenant leaks.
Every tenant-owned model must include the scope and auto-set tenant_id on create.
Step 1: Create a BelongsToTenant trait
Add a booted() method applying TenantScope and setting tenant_id from app('tenant') on creating.
Controllers never accept tenant_id from request input. Always derive from authenticated context.
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
$model->tenant_id = app('tenant')->id;
});
Step 2: Scope queries in tests
In tests, set app()->instance('tenant', $tenant) before creating models.
Assert users cannot access other tenants' records with policy and feature tests.
$tenant = Tenant::factory()->create();
app()->instance('tenant', $tenant);
Step 3: Use stancl/tenancy for database switching
composer require stancl/tenancy configures tenant databases, migrations, and bootstrappers.
Tenancy::initialize($tenant) switches default DB connection for the request lifecycle.
composer require stancl/tenancy
php artisan tenancy:install
Step 4: Run tenant migrations
php artisan tenants:migrate runs migrations across all tenant databases.
Seed demo data per tenant with tenants:seed for onboarding trials.
php artisan tenants:migrate
Billing and Onboarding Flows
SaaS tenants need signup, plan selection, and provisioning workflows.
Integrate Stripe Customer per tenant and webhooks to activate or suspend accounts.
Step 1: Build tenant registration
Landlord routes create Tenant and admin User, then dispatch ProvisionTenantJob to set up schema or defaults.
Use database transactions so failed provisioning rolls back partial records.
php artisan make:job ProvisionTenant
Step 2: Map users to roles per tenant
Packages like spatie/laravel-permission work with teams feature setting team_id to tenant_id.
Roles like owner, admin, and member differ per tenant without global role leakage.
setPermissionsTeamId($tenant->id);
Step 3: Customize per-tenant settings
Store branding and feature flags in a tenant_settings JSON column or related table.
Cache settings per tenant with Cache::tags(['tenant.'.$id]) and flush on update.
$settings = $tenant->settings;
Step 4: Audit cross-tenant access
Log admin impersonation and landlord support access. Alert on queries missing tenant scope in staging.
Regular security reviews should attempt IDOR attacks across tenant boundaries.
- Log impersonation sessions
- Automate cross-tenant leak tests
Aadyanex builds production-ready Laravel applications with clean architecture, secure APIs, and long-term support.
