
Multi-tenancy — serving multiple customers (tenants) from a single deployed application — is the defining architectural characteristic of most SaaS products. Getting the tenancy model right early is critical, because changing it later is one of the most disruptive migrations a SaaS team can face.
This guide covers the three primary tenancy models, their tradeoffs, and the implementation patterns required to build a secure, scalable multi-tenant application in 2026.
1. The Three Tenancy Models
Multi-tenant SaaS architectures fall into three primary data isolation models, each with distinct tradeoffs around cost, isolation, and operational complexity:
| Model | How It Works | Best For | Main Tradeoff |
|---|---|---|---|
| Shared Database, Shared Schema | All tenants share the same tables. Data separated by tenant_id column. | High-density SMB SaaS, cost-sensitive products | Weakest isolation; noisy neighbor risk |
| Shared Database, Schema-per-Tenant | One database, separate PostgreSQL schemas per tenant. Tables prefixed by schema name. | Mid-market SaaS needing better isolation | Schema proliferation at scale; more complex migrations |
| Database-per-Tenant | Each tenant has a completely separate database instance or cluster. | Enterprise SaaS, regulated industries, high-value accounts | Highest cost; operational complexity; cross-tenant analytics harder |
Many successful SaaS products use a hybrid approach — shared schema for standard plan customers (the majority), schema-per-tenant for professional tier, and database-per-tenant for enterprise contracts with compliance or data residency requirements.
2. Shared Schema Model — Implementation
The shared schema model is the most common starting point for SaaS products. Every table includes a tenant_id column, and all queries are scoped to the authenticated tenant.
A. Database Schema Design
-- Every table includes tenant_id as part of primary key or index
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, id)
);
-- Index on tenant_id is essential for query performance
CREATE INDEX idx_organizations_tenant ON organizations(tenant_id);
-- All queries MUST include tenant_id filter
SELECT * FROM organizations WHERE tenant_id = $1 AND id = $2;
B. Row-Level Security (RLS) in PostgreSQL
PostgreSQL's Row-Level Security enforces tenant isolation at the database level — even if application code forgets to include a tenant filter, the database will not return cross-tenant data:
-- Enable RLS on all multi-tenant tables
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
-- Create policy: users can only see rows matching their tenant context
CREATE POLICY tenant_isolation ON organizations
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- In your connection setup (Node.js example):
await db.query("SET app.tenant_id = $1", [req.tenant.id]);
RLS provides a defense-in-depth guarantee — it is a critical safety net for any shared-schema multi-tenant application.
3. Tenant Routing — Identifying the Tenant from a Request
Before any business logic executes, the application must identify which tenant the request belongs to. Common routing strategies:
A. Subdomain Routing
// tenant.app.com — extract tenant from subdomain
// In Next.js middleware:
export function middleware(req: NextRequest) {
const hostname = req.headers.get('host') ?? '';
const subdomain = hostname.split('.')[0]; // 'acme' from 'acme.app.com'
// Look up tenant by subdomain, inject into request headers
const tenantId = await resolveTenantBySubdomain(subdomain);
const requestHeaders = new Headers(req.headers);
requestHeaders.set('x-tenant-id', tenantId);
return NextResponse.next({ request: { headers: requestHeaders } });
}
B. Path-Based Routing
app.com/acme/dashboard — the tenant slug is the first path segment. Simpler to implement (no wildcard DNS needed) but less clean UX and makes white-labeling harder.
C. Token-Based (API)
For API-first products, the tenant is resolved from the API key or JWT claim. The tenant_id is embedded in the token at issuance time and extracted at the auth middleware layer.
4. Authentication & Role-Based Access Control (RBAC)
Multi-tenant SaaS products typically require two levels of access control:
- Tenant isolation: Users can only access their own tenant's data (enforced via
tenant_idscoping and RLS) - Role-based permissions: Within a tenant, different users have different permissions (Owner, Admin, Editor, Viewer)
// Example RBAC model
type Role = 'owner' | 'admin' | 'editor' | 'viewer';
interface TenantMembership {
tenantId: string;
userId: string;
role: Role;
}
// Permission check helper
function can(role: Role, action: string): boolean {
const permissions: Record = {
owner: ['read', 'write', 'delete', 'manage_billing', 'manage_members'],
admin: ['read', 'write', 'delete', 'manage_members'],
editor: ['read', 'write'],
viewer: ['read'],
};
return permissions[role]?.includes(action) ?? false;
}
5. Feature Flags by Subscription Plan
SaaS products commonly gate features behind subscription tiers (Starter, Pro, Enterprise). Feature flags scoped per tenant and plan enable this without complex conditional logic scattered through the codebase:
// features.ts
const PLAN_FEATURES: Record = {
starter: ['basic_reports', 'email_support'],
pro: ['basic_reports', 'advanced_reports', 'api_access', 'priority_support'],
enterprise: ['basic_reports', 'advanced_reports', 'api_access', 'sso', 'audit_logs', 'dedicated_support'],
};
export function hasFeature(tenant: Tenant, feature: string): boolean {
return PLAN_FEATURES[tenant.plan]?.includes(feature) ?? false;
}
// Usage in API handler:
if (!hasFeature(req.tenant, 'api_access')) {
return res.status(403).json({ error: 'Upgrade to Pro to use the API' });
}
6. Usage Metering & Billing Integration
Usage-based billing (charging per seat, per API call, per GB stored) requires tracking usage per tenant accurately and passing it to a billing provider. Stripe Billing with Stripe Metered Billing is the standard integration for SaaS metering:
- Track usage events in your database (API calls, document count, active users) with timestamps
- Aggregate usage at billing cycle end and report to Stripe via the Metered Billing API
- Consider using a dedicated metering service (OpenMeter, Lago, Amberflo) for high-volume usage tracking that doesn't impact your main database
7. Database Migration Strategy for Multi-Tenant Apps
Running database migrations across a multi-tenant application — especially schema-per-tenant models — requires careful planning:
- Zero-downtime migrations: Use additive migration patterns — add columns with defaults rather than dropping/renaming, deploy code that works with both old and new schema, then clean up the old structure in a subsequent migration
- Schema-per-tenant migration runners: Tools like Flyway, Liquibase, or custom scripts that iterate through all tenant schemas and apply migrations in parallel
- Tenant onboarding automation: New tenant setup (create schema, run baseline migrations, seed default data) should be fully automated and tested — manual tenant provisioning doesn't scale
Building a SaaS product from scratch or re-architecting a single-tenant application for multi-tenancy? Explore our SaaS development services, see our project portfolio, or talk to our engineering team about your architecture.
Related reading:
- How Much Does Custom Software Development Cost in 2026? A Complete Pricing Guide
- AI Agents for Business: How to Automate Operations in 2026 (With Real Use Cases)
- Headless Commerce vs Traditional Ecommerce: Which Architecture Is Right for Your Brand?
- Technical SEO Checklist for 2026: 30 Checks to Get Your Site Crawled, Indexed and Ranked
- How to Build a SaaS MVP in 2026: A Step-by-Step Guide from Idea to Launch
- Generative Engine Optimization (GEO): How to Get Your Brand Cited in AI Search
- Ecommerce Platform Migration: How to Replatform Without Losing SEO Rankings
- Custom Shopify App Development (2026): Architecture, Remix & GraphQL
- Enterprise AI Automation & Agentic Workflows: Architecture & Guardrails (2026)
- Full-Stack SaaS Architecture with Next.js App Router & PostgreSQL (2026)
- Shopify to Custom Platform Migration: Architecture & Execution (2026)
- Shopify Speed Optimization Guide 2026: Core Web Vitals, LCP & Performance Best Practices
- MERN Stack Web Development Guide 2026: MongoDB, Express, React & Node.js
- API Integration Best Practices 2026: REST, GraphQL, Webhooks & Third-Party Reliability
- eCommerce Conversion Rate Optimization (CRO) Guide 2026: Tactics, Testing & Checkout
- How to Measure ROI on AI Automation: A Business Guide for 2026
- Web3 & Blockchain Development Guide 2026: Smart Contracts, dApps & DeFi
- React Performance Optimization Guide 2026: Bundle Size, Rendering & React 19
- eCommerce Email Marketing Strategy 2026: Automation Flows, Segmentation & Klaviyo
- Cloud Cost Optimization Guide 2026: AWS, GCP & Azure FinOps Strategies
Frequently asked questions
Which multi-tenant database model should I choose for my SaaS?
For most early-stage SaaS products, a shared database with shared schema (separated by tenant_id) is the right starting point — it is the simplest to implement, cheapest to operate, and easiest to migrate away from as you grow. Add PostgreSQL Row-Level Security from day one to prevent accidental cross-tenant data leakage. Move to schema-per-tenant or database-per-tenant for specific enterprise customers with compliance requirements, rather than as a starting default.
What is Row-Level Security (RLS) and why is it important for SaaS?
PostgreSQL Row-Level Security is a database-level access control mechanism that filters rows based on the current database session context (e.g., the current tenant_id). Even if application code has a bug that forgets to include a tenant_id filter in a query, RLS prevents the database from returning other tenants' data. It is an essential defense-in-depth security layer for any shared-schema multi-tenant application.
How do I handle subdomain-based tenant routing in Next.js?
Use Next.js middleware (middleware.ts at the root) to intercept all requests before they hit route handlers. Extract the subdomain from the Host header, look up the corresponding tenant record, and inject the tenant context (tenant ID, plan, etc.) into request headers. Route handlers and server components then read this context from headers rather than re-resolving the tenant on every request.
How should I implement RBAC (Role-Based Access Control) in a SaaS application?
Store a membership table with user_id, tenant_id, and role columns. Define a permission map that translates each role to a set of allowed actions. Check permissions at the API layer (not just the UI) before executing sensitive operations. For complex permission hierarchies, consider attribute-based access control (ABAC) or a dedicated authorization library like Casbin or OpenFGA.




