SaaS4 min read
Choosing a tenancy model for a multi-tenant SaaS product
Shared database, schema per tenant or database per tenant? A practical comparison of SaaS tenancy models by isolation, cost and migrations.
By Xyoraa Engineering
Every SaaS product serves many customers from one codebase. The decision that shapes the rest of the architecture is how those customers' data is separated. Change it late and you are rewriting queries, migrations, backups and billing all at once.
There are three common models. None is universally right; each trades isolation against cost and operational effort.
The three tenancy models
Shared database, shared schema. Every table has a tenant identifier column, such as organization_id, and every query filters on it. One database, one set of migrations, one backup.
Shared database, schema per tenant. Each customer gets its own schema (or table prefix) inside one database server. Queries do not need a tenant filter, but migrations must run once per tenant.
Database per tenant. Each customer has a separate database. Isolation is strongest, but connection management, migrations, monitoring and backups multiply with every customer you sign.
How they compare
| Shared schema | Schema per tenant | Database per tenant | |
|---|---|---|---|
| Isolation | Logical, enforced in code | Stronger, enforced by schema | Strongest, enforced by the database |
| Cost per tenant | Lowest | Low to medium | Highest |
| Migrations | Run once | Run per tenant | Run per tenant |
| Cross-tenant reporting | Straightforward | Harder | Hardest |
| Per-tenant restore | Hard | Moderate | Easy |
| Best fit | Many small and mid-size tenants | Moderate tenant counts | Few large or regulated tenants |
Why most products should start with a shared schema
For a new B2B product with many small customers, the shared-schema model is usually the right starting point. It is the cheapest to operate, the simplest to migrate, and makes platform-wide reporting easy.
Its risk is equally clear: isolation depends entirely on your code never forgetting the tenant filter. That risk is manageable if scoping is enforced by the framework rather than remembered by developers:
- Resolve the tenant once per request, from a subdomain, a header or the authenticated user, and store it in request context.
- Apply the filter automatically. In Laravel, a global scope on a base tenant model adds the
organization_idcondition to every query; other frameworks have equivalent query hooks. - Set the tenant on writes automatically, so new records can never be created without an owner.
- Test for leaks explicitly. Write tests that authenticate as one tenant and try to read, update and delete another tenant's records by ID. These tests catch the mistakes that code review misses.
Our own school management platform uses this approach: a single database with organisation-level scoping, where each school group is a tenant and branches inside it keep their own data and permissions.
Hierarchies inside a tenant
Real customers are rarely flat. A school group has branches; a retailer has stores; an agency has client accounts. Model this hierarchy explicitly rather than treating each branch as a separate tenant:
- The tenant is the paying organisation that owns billing and global settings.
- Sub-units such as branches carry their own data boundary inside the tenant.
- Roles and permissions are granted per sub-unit, so a branch administrator cannot see another branch unless given access.
Getting this right early avoids a painful migration when your first multi-location customer signs up.
Per-tenant configuration and feature flags
Tenants differ in the modules they pay for, their limits and their preferences. Keep this in data, not in code branches:
- A plan record defines limits (users, storage, messages) and included features.
- A tenant settings record stores preferences and enabled modules.
- A single gate function — "can this tenant use this feature?" — is checked by both the API and the interface.
This keeps pricing changes a configuration task rather than a deployment.
Keeping your options open
Starting with a shared schema does not lock you in. A few habits keep the path to stronger isolation open:
- Route every database access through the tenant-aware layer, never raw connections scattered through the code.
- Avoid foreign keys and joins that cross tenant boundaries, except for genuinely global reference data.
- Make tenant data exportable, so a large customer can later be moved to a dedicated database.
- Keep audit logs of sensitive actions per tenant from day one.
A short decision checklist
- Do customers contractually require physical separation of data? Consider database per tenant for those customers.
- Will you have hundreds or thousands of small tenants? Start with a shared schema.
- Do you need platform-wide analytics? A shared schema makes it far simpler.
- Must you restore one customer's data without touching others? Plan per-tenant export and restore early, whichever model you choose.
The best tenancy model is the one your team can operate reliably today, with a clear path to stronger isolation for the customers who need it later.
- SaaS
- Architecture
- Multi-tenancy

