Skip to content
Back to skills

Saas Multitenancy

ASecurity

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.

  • 9 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 8, 2026
securitytypescriptgoawsazureapidatabasebackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill saas-multitenancy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Saas Multitenancy?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Saas Multitenancy
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-saas-multitenancy/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-saas-multitenancy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.