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.

  • 8 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…