Multi-Tenant Application Design
A multi-tenant application serves many customer organizations — tenants — from one codebase and, usually, shared infrastructure. Each tenant sees only its own users, data, and settings, as if the product were theirs alone. Almost every business SaaS product works this way, because running one deployment for thousands of customers is far cheaper and simpler than running thousands of deployments.
The price of sharing is risk. In a multi-tenant system, a single missing WHERE tenant_id = ?, a cache key without a tenant prefix, or a background job that runs in the wrong context can show one customer's data to another — the incident B2B customers forgive least. Good multi-tenant design chooses the right level of isolation for each kind of customer and makes tenant context so pervasive that forgetting it is difficult.
TL;DR
- Choose a tenancy model: shared tables with a
tenant_id(pool), a schema per tenant, a database per tenant (silo), or a hybrid by tier. - Resolve the tenant once from authenticated identity and carry it explicitly through every layer; never trust a tenant ID sent by the client.
- Enforce isolation in the data layer — tenant-scoped queries plus database row-level security — not only in route handlers.
- Make every shared resource tenant-aware: cache keys, queues, file paths, search indexes, logs, and metrics.
- Protect tenants from each other with per-tenant quotas, rate limits, and fair scheduling.
- Test cross-tenant access continuously, and design for moving, exporting, and deleting a single tenant.
Quick Example
Shared tables with PostgreSQL row-level security. The application sets the tenant for each transaction, and the database refuses to return or write another tenant's rows, even if a query forgets its filter.
If code runs a query without setting a tenant, current_setting fails and the query errors instead of returning everyone's data — the isolation fails closed. The application must connect as a role without BYPASSRLS or superuser rights, or the policies are silently skipped. See Row-Level Security.
Core Concepts
Tenancy Models
Many products are hybrid: most tenants share a pool, while enterprise tenants pay for a silo. Building the application so the same code runs against either keeps that option open.
Tenant Context
Every request, job, and event must know which tenant it acts for. Resolve the tenant at the edge from trusted inputs — the authenticated session or token claims, checked against the user's memberships, or a verified custom domain — and carry it in request-scoped context (AsyncLocalStorage in Node.js, contextvars in Python, request scope in Java frameworks). Pass it explicitly into the data layer rather than letting each query rediscover it.
Users often belong to several tenants. Model membership explicitly (memberships(user_id, tenant_id, role)), let the user choose the active tenant, and scope sessions or tokens to one tenant at a time. Authorization within a tenant is covered in Authorization Models: RBAC, ABAC, and ReBAC.
Layers of Isolation
Enforcing Isolation in the Data Layer
Scoped Access by Default
Make the tenant-scoped path the easy one: repositories or ORM scopes that require a tenant, query helpers that add the filter automatically, and a deliberately awkward, audited "system" context for the rare cross-tenant job. Row-level security then acts as a backstop for the query someone writes by hand.
Tenant IDs Everywhere
Put tenant_id on every tenant-owned table, including child tables whose parent already has one. It makes policies simple, indexes efficient, and future sharding by tenant possible. Lead indexes and unique constraints with it — UNIQUE (tenant_id, name), not UNIQUE (name), so two tenants can each have a project called "Website".
Prevent Cross-Tenant References
A foreign key on project_id alone lets a task in tenant A point at a project in tenant B. Including the tenant in the key makes that impossible:
Identifiers
Globally unique identifiers, such as UUIDs, avoid revealing how many records other tenants have, make URLs harder to guess, and let you move a tenant's data to another database without renumbering it.
Tenant-Aware Everything Else
Data leaks between tenants rarely come from the main tables. They come from everything around them:
Noisy Neighbors and Fairness
In a shared system, one tenant's bulk import, runaway integration, or enormous report can slow everyone else. Protect tenants from each other:
- Per-tenant rate limits and quotas on API requests, storage, and expensive operations — see Rate Limiting.
- Fair scheduling of background work: per-tenant queues or weighted round-robin, so one tenant's backlog does not block the rest.
- Database guardrails: statement timeouts, connection limits per tenant, and moving the heaviest tenants to their own shard or silo.
- Per-tenant monitoring of usage and cost, so you notice a noisy tenant before other customers do.
Proving these protections work needs dedicated tests — see Tenant Isolation and Noisy Neighbor Testing.
The Tenant Lifecycle
- Onboarding: automated, idempotent provisioning — create the tenant record, default roles, settings, and, for silos, the database and keys.
- Configuration and entitlements: plan limits and features per tenant, often driven by feature flags keyed on the tenant.
- Data residency: place tenants in a region on creation; moving them later is a migration project.
- Moving tiers: copying a tenant from the pool to a dedicated database requires tenant-scoped export and import, and a cutover plan.
- Offboarding: export on request, then deletion that honors retention rules — see Data Deletion and Retention Enforcement.
- Single-tenant restore: restoring one tenant from a shared database means restoring to a side instance and copying its rows back. Plan and rehearse it before a customer asks.
Best Practices
Fail Closed
Missing tenant context should cause an error, never an unfiltered query. Design defaults — RLS that errors without a setting, repositories that demand a tenant — so mistakes are loud.
Test Isolation Like a Feature
Run automated tests that act as one tenant and try to read, update, and enumerate another tenant's resources across every endpoint. See Authorization Testing Across Tenant Boundaries.
Audit Cross-Tenant Access
Support staff and internal tools sometimes need access across tenants. Make it explicit, time-limited, approved where appropriate, and logged — and visible to the customer if your contracts promise that.
Plan for Sharding Early
A tenant_id on every table and tenant-scoped queries make it straightforward to split tenants across databases later. See Database Sharding.
Common Mistakes
Trusting the Tenant ID From the Request
Filtering Only in Handlers
Connecting as a Role That Bypasses RLS
Cache Keys Without a Tenant
FAQ
Which tenancy model should I start with?
For most new SaaS products, shared tables with a tenant_id on every row, scoped data access, and row-level security. It is the cheapest to run and simplest to migrate. Keep the code independent of where a tenant's data lives so that a dedicated-database tier can be added for customers who need it.
Is row-level security enough on its own?
It is a strong backstop, not a complete design. Use it alongside tenant-scoped application code, tenant-aware caches, jobs, and storage, and automated cross-tenant tests. RLS protects the database; it cannot stop a cache or a file URL from leaking data.
How do users who belong to several tenants work?
Store memberships explicitly with a role per tenant, let the user pick the active tenant, and scope each session or token to that tenant. Switching tenants should re-run authorization rather than reuse permissions from the previous tenant.
When should a tenant get its own database?
When contracts or regulation require physical separation, data must live in a specific region, the tenant's size or workload affects others, or the customer needs independent backups, restores, or encryption keys. Price the tier to cover the extra operational cost.
How do we run migrations across many tenant databases?
Automate them, run them in batches with monitoring, track the schema version per tenant, and keep every migration backward compatible so the application works while the fleet is partly migrated. See Database Migrations.
Related Topics
- Backend Development — Server-side architecture and frameworks
- Row-Level Security (RLS) — Database-enforced tenant isolation in PostgreSQL
- Authorization Models: RBAC, ABAC, and ReBAC — Permissions within a tenant
- Authorization Testing Across Tenant Boundaries — Proving isolation holds
- Tenant Isolation and Noisy Neighbor Testing — Testing fairness and blast radius
- Database Sharding — Spreading tenants across databases
- Rate Limiting — Per-tenant limits and quotas
- Kubernetes Multi-Tenant Isolation — Tenant isolation in Kubernetes
- Quotas, Fairness, and Backpressure — Per-tenant quotas and fairness
- Designing a Multi-Tenant SaaS System — A worked end-to-end SaaS design