Multi-Tenancy Guide
This guide explains the multi-tenancy architecture and how to work with tenant-isolated data. For complete technical details, see .context/global/multi-tenancy.md.Overview
The CMS uses schema-per-tenant isolation with multi-site support:- Database: One master database + one schema per tenant
- Isolation: Complete data separation at PostgreSQL schema level
- Multi-Site: One tenant can manage multiple websites (v0.13+)
- Headers:
X-Tenant-Id(required),X-Site-Id(optional, UUID format, defaults to first active site)
Architecture
Database Structure
Three-Tier Isolation
Working with Tenants
Headers Required
Every API request must includeX-Tenant-Id:
X-Site-Id for multi-site (UUID of the target site):
Creating a Tenant
Listing Tenants
Deleting a Tenant
Multi-Site Support (v0.13+)
Concept
One tenant = Multiple websites:Site Isolation
Database:- All tenant entities have
sitecolumn (integer) - Unique constraints scoped per site:
(slug, site)
Working with Sites
List Sites (for current tenant):Security Model
Cross-Tenant Access Prevention
Automatic Enforcement:- Schema switching prevents cross-tenant queries
- Doctrine filters add
WHERE site = :siteto all queries - TenantSubscriber validates user belongs to tenant
Query Safety
✅ SAFE (automatic filtering):Storage Isolation
S3/MinIO Structure
tenant_{id}/
Storage Quotas
Per-Tenant Quotas:- Default: 1 GB (1024 MB)
- Range: 1 MB - 100 GB
- Enforced before upload
Development Workflow
Testing Multi-Tenancy
Create Test Tenants:Frontend Configuration
Nuxt 3 (environment-based):Production Considerations
Scaling Strategies
1. Schema per Tenant (Current):- Pros: Complete isolation, easy to understand
- Cons: Limited to ~1000 tenants per database
- Best for: B2B SaaS with hundreds of customers
- Small tenants: Shared schema with
tenant_idcolumn - Large tenants: Dedicated schemas
- Best for: 10,000+ tenants with varying sizes
Backup Strategy
Per-Tenant Backups:Monitoring
Key Metrics:- Active tenants count
- Database schema size per tenant
- Storage usage per tenant
- API requests per tenant
- Cache hit rate per tenant
- Storage quota > 90%
- Schema size > 10 GB
- Unusual cross-tenant access attempts
Troubleshooting
”Tenant not found”
Causes:X-Tenant-Idheader missing- Tenant slug incorrect
- Tenant status = ‘suspended’ or ‘deleted’
- Check header:
X-Tenant-Id: <slug> - Verify tenant exists:
php bin/console app:tenant:list - Check tenant status in
cms_tenantstable
”User does not belong to this tenant”
Causes:- User’s
tenant_iddoesn’t matchX-Tenant-Id - User created in wrong tenant
- Check user’s
tenant_idincms_userstable - Recreate user in correct tenant
- Update user’s
tenant_id(⚠️ use with caution)
Schema not found
Causes:- Tenant schema not created
- Migrations not run