If you’re building B2B SaaS, multi-tenancy is going to happen. Your first customer has one organization; your tenth wants to invite 50 colleagues into a shared workspace. You can architect for this from the start, or you can architect for it at 3am when a customer reports seeing another customer’s data.
What “tenant-aware from day one” means
- A
tenants(ororganizations) table. Every tenant is a row. - Every non-lookup table has a
tenant_idforeign key. - Every query — every single one — filters by
tenant_id. - Users belong to tenants via a join table with a role.
- The current user’s session knows their active tenant.
- Integration tests seed at least two tenants and verify isolation on every endpoint.
That’s it. The schema and conventions are the architecture.
The retrofit trap
Teams that skip multi-tenancy at the start usually discover the need around customer 20-50. By then:
- Every query in the codebase has no tenant filter
- Half the endpoints assume a single “current user” with no tenant context
- The frontend has hardcoded “my account” semantics
The retrofit looks like this: add tenant_id columns everywhere, add it to every query, update every endpoint’s auth check, update the frontend navigation model. Expected: 2 weeks. Actual: 3 months. And during those 3 months, every missed query is a cross-tenant data leak — a security bug that can end customer relationships.
How to do it lightweight
You don’t need to ship “enterprise multi-tenancy” on day one. You need the plumbing in place so adding it later is incremental.
Minimum viable multi-tenant:
tenantstable with one row per customer (auto-created on signup, named after the user)membershipstable:(tenant_id, user_id, role)- Every API endpoint selects the user’s current tenant (from session) and filters queries by it
- No endpoint trusts a
tenantIdpassed from the client — always derive from session
At signup, a single-user tenant is created with the user as admin. From the user’s perspective, nothing changes. From your perspective, when the customer says “I want to invite my team,” you’re ready.
The database-level guarantee
For extra safety, enable Postgres row-level security (RLS). Every SELECT from an authenticated connection has an implicit WHERE tenant_id = current_setting('app.current_tenant') added. If your application code forgets a filter, the DB enforces it.
RLS is a bit of learning curve but pays for itself the first time a junior dev writes a query without the tenant filter. The DB refuses to return anything.
The bottom line
Multi-tenant from day one adds maybe 5% to your initial schema design time. Retrofitting costs 10x that, plus the security risk. The decision is which side of the trade to be on — and for B2B, the answer is always “do it now.”