Blog
How to Build a Multi-Tenant SaaS Application
PrimeTkAugust 14, 20265 min read
Shared schema vs schema-per-tenant vs database-per-tenant — what PlanetScale, ClickHouse engineering notes, and teams shipping in 2026 actually recommend.
Blog
PrimeTkAugust 14, 20265 min read
Shared schema vs schema-per-tenant vs database-per-tenant — what PlanetScale, ClickHouse engineering notes, and teams shipping in 2026 actually recommend.
The first multi-tenant bug I ever caused wasn’t dramatic. No breach headline. Just a support ticket: “Why am I seeing Acme Corp’s invoices?”
One missing WHERE tenant_id = ? in a report query. Shipped on a Friday. Found on a Monday. The kind of mistake that makes your stomach drop even when you catch it early.
That’s the whole topic in miniature. Multi-tenancy isn’t a framework checkbox. It’s a promise that customer A never sees customer B’s stuff — including when someone is tired, rushing, or “just testing in prod.”
The best practical write-ups on this (PlanetScale’s Postgres guide on Medium, Navanath Jadhav’s “database design nobody talks about,” ClickHouse’s 2026 architecture notes) keep landing in the same place: start shared, isolate when someone pays for isolation. Fashionable complexity early is how you spend a year operating a database zoo for twelve customers.
tenant_id on rows)Everyone shares tables. Every row carries a tenant key. Queries filter on it. Indexes usually lead with (tenant_id, …).
This is the right default for most B2B SaaS. Lowest ops cost. One migration runs once. Scales to a lot of tenants before you seriously talk about sharding. PlanetScale puts it bluntly: this is the only approach that’s genuinely multi-tenant at the table level — schema-per-tenant and database-per-tenant mostly share machines, not tables.
The risk is obvious: forget the filter once and you’ve got a leak. So you don’t rely on “remembering.” Use an ORM default scope and Postgres Row Level Security as a backstop. RLS won’t invent the tenant for you — your app still has to set it on the connection — but it stops the classic footgun where a raw query returns the whole table because someone wrote a one-off script.
If you’re early, shared schema feels almost too simple. That’s usually a good sign.
One database, schema tenant_abc, schema tenant_def, duplicated tables everywhere.
It feels safer in a design review. Migrations become a loop over N schemas. Catalog bloat gets weird past hundreds or thousands of tenants. Most teams who pick this for “clean isolation” later wish they’d stayed on shared schema with RLS and better tests.
Reach for schema-per-tenant when a regulated niche needs logical separation and you’re still under a manageable tenant count — not because it sounded tidy in a kickoff. I’ve inherited systems with 400 schemas and a migration script that took longer than the feature it was deploying.
Maximum blast-radius control. Per-tenant backup and restore is easy. Connection pooling and idle cost get expensive. You can’t casually run analytics across customers without a warehouse story.
Use this when a contract, regulator, or whale literally requires it — or when one tenant’s load is melting everyone else. Don’t use it because it sounded safe in a pitch deck for tenant number four.
The session knows the org. Auth isn’t just “user logged in.” It’s user-in-tenant, or a user with an explicit tenant list. Magic links into a floating workspace with no org are how quiet leaks start. We make org creation part of sign-up even when there’s only one org for months.
Files and jobs are tenant-aware. S3 keys, email queues, background workers — leaks hide there after the SQL looks clean. If a PDF export doesn’t include tenant in the path and the auth check, assume someone will guess a URL eventually.
Admin tools are the danger zone. “View as customer” needs an audit trail. Support shouldn’t get a writable production console without a reason and a log line. Most serious incidents I’ve seen weren’t public APIs — they were internal tools with god mode.
You can export one tenant. If you can’t dump their data without everyone else’s, you don’t have tenancy. You have a shared spreadsheet with aspirations. Enterprise buyers will ask. Regulators might ask. Your future self will definitely ask when a customer churns and wants their data out.
Seats and usage attach to the tenant, not the person who happened to click Upgrade. Personal Stripe customers with five forgotten team workspaces are how finance and product stop speaking to each other.
You don’t need fancy metering on day one. You do need a clear owner for the subscription. A lot of B2B products eventually land on hybrid pricing — platform fee plus usage on the scarce dimension. Design toward that instead of inventing a custom SKU for every sales call. Custom SKUs feel flexible until finance closes the month.
tenant_id everywhere, tests that fail if a query drops the filterHybrid is normal at scale: long-tail tenants share; enterprise tenants get a silo. Route in middleware by plan. Don’t build the silo path before you have a customer asking for it in writing — preferably with a purchase order attached.
Ask the founding team three questions:
If the answers are “probably later,” “whole DB is fine,” and “the company,” you almost certainly want shared schema. Everything else is optimization for a future that might never arrive.
If you’re still picking a database, start with Postgres unless you can name a document-shaped problem — PostgreSQL vs MongoDB for SaaS. And keep the product thin enough that tenancy is a property of the core job, not a parallel platform rewrite: SaaS MVP guide.
Keep reading
We'll pick an isolation model that matches your buyers — not a conference-talk ideal.