Multi-Tenant SaaS Application
The hardest project here. Serve several customers from one system without ever leaking data between them.
The brief
What the finished thing needs to do.
- Multiple tenant organisations, each with their own users and data
- Complete data isolation between tenants, enforced structurally
- Per-tenant configuration and feature flags
- Tenant-aware authentication and authorization
- Usage metering per tenant
- Onboarding a new tenant without a deployment
How to structure it
The central decision is the isolation strategy: a database per tenant, a schema per tenant, or a shared schema with a tenant identifier on every row. Each has genuine trade-offs in isolation strength, operational cost and query complexity. Choose deliberately and write down why.
Whichever you pick, isolation must be enforced structurally rather than by remembering to filter. A forgotten WHERE clause in a shared-schema design is a data breach, so the filter has to be applied somewhere it cannot be omitted — a global query filter, or a per-request scoped context.
Tenant resolution happens once per request, early, and everything downstream reads it from there. Passing a tenant identifier through method signatures manually is exactly how one call site gets missed.
Test isolation explicitly and adversarially: authenticate as tenant A and attempt to reach tenant B's records by direct identifier. That test is not optional.
Why these technologies
- Global query filters
- Applies the tenant filter automatically so it cannot be forgotten on an individual query.
- Scoped tenant context per request
- Resolved once at the boundary and available everywhere without manual passing.
- Per-tenant configuration store
- Onboarding should be data, not a code change and deployment.
- Adversarial isolation tests
- Cross-tenant leakage is the one failure that cannot be allowed to reach production.
Build it in this order
Each stage produces something that works. That matters — a project that only runs at the very end is a project people abandon.
Choose and document the isolation strategy
Write down the trade-offs and the decision before writing code.
Tenant model and resolution
Identify the tenant from the request and make it available for the request's lifetime.
Structural isolation
Apply filtering where it cannot be bypassed, not at each call site.
Adversarial isolation tests
Attempt cross-tenant access by direct identifier and confirm it fails.
Tenant-aware identity
Users belong to a tenant; a token is valid only for its own tenant.
Per-tenant configuration
Settings and feature flags resolved per tenant at run time.
Usage metering
Record usage per tenant without adding meaningful request latency.
Self-service onboarding
Create a tenant through the application, with no deployment required.
Done means
How to know it is finished
Check each of these before moving on. If one fails, the project is not done yet — and that is useful information rather than a setback.
- No request can return another tenant's data, including by guessing identifiers
- Isolation is enforced structurally, not by per-query discipline
- Adversarial cross-tenant tests exist and pass
- A new tenant can be onboarded without a code change or deployment
- Feature flags and configuration resolve per tenant correctly
- Usage is metered accurately per tenant
If you want to go further
Extensions worth attempting
Only once the core build meets every criterion above.
- Add per-tenant data export and deletion for compliance requests
- Add tenant-level rate limiting
- Support migrating a large tenant to a dedicated database
- Add per-tenant custom domains
Have a project worth talking through?
Tell us what you're building or what's slowing your current system down. We'll give you a direct read on scope and approach.