# Multi-Tenant SaaS Architecture: 7 Decisions That Are Expensive to Change Later

> How to build a multi-tenant SaaS: shared tables or schema per tenant, row-level security, tenant-aware jobs and billing, from a codebase serving 17 projects.

Source: https://eaquirstech.com/en/blog/multi-tenant-saas-architecture · Eaquirs Tech, AI-native software engineering partner · Updated 2026-11-09

By Muhammad Safdar Iqbal · Published 2026-10-25 · Tags: Multi-Tenant SaaS, SaaS Development, Software Architecture, PostgreSQL

Most decisions in a SaaS product can be changed next month. A button moves, a plan gets a new price, a report gets a new column. A few can't. Get them wrong and you are rewriting queries across the whole codebase with paying customers on top of it.

This article is about those few. They come from Turner 10, a system that started in 2016 for one housing scheme in Bahawalpur and now runs 17 projects from one codebase, each in its own workspace with its own users, accounts and settings. Nobody called it SaaS at the start. It became one, and it taught us which early choices matter.

If you are planning a [multi-tenant SaaS](/services/saas-development), these are the seven to settle before the first sprint.

### 1. What exactly is a tenant?

It sounds like a formality. It isn't.

In a CRM, a tenant is usually a company. In Turner 10 it's a project: a housing scheme, a sub-sector, a head office. One developer can own several projects. So "tenant" and "customer" are not necessarily the same thing.

Decide early:

- Is the tenant the company that pays, or a unit inside it (a branch, a project, a clinic)?
- Can one user belong to several tenants? With a different role in each?
- Does anything belong to no tenant at all, such as shared reference data or a global product catalogue?

Every table in your database will carry the answer, so it's worth an afternoon of argument now.

### 2. Shared tables, a schema per tenant, or a database per tenant?

This is the decision people ask about most, and there are three honest answers.

| Approach | Good for | The catch |
|---|---|---|
| Shared tables with a `tenant_id` column | Most B2B SaaS. Cheapest to run, one migration updates everyone | Every query must filter by tenant. One missed filter is a data leak |
| A schema per tenant | Customers who want their data kept apart without paying for a server | Migrations run once per tenant, so you need tooling from day one |
| A database per tenant | Hospitals, finance, contracts that demand it | Highest running cost; onboarding and upgrades must be automated |

For most products we start with shared tables and enforce isolation in two places, which is the next decision. If a large customer later needs separate storage, a well-structured shared model can move that one customer to its own schema or database without rewriting the product. Going the other way, from a database per tenant back to shared tables, is much harder.

### 3. Where is isolation enforced?

"Every query filters by tenant" is a promise developers make and code eventually breaks. A report written in a hurry, a background job, an admin endpoint: one of them will forget.

So isolation should be enforced in places that don't depend on every developer remembering:

**In the data layer.** Queries go through a repository or ORM layer that adds the tenant filter automatically, so a developer has to go out of their way to skip it.

**In the database itself.** PostgreSQL's row-level security can refuse to return another tenant's rows even if the application forgets to ask:

```sql
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON invoices
  USING (tenant_id = current_setting('app.tenant_id')::uuid);
```

The application sets `app.tenant_id` at the start of each request, from the authenticated session, and the database does the rest. It's the safety net under the safety net.

**Never in the browser.** The tenant comes from the server-side session, not from a URL parameter or a field the client sends. If a user can change a number in the address bar and see another company's invoices, isolation was never real.

In Turner 10, access is re-checked on the server on every call, and a user only ever sees the modules their role allows in the project they're working in.

### 4. Are background jobs tenant-aware?

This is where good multi-tenant apps leak most quietly.

A web request has a logged-in user and a tenant. A background job usually doesn't. The nightly job that sends payment reminders, the import that processes an uploaded spreadsheet, the report emailed every Monday: each of them runs outside a request, and each one has to be told which tenant it belongs to.

The rule we follow: every job carries its tenant ID as part of its payload, sets the tenant context before it touches the database, and is tested with two tenants' data present at once. The bug you're looking for is the reminder that goes to the right customer list in the wrong company.

### 5. How do roles work inside a tenant?

Two levels of permission get mixed up all the time.

The first is **tenant isolation**: company A never sees company B. That's architecture, and it should be impossible to get wrong.

The second is **roles inside a tenant**: in company A, the accountant sees the books, the sales officer sees the leads, the owner sees everything. That's configuration, and it changes as customers grow.

Keep them separate in the code. Turner 10 has thirteen modules in five groups, and each role opens only the modules it needs, re-checked on every request. Customers ask for new roles constantly. If roles are tangled with isolation, every new role is a security review. If they're separate, it's a settings change.

### 6. How does billing know what a tenant is using?

Billing looks like a feature you add before launch. It's really a data model decision.

- **What do you charge for?** Seats, projects, transactions, storage? Whatever it is, the system has to count it reliably per tenant.
- **What happens at the limit?** A hard stop, a warning, an automatic upgrade? Each needs a different code path.
- **What happens when a payment fails?** Retries, a grace period, read-only mode, then suspension. Write it down before your first failed card, not after.

And make payments idempotent from the first day. On [OpenInterview.me](/work/openinterview-hiring-platform), the Stripe flow records each payment exactly once, even if a customer double-clicks or Stripe delivers the same webhook twice, which Stripe says it may do. Retrofitting that after real money has been double-recorded is a miserable week.

### 7. Where does each tenant's data live, and in what language?

Two questions that sound like later problems and aren't.

**Data residency.** A customer in the UK may need their data in a UK region. A hospital may need it on its own server. If your model can't place one tenant's data somewhere specific, you will lose those deals. You don't need to build it on day one, but you shouldn't build anything that makes it impossible.

**Language and direction.** If you'll ever sell in the Gulf or Pakistan, Arabic and Urdu mean right-to-left layouts, different fonts and different number formats. Turner 10 runs in English, Urdu and Arabic on every screen. Designed in from the start, that's a modest cost. Retrofitted, it touches nearly every component.

### What it costs to get this right

Less than people expect, if it's decided early. Our [cost estimator](/estimate/saas-mvp) puts a typical SaaS MVP at $12,500 to $20,500 over six to nine weeks, and multi-tenancy adds roughly 60 hours to a traditional build before the AI saving. The expensive version is the one where tenancy is bolted on after launch, because by then every query, job and report has been written without it.

We build with a team of AI agents and senior engineers, and this is exactly the split that makes sense: agents are fast at the repetitive part of multi-tenant code, the tenant filters on every repository, the tests that try to read across tenants, the migrations. The seven decisions above are the part a person has to own.

### A checklist before your first sprint

- [ ] We've written down what a tenant is, and whether a user can belong to several.
- [ ] We've chosen shared tables, schema per tenant or database per tenant, and know how one customer could move later.
- [ ] Tenant isolation is enforced in the data layer and in the database, never by the browser.
- [ ] Every background job carries its tenant and is tested with two tenants present.
- [ ] Roles inside a tenant are separate from isolation between tenants.
- [ ] We know what we bill for, how it's counted per tenant, and what happens when a payment fails.
- [ ] Nothing in the design stops us placing one tenant's data in a specific region or adding a right-to-left language.

These decisions drive the budget, so read [MVP development cost in 2026](/en/blog/mvp-development-cost) next. If the same system needs a mobile app, [Flutter vs React Native](/en/blog/flutter-vs-react-native) covers the choice, and [custom ERP vs Odoo vs ERPNext](/en/blog/custom-erp-vs-odoo-erpnext) explains when a multi-tenant ERP is worth building yourself.

### Common questions

**What is multi-tenant SaaS architecture?** A way of building one software product that serves many customers (tenants) from the same codebase and infrastructure, while keeping each customer's data, users and settings separate from everyone else's.

**Is row-level security enough to isolate tenants?** It's an excellent safety net, but don't make it the only layer. Enforce the tenant in your data access layer too, and always take the tenant from the server-side session.

**Should I start single-tenant and switch later?** Usually not. Moving from one database per customer to shared tables is harder than the other way round. Start shared, with strict isolation, unless a contract requires otherwise.

**How long does it take to build a multi-tenant SaaS MVP?** Typically six to nine weeks with AI-assisted delivery for a focused first version, with a working demo every week.

If you're planning a SaaS and want a second opinion on these decisions, [get a free project plan](/#contact). We'll tell you what we'd decide, and why, before you write a line of code.

## Contact

- Free written project plan: https://eaquirstech.com/en#contact
- Book a free 30-minute call: https://eaquirstech.com/en/book
- Cost and timeline estimate: https://eaquirstech.com/en/estimate
- WhatsApp: https://wa.me/923322060667 (+92 332 2060667)
- Email: teams@eaquirstech.com

More: https://eaquirstech.com/llms.txt (site index for AI) · https://eaquirstech.com/llms-full.txt (full profile) · https://eaquirstech.com/sitemap.xml
