Multi-Tenant on Day One, or Not at All

Almost every B2B product eventually serves more than one organisation. Very few are built that way from the start, because the first customer is right there and the second is hypothetical. This is a reasonable-sounding decision that turns into one of the most expensive migrations in software.
The reason it is so expensive is that single-tenancy is not a feature you can point at and remove. It is an assumption that has quietly spread into every query, every background job, every cache key and every export. Undoing it means auditing all of them, and being wrong once means one customer sees another customer's data.
The cheap version costs almost nothing
Building for multiple tenants on day one does not mean building a control plane, per-tenant clusters, or configurable everything. It means one discipline: every row that belongs to a customer carries a tenant identifier, and no query is ever written without it.
On day one that is a column and a habit. Three years later it is the difference between a routine feature and a six-month remediation programme.
Enforce it below the application
A convention that every query must filter by tenant will hold for about as long as the team stays the same size. The moment someone new writes a reasonable-looking query without the filter, the convention has failed, and nothing will tell you.
Push the guarantee down a layer. PostgreSQL row-level security enforces it in the database, where a forgotten clause returns nothing rather than everything. Where that does not fit, a repository layer that takes tenant context as a required argument gets you most of the way. The principle is the same: make the unsafe query hard to write, rather than trusting everyone to remember.
The places isolation quietly leaks
Application queries are the part everyone remembers. These are the parts they do not:
- Background jobs, which usually run with elevated permissions and no request context to inherit a tenant from.
- Caches, where a key missing the tenant prefix will happily serve one organisation's data to another.
- Full-text search indexes, which are frequently built as one global index because that was simpler at the time.
- File storage, where predictable object paths let anyone who guesses a URL read someone else's document.
- Exports, reports and webhooks — everything that leaves the system, which is exactly where a leak is least recoverable.
Shared schema until proven otherwise
A separate database per tenant sounds like the safest option, and for a handful of large enterprise customers with hard regulatory boundaries it can be. It also multiplies your migration surface by the number of tenants, which is fine at five and miserable at three hundred.
Start with a shared schema and enforced row-level isolation. Keep the option of promoting an individual tenant to its own database when a contract genuinely requires it. What you must not do is start shared, skip the enforcement, and discover at customer forty that you have neither isolation nor a way to add it.



