A pragmatic multi-tenant SaaS architecture
Single database, row-level tenancy, and a billing model that scales — the boring, reliable SaaS foundation we reach for on most builds.
There's a temptation to reach for database-per-tenant isolation on day one. For the vast majority of SaaS products, that's premature. A single database with a tenant_id on every row is simpler to operate, cheaper to run, and perfectly secure when you enforce scoping at the framework level.
Row-level tenancy
Every tenant-owned model carries a tenant_id. A global scope automatically filters every query, so application code never has to remember to add the where clause. The one place you bypass it — background jobs — gets an explicit, reviewed exception.
Billing that maps to value
We wire subscriptions to the unit a customer actually cares about: seats, projects, or usage. Stripe handles the money; our app stores the entitlement and checks it at the boundary. Webhooks keep the two in sync, with every event logged for audit.
When to split
Move a large or compliance-sensitive tenant to its own database when — and only when — the numbers demand it. Designing for that day from the start usually means paying for complexity you never use.
Row-level tenancy
Every tenant-owned model carries a tenant_id. A global scope automatically filters every query, so application code never has to remember to add the where clause. The one place you bypass it — background jobs — gets an explicit, reviewed exception.
Billing that maps to value
We wire subscriptions to the unit a customer actually cares about: seats, projects, or usage. Stripe handles the money; our app stores the entitlement and checks it at the boundary. Webhooks keep the two in sync, with every event logged for audit.
When to split
Move a large or compliance-sensitive tenant to its own database when — and only when — the numbers demand it. Designing for that day from the start usually means paying for complexity you never use.