model_tier: medium name: multi-tenancy description: "Use when working with the multi-tenant architecture — customer DB switching, FQDN routing, tenant isolation, or cross-tenant operations." domain: engineering framework: laravel workspaces: - engineering packs: - engineering-base
multi-tenancy
When to use
Use this skill when working with tenant-specific data, customer database connections, or any code that touches the dual-database architecture.
Do NOT use when:
- Single-database applications
- Frontend-only changes
Procedure: Work with multi-tenancy
- Gather context — read
agents/reference/docs/for multi-tenant architecture,config/database.phpfor connection definitions, and search for the tenant switching service. - Identify connection — determine whether the code touches central, tenant, or both databases. Set
$connectionexplicitly on any new model. - Implement — write the feature using the correct connection. Use the tenant switching service for cross-tenant operations. Never mix connections in a single query.
- Verify isolation — inspect the code for tenant leaks: global scopes, missing
$connection, shared caches, or job serialization without tenant context. - Test — write a test that exercises the tenant boundary: seed tenant-specific data, switch context, verify correct data is returned and other tenants' data is invisible.
Architecture overview
Request → Identify Tenant (JWT / subdomain / API key)
→ Lookup credentials from central database
→ Reconfigure tenant connection at runtime
→ All tenant queries use tenant connection
Dual-database pattern
| Connection type | Purpose | Scope |
|---|---|---|
| Central / shared | Global data — tenants, config, shared resources | Shared across all tenants |
| Tenant / customer | Tenant-specific data — domain entities | One per tenant |
The central connection is typically the default connection. The tenant connection starts empty and is configured dynamically per request.
Additional connections
Projects may have additional connections for admin operations, provisioning, or monitoring. Check config/database.php.
Core tenant switching service
Scope: Laravel-specific (see frontmatter). For non-Laravel multi-tenant systems, the concepts below still apply but the implementation differs — consult the framework's connection / session / DI conventions.
Search the codebase for the service responsible for tenant switching. Typical responsibilities:
- Store tenant context (e.g., in Laravel Context or a singleton)
- Load tenant configuration
- Set monitoring context (tenant ID, name, domain)
- Reconfigure the database connection with tenant credentials
- Bind tenant-specific services via the container
Model conventions
Setting $connection
Every model must explicitly set its connection:
// Central models
class Tenant extends Model
{
protected $connection = 'central_database';
}
// Tenant models
class Project extends Model
{
protected $connection = 'tenant_database';
}
Check the project for the actual connection names and namespace conventions.
Tenant isolation rules
- Never query the tenant connection before the tenant is set.
- Never mix connections in a single query — use explicit joins or separate queries.
- Always specify
$connectionon new models — never rely on the default. - Use transactions per connection —
DB::connection('tenant_database')->transaction(...). - Artisan commands that need tenant access must use the appropriate trait (search for it in the project).
Testing with tenants
- Tests use dedicated tenant seeders (check
agents/reference/docs/for seeder conventions). - The testing database may consolidate multiple connections into a single DB for simplicity.
- Use
RefreshDatabaseor manual seeding — never assume a specific tenant state from previous tests.
Common pitfalls
| Problem | Solution |
|---|---|
Query on customer_database returns empty |
Customer not set yet — check middleware order |
| Race condition in parallel tests | Each test process gets its own DB (parallel testing with separate DBs) |
| Wrong tenant data in background jobs | Serialize customer ID, re-resolve in job's handle() method |
| Migration on wrong connection | Specify --database=customer_database or set $connection in migration |
Output format
- Tenant-aware code with correct DB connection switching
- Verification that tenant isolation is maintained
Auto-trigger keywords
- multi-tenant
- tenant isolation
- customer database
- FQDN routing
Gotcha
- Always verify which database connection is active before running queries — cross-tenant data leaks are critical bugs.
- The model forgets to switch back to the main connection after tenant operations.
- Queue jobs serialize the connection state — ensure the tenant context is restored when the job runs.
- Don't use
DB::connection()directly — use the tenant switching helpers.
Do NOT
- Do NOT hardcode database names — always use connection names.
- Do NOT assume
customer_databaseis available in service providers or early boot. - Do NOT access tenant data in global middleware that runs before customer identification.
- Do NOT store tenant DB credentials in code — they come from the
customer_databasestable.