Back to skills
SKILL.md
Saas Multitenancy
ASecurityExpert in designing multi-tenant SaaS architectures with isolation, scalability, and enterprise features. Use when designing system architecture or making technical decisions about saas multitenancy.
- 8 stars
- 0 votes
- 0 copies
- 4 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill saas-multitenancy --agent claude-codeAre you the author of Saas Multitenancy?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-saas-multitenancy)---
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)
Attribution
Comments
Loading comments…