Most products begin single-tenant without deciding to. A user signs up, they get an account, the account is theirs. Then someone wants to invite a colleague, and the colleague needs to see the same data, and the shape of the problem changes permanently.
The retrofit is painful because the tenant boundary is not one field. It is a predicate on every query, an assumption in every cache key, a scope on every session, and a column in every table you thought was global.
The cheap version of getting it right
You do not need a full organisation model on day one. You need one decision: data belongs to a tenant, and a user's access to it is a membership rather than ownership. Even if every tenant has exactly one member for the first year, the indirection is nearly free while you have no data and nearly impossible once you do.
Carry the tenant in the session rather than deriving it per request. A session that says 'this person, in this organisation, with these permissions' makes the authorisation check local and makes the wrong answer hard to write by accident.
Data belongs to a tenant. A user's access is a membership, not ownership. That is the whole decision.
What the boundary buys you later
Enterprise features assume it. A connection belongs to an organisation. A directory syncs into an organisation. A role is assigned within an organisation. An audit export is scoped to an organisation because that is what the customer is entitled to see.
If your data model has no such concept, every one of those features arrives with a migration attached, and the migrations interact.
- Tenant on every row that is not genuinely global
- Membership as its own object, with a role attached
- Tenant in the session, not recomputed per request
- Switching tenant issues a new session rather than mutating one
The failure you are avoiding
Cross-tenant leakage is the incident nobody recovers from gracefully. It is almost never a dramatic breach; it is a query somewhere that forgot a predicate, a cache key without the tenant in it, a report that joined one table too many.
Making the boundary structural — so that forgetting it fails loudly rather than quietly returning someone else's data — is worth more than any amount of care applied case by case.
Everything here, already built
Sign-in, enterprise SSO, directory provisioning, roles and an audit trail behind one API. Start with the quickstart and have a working sign-in this afternoon.