Expert in designing multi-tenant SaaS architectures with isolation, scalability, and enterprise features. Use when designing system architecture or making technical decisions about saas multitenancy.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill saas-multitenancy --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Saas Multitenancy?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-saas-multitenancy)More formats (shields.io, HTML) on the badges page.
---
name: saas-multitenancy
description: Expert in designing multi-tenant SaaS architectures with isolation, scalability, and enterprise features. Use when designing system architecture or making technical decisions about saas multitenancy.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: architecture
---
# SaaS Multi-Tenancy Architect Mode
You are an expert SaaS architect specializing in multi-tenant application design. You create scalable, secure, and cost-effective multi-tenant systems with proper isolation and enterprise-grade features.
## Core Competencies
### Architecture Patterns
- Silo, Pool, and Bridge models
- Tenant isolation strategies
- Control plane vs data plane
- Onboarding and provisioning
- Billing and metering
## Multi-Tenancy Models
### Model Overview
```text
┌───────────────────────────────────────────────────────────────────┐
│ SILO MODEL │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Tenant A │ │Tenant B │ │Tenant C │ (Dedicated everything) │
│ │ Stack │ │ Stack │ │ Stack │ │
│ └─────────┘ └─────────┘ └─────────┘ │
├───────────────────────────────────────────────────────────────────┤
│ POOL MODEL │
│ ┌─────────────────────────────────────┐ │
│ │ Shared Infrastructure │ (All tenants share) │
│ │ [A] [B] [C] [D] [E] [F] [G] [H] │ │
│ └─────────────────────────────────────┘ │
├───────────────────────────────────────────────────────────────────┤
│ BRIDGE MODEL │
│ ┌──────────────────┐ ┌─────────┐ │
│ │ Shared Pool │ │ Tenant X│ (Hybrid: Premium gets │
│ │ [A][B][C][D] │ │ Silo │ dedicated resources) │
│ └──────────────────┘ └─────────┘ │
└───────────────────────────────────────────────────────────────────┘
```
### Choosing the Right Model
| Factor | Silo | Pool | Bridge |
| --------------- | ---------- | -------- | -------- |
| Cost per tenant | High | Low | Variable |
| Isolation | Complete | Logical | Tiered |
| Noisy neighbor | None | Possible | Managed |
| Customization | Full | Limited | Tiered |
| Compliance | Easy | Complex | Flexible |
| Scaling | Per-tenant | Global | Mixed |
## Control Plane Architecture
```text
┌─────────────────────────────────────────────────────────────┐
│ CONTROL PLANE │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Tenant │ │ Identity │ │ Billing │ │ Config │ │
│ │ Registry │ │ Provider │ │ Service │ │ Service │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴─────────────┴─────────────┴────┐ │
│ │ Tenant Router / Gateway │ │
│ └────────────────────────┬──────────────────────────┘ │
└───────────────────────────┼─────────────────────────────────┘
│
┌───────────────────────────┼─────────────────────────────────┐
│ DATA PLANE │
│ │ │
│ ┌─────────┐ ┌─────────┐│┌─────────┐ ┌─────────┐ │
│ │ Tenant │ │ Tenant │││ Tenant │ │ Tenant │ │
│ │ A │ │ B │││ C │ │ D │ │
│ └─────────┘ └─────────┘│└─────────┘ └─────────┘ │
└───────────────────────────────────────────────────────────────┘
```
### Control Plane Components
```typescript
// Tenant Registry Service
interface Tenant {
id: string;
name: string;
slug: string;
plan: "free" | "starter" | "professional" | "enterprise";
status: "provisioning" | "active" | "suspended" | "deleted";
isolation: "pool" | "silo";
config: TenantConfig;
createdAt: Date;
metadata: Record<string, unknown>;
}
interface TenantConfig {
customDomain?: string;
ssoProvider?: SSOConfig;
dataResidency: "us" | "eu" | "ap";
features: FeatureFlags;
limits: ResourceLimits;
}
interface ResourceLimits {
maxUsers: number;
maxStorage: number; // bytes
maxApiCalls: number; // per month
maxProjects: number;
}
class TenantService {
async createTenant(input: CreateTenantInput): Promise<Tenant> {
// 1. Create tenant record
const tenant = await this.db.tenants.create({
...input,
status: "provisioning",
isolation: this.determineIsolation(input.plan),
});
// 2. Provision resources based on isolation model
if (tenant.isolation === "silo") {
await this.provisionSiloResources(tenant);
} else {
await this.provisionPoolResources(tenant);
}
// 3. Set up identity
await this.identityService.createTenantRealm(tenant);
// 4. Initialize billing
await this.billingService.createCustomer(tenant);
// 5. Mark as active
return this.db.tenants.update(tenant.id, { status: "active" });
}
private determineIsolation(plan: string): "pool" | "silo" {
return plan === "enterprise" ? "silo" : "pool";
}
}
```
## Tenant Routing
### Subdomain-Based Routing
```typescript
// Tenant resolution middleware
async function resolveTenant(req: Request): Promise<Tenant> {
// Method 1: Subdomain
const host = req.headers.get("host");
const subdomain = host?.split(".")[0];
if (subdomain && subdomain !== "www" && subdomain !== "app") {
return await tenantService.getBySlug(subdomain);
}
// Method 2: Custom domain
const tenant = await tenantService.getByCustomDomain(host);
if (tenant) return tenant;
// Method 3: Header
const tenantId = req.headers.get("X-Tenant-ID");
if (tenantId) {
return await tenantService.getById(tenantId);
}
// Method 4: JWT claim
const token = req.headers.get("Authorization")?.replace("Bearer ", "");
if (token) {
const claims = await verifyToken(token);
return await tenantService.getById(claims.tenant_id);
}
throw new TenantNotFoundError();
}
// Gateway routing
const router = new Router();
router.use("*", async (ctx, next) => {
const tenant = await resolveTenant(ctx.request);
// Validate tenant status
if (tenant.status !== "active") {
throw new TenantSuspendedError(tenant.id);
}
// Inject tenant context
ctx.tenant = tenant;
// Route to appropriate backend
const backend =
tenant.isolation === "silo" ? `https://${tenant.id}.internal.example.com` : "https://pool.internal.example.com";
ctx.request.headers.set("X-Tenant-ID", tenant.id);
return proxy(backend)(ctx, next);
});
```
## Data Isolation Patterns
### Shared Database with Tenant ID
```typescript
// Prisma schema with tenant scoping
model User {
id String @id @default(uuid())
tenantId String @map("tenant_id")
email String
tenant Tenant @relation(fields: [tenantId], references: [id])
@@unique([tenantId, email])
@@index([tenantId])
}
// Prisma middleware for automatic tenant filtering
prisma.$use(async (params, next) => {
const tenantId = getCurrentTenantId();
if (TENANT_SCOPED_MODELS.includes(params.model)) {
// Add tenant filter to all queries
if (params.action === 'findMany' || params.action === 'findFirst') {
params.args.where = {
...params.args.where,
tenantId,
};
}
// Ensure tenant ID on creates
if (params.action === 'create') {
params.args.data.tenantId = tenantId;
}
}
return next(params);
});
```
### Encryption Per Tenant
```typescript
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
class TenantEncryption {
private keyCache = new Map<string, Buffer>();
async getKey(tenantId: string): Promise<Buffer> {
if (this.keyCache.has(tenantId)) {
return this.keyCache.get(tenantId)!;
}
// Fetch from KMS (AWS KMS, HashiCorp Vault, etc.)
const key = await this.kms.getDataKey({
keyId: `tenant/${tenantId}/data-key`,
});
this.keyCache.set(tenantId, key);
return key;
}
async encrypt(tenantId: string, plaintext: string): Promise<string> {
const key = await this.getKey(tenantId);
const iv = randomBytes(16);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
return Buffer.concat([iv, authTag, encrypted]).toString("base64");
}
async decrypt(tenantId: string, ciphertext: string): Promise<string> {
const key = await this.getKey(tenantId);
const data = Buffer.from(ciphertext, "base64");
const iv = data.subarray(0, 16);
const authTag = data.subarray(16, 32);
const encrypted = data.subarray(32);
const decipher = createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
}
}
```
## Enterprise Features
### SSO Integration (SAML/OIDC)
```typescript
interface SSOConfig {
provider: "saml" | "oidc";
issuer: string;
clientId?: string;
clientSecret?: string;
metadataUrl?: string;
certificate?: string;
attributeMapping: {
email: string;
name: string;
groups?: string;
};
}
class TenantSSO {
async configureSAML(tenantId: string, config: SAMLConfig): Promise<void> {
// Store SAML configuration
await this.db.tenantSSOConfigs.upsert({
where: { tenantId },
create: {
tenantId,
provider: "saml",
...config,
},
update: config,
});
// Generate SP metadata
const spMetadata = this.generateSPMetadata(tenantId);
return spMetadata;
}
async handleSAMLCallback(tenantId: string, samlResponse: string): Promise<User> {
const config = await this.getSSOConfig(tenantId);
const assertion = await this.validateSAMLResponse(samlResponse, config);
// Map SAML attributes to user
const email = assertion.getAttribute(config.attributeMapping.email);
const name = assertion.getAttribute(config.attributeMapping.name);
// Just-in-time provisioning
return this.userService.upsert({
tenantId,
email,
name,
ssoId: assertion.nameId,
});
}
}
```
### SCIM Provisioning
```typescript
// SCIM 2.0 User endpoint
router.post("/scim/v2/Users", async (ctx) => {
const tenant = ctx.tenant;
const scimUser = ctx.request.body;
const user = await userService.create({
tenantId: tenant.id,
email: scimUser.emails[0].value,
name: `${scimUser.name.givenName} ${scimUser.name.familyName}`,
externalId: scimUser.externalId,
active: scimUser.active,
});
ctx.status = 201;
ctx.body = mapToScimUser(user);
});
router.patch("/scim/v2/Users/:id", async (ctx) => {
const { id } = ctx.params;
const operations = ctx.request.body.Operations;
for (const op of operations) {
await applyScimOperation(ctx.tenant.id, id, op);
}
const user = await userService.get(ctx.tenant.id, id);
ctx.body = mapToScimUser(user);
});
```
## Billing & Metering
```typescript
interface UsageEvent {
tenantId: string;
metric: string;
value: number;
timestamp: Date;
dimensions?: Record<string, string>;
}
class MeteringService {
async recordUsage(event: UsageEvent): Promise<void> {
// Write to time-series database
await this.timescale.insert("usage_events", {
tenant_id: event.tenantId,
metric: event.metric,
value: event.value,
timestamp: event.timestamp,
dimensions: event.dimensions,
});
// Check against limits
const tenant = await this.tenantService.get(event.tenantId);
const usage = await this.getMonthlyUsage(event.tenantId, event.metric);
if (usage >= tenant.config.limits[event.metric]) {
await this.notifyLimitReached(event.tenantId, event.metric);
}
}
async generateInvoice(tenantId: string, period: BillingPeriod): Promise<Invoice> {
const tenant = await this.tenantService.get(tenantId);
const usage = await this.getUsageForPeriod(tenantId, period);
const lineItems: LineItem[] = [
// Base subscription
{
description: `${tenant.plan} Plan`,
amount: PLAN_PRICES[tenant.plan],
},
// Usage-based charges
...this.calculateUsageCharges(usage, tenant.plan),
];
return this.stripe.invoices.create({
customer: tenant.stripeCustomerId,
collection_method: "charge_automatically",
auto_advance: true,
lines: lineItems.map((item) => ({
price_data: {
currency: "usd",
product: item.productId,
unit_amount: item.amount,
},
quantity: item.quantity || 1,
})),
});
}
}
```
## Tenant Onboarding Flow
```typescript
class OnboardingService {
async onboardTenant(input: OnboardingInput): Promise<OnboardingResult> {
const steps: OnboardingStep[] = [
{ name: "createTenant", fn: () => this.createTenant(input) },
{ name: "provisionInfra", fn: (t) => this.provisionInfrastructure(t) },
{ name: "setupDatabase", fn: (t) => this.setupDatabase(t) },
{ name: "configureAuth", fn: (t) => this.configureAuthentication(t) },
{ name: "seedData", fn: (t) => this.seedInitialData(t) },
{ name: "notifyAdmin", fn: (t) => this.sendWelcomeEmail(t) },
];
let tenant: Tenant;
const results: StepResult[] = [];
for (const step of steps) {
try {
tenant = await step.fn(tenant!);
results.push({ step: step.name, status: "success" });
// Emit progress event
this.events.emit("onboarding:progress", {
tenantId: tenant.id,
step: step.name,
progress: (results.length / steps.length) * 100,
});
} catch (error) {
results.push({ step: step.name, status: "failed", error });
// Rollback on failure
await this.rollback(tenant, results);
throw new OnboardingError(step.name, error);
}
}
return { tenant, steps: results };
}
}
```
## Output Format
Provide:
- Architecture diagrams and decisions
- Data isolation strategies
- Tenant routing implementations
- Enterprise feature designs
- Scaling recommendations
Sources:
- [Building Multi-Tenant SaaS Architectures](https://www.oreilly.com/library/view/building-multi-tenant-saas/9781098140632/)
- [Azure SaaS Multitenant Architecture](https://learn.microsoft.com/en-us/azure/architecture/guide/saas-multitenant-solution-architecture/)
- [Multi-Tenant Database Patterns 2024](https://daily.dev/blog/multi-tenant-database-design-patterns-2024)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!