NavTech Solution is currently accepting new Next.js and AI development projects.Get in touch

SaaSJune 15, 2026 · 4 min read

Designing Multi-Tenant SaaS Data Models Before You Need Them

The tenant-isolation decisions that are cheap to make early and expensive to retrofit, and how to choose between them based on your actual compliance and scale requirements.

By NavTech Solution

Almost every SaaS product starts single-tenant, because the first version only has one customer: whoever's testing it. The trouble starts when customer two signs up and the data model was never designed to keep two organizations' data apart.

The three tenancy models

There isn't one correct way to isolate tenants — there are three common approaches, and the right one depends on your compliance requirements and scale, not on which is "more correct" in the abstract.

Shared database, shared schema, tenant ID column. Every table gets a tenant_id column, and every query filters by it. Cheapest to build and operate, and the right default for most B2B SaaS products.

SELECT * FROM invoices WHERE tenant_id = $1 AND id = $2;

The risk here isn't the model — it's forgetting the filter. A single query missing WHERE tenant_id = $1 is a cross-tenant data leak. This is why isolation should live in a shared data-access layer, not be re-typed at every call site.

Shared database, separate schema per tenant. Stronger isolation than a shared schema, still shares infrastructure. Useful when tenants need customized fields or reporting that would be awkward in a single shared table, without the operational cost of fully separate databases.

Separate database per tenant. The strongest isolation, and the most expensive to operate — migrations run per tenant, connection pooling gets more complex, and cross-tenant analytics require a separate pipeline. This is usually reserved for enterprise customers with specific compliance requirements (data residency, contractual data-separation clauses) that a shared schema can't satisfy, not applied to every tenant by default.

Most products don't need to choose one model forever — a common pattern is shared schema for standard plans, with a path to a dedicated database for enterprise tenants who require it.

Enforce isolation in one place

Whichever model you choose, the isolation check should not be something every engineer remembers to add to every query by hand. Centralize it:

function tenantScoped(tenantId: string) {
  return {
    invoices: {
      findMany: () => db.invoice.findMany({ where: { tenantId } }),
      findById: (id: string) => db.invoice.findFirst({ where: { tenantId, id } }),
    },
  };
}

Every data-access call goes through a tenant-scoped client instantiated once per request, from the authenticated session — not passed around as a parameter that a new engineer might forget six months from now. This is the single highest-leverage decision in multi-tenant architecture: make the unsafe query (the one missing a tenant filter) structurally impossible to write, rather than relying on code review to catch it.

Authorization is a second, separate layer

Tenant isolation answers "which organization does this data belong to." Authorization answers "can this specific user, within that organization, do this specific thing." They're both necessary and neither substitutes for the other — a user correctly scoped to the right tenant can still not be allowed to delete another user's records within that same tenant.

Centralize this too, ideally as a single policy layer checked before any mutation runs, rather than scattered if (user.role === "admin") checks copy-pasted across routes. When that logic is duplicated, it drifts — someone adds a new route and forgets the check that exists everywhere else.

Billing changes the data model, not just the invoice

Subscription and usage-based billing aren't a bolt-on integration — they affect the schema. Plan tier needs to be queryable efficiently (you'll check it on nearly every feature-gated request), usage counters need to be resettable per billing period without losing historical data, and plan changes mid-cycle need proration logic that has to reconcile with your billing provider's own state, not just your local database's.

Treat billing as core product architecture from the start, not a Stripe webhook handler added after the schema is already frozen. Retrofitting plan-aware queries onto a schema that didn't anticipate them is one of the more expensive migrations we get asked to do.

Don't over-build for scale you don't have yet

None of this means every new SaaS product needs separate databases per tenant and a bespoke authorization engine on day one. A shared-schema model with a centralized tenant-scoped data layer and a straightforward role check covers the overwhelming majority of B2B products well past their first hundred customers. Build the isolation boundary correctly from the start; add the more expensive isolation models only when a specific customer's compliance requirement actually demands it.

This is exactly the kind of architecture decision we make explicit — in writing, before the first migration — on every SaaS development engagement, and it's the same discipline that shows up in Next.js data-fetching patterns once the tenant-scoped data layer needs to reach your Server Components.

SaaSArchitectureMulti-tenancy