Skip to content
Hameed's dev blog

Blog

System Design: Multi-Tenant SaaS - High-Level Overview

·11 min read·---
Tree multi tenant.jpg
On this page

One application / platform serves multiple customers (tenants) while keeping their data, config, permissions, and branding completely isolated.

Your SaaS Platform serves:

  • Nike (Tenant A) → Users / Data / Config A
  • Spotify (Tenant B) → Users / Data / Config B
  • Adobe (Tenant C) → Users / Data / Config C

Each organization believes it has its own private environment. The challenge? Ensuring Nike's data never leaks to Spotify, and Spotify can never see Adobe's information.


Part 1️⃣: Multi-Tenant SaaS Fundamentals

🔴 CHECKPOINT 1: Remember

Multi-tenancy ≠ Database type

Multi-tenancy = Architecture for serving multiple tenants with isolation at many layers

This is critical. You can have a multi-tenant app with:

  • Shared Database + Shared Schema
  • Shared Database + Separate Schemas
  • Separate Database per Tenant

The database choice doesn't define whether you're multi-tenant. Multi-tenancy is about architecture.

Multi-Tenant SaaS Fundamentals

📚 Key Terms

TermDefinition
TenantOrganization / Customer
InstanceThe running application shared by all tenants
IsolationEnsuring tenants cannot see or access each other's data
CustomizationTenant-specific branding, rules, workflows, permissions
ConfigurationSettings that vary per tenant (features, limits, policies)

📊 Real-World Example: Microsoft 365

Microsoft 365 is one platform used by millions of organizations. Each is a separate tenant with its own data, users, policies, and admins.

Microsoft 365 Platform
├── Nike (Tenant A)
│   ├── Users, Teams, SharePoint Sites
│   ├── Data, Policies, Admins
├── Spotify (Tenant B)
│   ├── Users, Teams, SharePoint Sites
│   ├── Data, Policies, Admins
└── Adobe (Tenant C)
    ├── Users, Teams, SharePoint Sites
    ├── Data, Policies, Admins

Each company believes it's using its own Microsoft 365, even though they share the platform.


🛡️ Isolation Must Exist At:

✓ Identity → User belongs to exactly one tenant. User IDs are tenant-specific.
✓ Authentication → Login through tenant's identity provider (Entra ID, Okta, Auth0)
✓ Authorization → Roles/permissions evaluated per tenant
✓ Business Logic → Workflows, rules, automations apply per tenant
✓ UI / Theme → Branding, logo, language are tenant-specific
✓ Database → Queries filter by tenant_id
✓ Storage → Files stored and accessed per tenant
✓ Logging / Audit → All logs tagged with tenant_id

🔒 GOLDEN RULE

A tenant must NEVER be able to see, modify, or even know that another tenant exists.


🗄️ How Can Tenant Data Be Stored?

The database model answers: "WHERE is each tenant's data stored?"

Model A: Shared Database + Shared Schema (Most Common) ⭐⭐⭐

Setup: One database, one schema, all tenants share tables. Isolation via tenant_id column.

Users Table (One Table For All Tenants):

idtenant_iduser_nameemail
1nikeAlicealice@nike.com
2nikeBobbob@nike.com
3spotifySarahsarah@spotify.com
4adobeJohnjohn@adobe.com

Pros: Low cost, easy ops, scalable
Cons: Risk if tenant_id filter missed, noisy neighbor problem
Best For: Startups, many small tenants, cost-sensitive


Model B: Shared Database + Separate Schemas (Good Balance) ⭐⭐

Setup: One database, each tenant gets its own schema (namespace).

Microsoft 365 Database
├── nike.users (Alice, Bob)
├── spotify.users (Sarah, Mike)
└── adobe.users (John, Lisa)

Pros: Better isolation, harder to leak cross-tenant data, easier per-tenant migration
Cons: More schemas → migration complexity
Best For: Growing SaaS, medium tenants, need better isolation


Model C: Separate Database per Tenant (Highest Isolation) ⭐

Setup: Every tenant gets a dedicated database.

Infrastructure
├── DB_Nike → Users, Teams, Files, Roles
├── DB_Spotify → Users, Teams, Files, Roles
└── DB_Adobe → Users, Teams, Files, Roles

Pros: Strongest isolation, easy per-tenant backup, easy compliance
Cons: High cost, operational complexity, complex migrations
Best For: Large enterprise, strict compliance, regulated industries


📋 Comparison of Database Models

CriteriaShared SchemaSeparate SchemasSeparate DB
CostVery Low ⭐⭐⭐Medium ⭐⭐High ⭐
Data IsolationLow ⭐Medium ⭐⭐High ⭐⭐⭐
Operational ComplexityLow ⭐Medium ⭐⭐High ⭐⭐⭐
Backup/Restore per TenantDifficultEasierEasy ⭐⭐⭐
Best ForStartups, Small tenantsGrowing SaaS, Medium tenantsLarge Enterprise, Strict compliance

🔴 CHECKPOINT 3: Which One to Choose?

Just starting? Many small tenants?
→ USE MODEL A (Shared Schema) - Cost effective

Need better isolation? Growing platform?
→ USE MODEL B (Separate Schemas) - Good balance

Large enterprise? Strict compliance?
→ USE MODEL C (Separate DB) - Maximum isolation

Mix of small & large tenants?
→ USE HYBRID (Most Real-World SaaS)

  • Small tenants: Shared Schema (cost-effective)
  • Enterprise tenants: Separate Database (premium)

Real-World Note: Many SaaS use HYBRID. Microsoft 365 keeps thousands of small organizations in shared databases while giving enterprise customers dedicated databases.

Database Models for Multi-Tenant SaaS

🏗️ Schema vs Database: Building Analogy

Think of database structure like a building:

🏢 BUILDING = DATABASE
├── 1st Floor (Schema) = nike schema
│   ├── Room = Users table (Alice, Bob)
│   ├── Room = Teams table
│   └── Room = Files table
├── 2nd Floor (Schema) = spotify schema
│   ├── Room = Users table (Sarah, Mike)
│   ├── Room = Teams table
│   └── Room = Files table
└── 3rd Floor (Schema) = adobe schema
    ├── Room = Users table (John, Lisa)
    ├── Room = Teams table
    └── Room = Files table

Floors are completely isolated. Same rooms (tables) on each floor. In Microsoft 365, each organization truly believes it's using its own "building."


❌ Common Misconceptions

❌ "Separate Database = Better Architecture"
Reality: Separate database is a storage choice, not an architectural approach.

❌ "Multi-Tenancy = Must Share Everything"
Reality: Multi-tenancy is about isolation at every layer. Model C (separate DB) is still multi-tenant.

❌ "You Can Change Your Model Later"
Reality: Migrating from Shared Schema → Separate Schemas → Separate DB is complex. Choose wisely.


Part 2️⃣: Request Flow & Implementation

🔴 CHECKPOINT 2: Principle

Tenant context is created ONCE (during authentication) and travels with the request end-to-end.

Once a user logs in and gets a JWT with their tenant_id, that context flows through every layer: API Gateway → Microservices → Database. Every component validates it received the correct tenant context.

Request Flow & Tenant Context

📡 How Does tenant_id Travel? (Request Life Cycle)

React UI → API Gateway → Microservices → Database

1. User logs in → IdP issues JWT with tenant_id
2. React sends request with JWT in Authorization header
3. API Gateway validates token & extracts tenant_id
4. Gateway forwards request (JWT or headers) to services
5. Each service reads tenant_context → applies business logic
6. Repository/DB layer uses tenant context → applies in business logic
7. DB uses tenant context → data isolation

Sample JWT (Simplified):

{
  "sub": "12345",
  "email": "alice@nike.com",
  "tenant_id": "nike",
  "roles": ["Admin"]
}

📊 What Happens at Each Step

StepWhat HappensInformation Available
1. Login (User)User enters email & passwordEmail, Password
2. Token Issued (Entra ID)Authenticates user, issues JWT with tenant_idtenant_id, user_id, roles, exp
3. Request to GatewayBrowser sends request with JWTFull JWT in Authorization header
4. Gateway ProcessingValidates JWT, extracts claims, forwardstenant_id, user_id, roles (verified)
5. Service ProcessingApplies business rules, checks authtenant_id from JWT/headers (validated)
6. Data Access (Database)Uses tenant_id to filter queriesOnly that tenant's data

🎟️ What is in the JWT?

Sample JWT Payload (Decoded):

{
  "iss": "https://login.microsoftonline.com/xxx",
  "aud": "api://your-app-id",
  "tid": "nike-tenant-id",
  "oid": "alice-object-id",
  "upn": "alice@nike.com",
  "roles": ["Global Admin", "User"],
  "exp": 1716100000
}

Why JWT? ✓ Stateless (no server session required)
✓ Contains tenant context (tenant_id is embedded)
✓ Signed by identity provider (cannot be tampered with)
✓ Short lived & secure


🔄 Tenant Context Propagation

The tenant_id travels through every layer:

Browser
   │ JWT in Authorization header
   ↓
API Gateway
   │ Extracts tenant_id
   │ Puts in headers: X-Tenant-ID
   ↓
Microservice A
   │ Reads X-Tenant-ID
   │ Uses in business logic
   │ Passes to next service
   ↓
Microservice B
   │ Reads X-Tenant-ID
   │ Uses in business logic
   │ Passes to database layer
   ↓
Database / Repository
   │ Receives tenant_id
   │ Filters: WHERE tenant_id = 'nike'
   │ Returns only Nike's data
   ↓
Response (Tenant-filtered)

✅ API Gateway Responsibilities

MUST DO ✅
✓ Validate JWT signature using identity provider keys
✓ Check token expiry
✓ Extract claims (tenant_id, user_id, roles)
✓ Reject unauthorized or invalid tokens
✓ Forward request to appropriate service
✓ Pass context in headers (X-Tenant-ID)

SHOULD NOT DO ❌
❌ Apply business logic
❌ Make authorization decisions
❌ Access the database
❌ Trust client-provided tenant_id


🔧 Microservice Responsibilities

MUST DO ✅
✓ Read tenant context from JWT or headers
✓ Apply business rules per tenant
✓ Check authorization (roles/permissions)
✓ Validate all data access
✓ Propagate tenant context to database layer
✓ Never trust client → Always derive tenant_id from JWT

SHOULD NOT DO ❌
❌ Validate JWT (gateway already did)
❌ Trust client-provided tenant information
❌ Allow cross-tenant operations


👥 Who Owns What? (Responsibility Matrix)

ComponentResponsibilityMust Validate Tenant?
BrowserSends JWT to APINO (just passes token)
Identity ProviderIssues JWT with tenant_idYES (in token)
API GatewayValidates JWT, extracts tenant_idYES (validates signature)
Microservice AApplies business logicYES (validates context)
Microservice BMore business logicYES (validates context)
Repository / DB LayerFilters by tenant_idYES (in WHERE clause)
Storage LayerStores files per tenantYES (organizes by tenant)
Logging SystemTags logs with tenant_idYES (in logs)

🔑 Key Principle

Every component that touches data must independently verify it's operating on the correct tenant.

This is defense in depth — multiple layers checking the same thing.


📊 Common Data Flow Patterns

Pattern 1: Shared Schema (Model A)

Query: SELECT * FROM Users 
       WHERE user_id = 'alice-obj-id' 
       AND tenant_id = 'nike'

Returns: Alice, Bob (nike users)
Filters out: Sarah (spotify), John (adobe)

Pattern 2: Separate Schemas (Model B)

Query: SELECT * FROM nike.users 
       WHERE user_id = 'alice-obj-id'

Returns: Data from nike schema only
Cannot access: spotify or adobe schemas

Pattern 3: Separate Database (Model C)

Connect to DB_Nike
Query: SELECT * FROM users WHERE user_id = 'alice-obj-id'

Returns: Data from DB_Nike only
Cannot access: DB_Spotify or DB_Adobe (different databases)


🎯 Key Takeaways

⭐ Multi-tenancy allows one platform to serve many customers efficiently
⭐ Isolation is enforced at every layer, not just the database
⭐ Tenant context is identified during authentication and travels end-to-end with request
⭐ Same codebase, same application → Different data, config, experience for each tenant
⭐ Proper tenant context handling is critical for security, scalability, and compliance
⭐ Database models answer WHERE data is stored — they don't determine IF you're multi-tenant
⭐ Gateway is first line of defense — validate JWT, extract tenant_id, reject unauthorized requests
⭐ Every service must validate tenant context — never trust it from the client
⭐ Database must filter by tenant_id — this is your final safeguard
⭐ The Golden Rule: A tenant must never see, modify, or know about another tenant


Document Version: 1.0
Last Updated: August 5, 2026
Source: Handwritten Multi-Tenant SaaS System Design Notes

Practical notes on product management, distributed systems, and AI—written from real engineering and product experience.