Use when working with the multi-tenant architecture — customer DB switching, FQDN routing, tenant isolation, or cross-tenant operations.
Scanned 6/5/2026
Install via CLI
openskills install event4u-app/agent-config---
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
---
<!-- Framework scope: Laravel only. Uses Eloquent model conventions,
Laravel Context, and Artisan traits — concepts not portable across
frameworks. Other stacks need a stack-native multi-tenancy skill. -->
# 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
1. **Gather context** — read `agents/reference/docs/` for multi-tenant architecture, `config/database.php` for connection definitions, and search for the tenant switching service.
2. **Identify connection** — determine whether the code touches central, tenant, or both databases. Set `$connection` explicitly on any new model.
3. **Implement** — write the feature using the correct connection. Use the tenant switching service for cross-tenant operations. Never mix connections in a single query.
4. **Verify isolation** — inspect the code for tenant leaks: global scopes, missing `$connection`, shared caches, or job serialization without tenant context.
5. **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:
1. Store tenant context (e.g., in Laravel Context or a singleton)
2. Load tenant configuration
3. Set monitoring context (tenant ID, name, domain)
4. Reconfigure the database connection with tenant credentials
5. Bind tenant-specific services via the container
## Model conventions
### Setting `$connection`
Every model **must** explicitly set its connection:
```php
// 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 `$connection`** on 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 `RefreshDatabase` or 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
1. Tenant-aware code with correct DB connection switching
2. 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_database` is 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_databases` table.
No comments yet. Be the first to comment!