Most architectural decisions in a SaaS product are reversible. You can swap a queue, change a framework, restructure a service, move a database — expensive, disruptive, but possible.
A few are not. They harden the moment real customers hold real data, because changing them means migrating live tenants with no acceptable downtime, and every integration built against the old shape breaks.
These are those decisions. There are fewer than people expect, and they are worth a week of thought at the start.
What a tenant is
Deceptively simple, and the source of more painful migrations than anything else on this list. Is a tenant a company, a team, or a user?
Answer "a user", the fastest thing to build, and you will eventually be asked for teams. Now every record in the system belongs to an individual, and you have to invent an owning entity above them and reassign everything, while people are using it.
The related question is whether a user can belong to more than one tenant. If the answer might ever be yes, the relationship has to be many-to-many from the start. Converting a one-to-many into a many-to-many after launch touches every query in the application.
Our default recommendation is to model an explicit organisation from day one, with users as members, even when the launch product has exactly one member per organisation. It costs very little now. The alternative costs a migration.
How tenants are isolated
Three broad models: a shared database with a tenant column on every row; a schema per tenant; a database per tenant.
Shared with a tenant column is where most products should start: simplest to build, cheapest to run, easiest to query across tenants for your own analytics. It has one non-negotiable condition: isolation has to be enforced somewhere it cannot be forgotten. Not by remembering to add a WHERE tenant_id = ? to each query. One forgotten clause in one endpoint is a cross-tenant data leak, and it will be found by a customer rather than by you.
So enforce it below the query: row-level security in the database, or a data-access layer that physically cannot construct a query without a tenant scope. This is the single highest-value piece of engineering discipline in a multi-tenant product.
Move to heavier isolation when there is a reason, a regulatory requirement, data residency, an enterprise customer contractually demanding it, or one tenant large enough to need its own resources. Starting there without a reason buys operational complexity you will pay for on every deploy and every migration.
What your identifiers look like
Sequential integer IDs leak information. /invoices/1043 tells any customer roughly how many invoices your entire platform has issued, and their neighbours' IDs are 1042 and 1044, which turns a missing authorisation check into trivially exploitable enumeration.
Use non-sequential identifiers in anything externally visible. Changing this later means every URL, every API response, every integration a customer has built, and every support document. It is a fifteen-minute decision now and a project later.
Where you enforce permissions
Not what the permission model is, that can evolve. Where it is enforced.
Enforced in the interface, it is decoration: the API is the real surface, and anyone can call it directly. Enforced in each endpoint, it will be inconsistent, because someone will add an endpoint in a hurry.
Enforced in the data-access layer, below anything that queries, it holds. Same principle as tenant isolation, and ideally the same mechanism.
This matters more than it used to. Retrieval systems and internal AI features query broadly by design — an assistant that answers questions across a customer's documents will happily surface one the user should never see if the only permission check lived in a screen. That is a breach, not a bug, and it is the failure mode we most often find in AI features bolted onto existing SaaS.
Whether you can answer "who changed this?"
Every B2B product is eventually asked. By a customer investigating a mistake, by a security review, by a compliance process during a large deal.
An audit log has to be written as things happen. It cannot be reconstructed, because the information was never recorded. Adding one on day one is a table and a hook. Adding one in year two gives you history that starts in year two, which is exactly when someone asks about year one.
Record actor, action, target, before and after, timestamp, tenant. Boring, small, and the thing large customers ask about before they sign.
Where the tenant lives in your URLs
Path (/t/acme/...), subdomain (acme.yourapp.com), or implicit in the session. This looks cosmetic and is not: it determines whether a user in two tenants can be in both at once in different tabs, how sessions and cookies scope, whether custom domains are possible later, and how single sign-on will work.
Implicit-in-session is the easy start and the one that blocks the most later. Changing it invalidates every bookmark and every link a customer has shared internally.
The test for this list: if changing it means emailing customers to apologise, decide it now.
Whether background work knows which tenant it is for
Tenant isolation is usually enforced carefully on the request path, where there is a logged-in user to derive scope from, and then quietly forgotten everywhere else. Background jobs, scheduled tasks, webhook handlers, export routines and admin tooling all run without a user session, and every one of them queries your data.
This is where cross-tenant leaks happen in practice. A nightly job that aggregates across all tenants and emails each one their summary is one off-by-one away from sending the wrong customer's numbers to the wrong customer.
So the tenant has to be part of the job payload, not inferred, and the same enforcement that protects request-path queries has to apply to workers. If your isolation lives in a request middleware, it does not exist for any of the work above. Deciding this early is easy; discovering it during a security review, after eighteen months of jobs written on the assumption, is not.
Whether you can extract or delete one tenant
Two requests arrive at every B2B product eventually. A customer wants their data out, and a customer wants their data deleted. Both are frequently contractual, and in several jurisdictions deletion is a legal obligation with a deadline attached.
Both are straightforward if the data model has a clear tenant boundary and every record's owner is unambiguous. Both are miserable if tenant data is spread across tables without consistent ownership, denormalised into shared aggregates, or entangled in a way that makes "everything belonging to this customer" a query nobody can write with confidence.
The design decision is upstream of the feature: can you enumerate everything one tenant owns? If you cannot answer that with a query, you cannot export and you cannot delete, and you will find out when a deadline is already running. Note that this includes the places people forget, uploaded files, cached derivatives, search indexes, analytics events, and logs.
What you can comfortably defer
Equally worth saying, because over-engineering the start is its own failure:
- Billing tiers and plan logic, swappable, and you do not know your plans yet.
- Microservices, a well-structured single application is easier to split later than a premature split is to merge.
- Caching layers, search infrastructure, read replicas, all addable when a real bottleneck appears.
- Custom domains and white-labelling — deferrable, provided the tenant is already explicit in your URLs rather than implicit in the session.
- The specific permission model, as long as where it is enforced is right.
- Most of your infrastructure choices.
The pattern is consistent: things that shape the data model, identity, and isolation are hard to undo. Things that shape performance and deployment are not.
A useful way to apply that test to anything not on this list is to ask what the change would require once you have a thousand paying accounts. If the answer is a deploy, defer it without guilt. If the answer is a data migration that has to run without downtime, plus a note to every customer whose integration breaks, it belongs in the first week rather than the second year.
A short review
For anyone mid-build, these eight questions take an afternoon and are worth it:
- Is there an explicit organisation entity, even if every organisation has one member today?
- Can a user belong to two organisations without a schema change?
- Can any query in the codebase be written without a tenant scope? If yes, that is the finding.
- Are externally visible IDs non-sequential?
- Are permissions enforced below the query layer, not in the interface?
- Is there an audit log recording who changed what, today?
- Do background jobs and webhook handlers carry a tenant, and is isolation enforced for them too?
- Can you enumerate everything one tenant owns: files, indexes and logs included, in a single query?
Any "no" is cheap now and expensive after your first significant customer. It is the review we run at the start of SaaS development engagements, and the reason the first week is spent on the data model rather than on screens. The same argument as scoping an MVP, applied to architecture.
Keep reading
Why Staffing Juniors on Your MVP Costs More
A cheaper day rate is not a cheaper project. What actually drives cost on a small build, and why it is decisions rather than hours.
App Store Optimisation in the AI-Search Era
Store search still works the way it always did. What changed is everything upstream of it — and most teams are still optimising only the last step.