multitenancy

v2026.09.24

Multi-tenant architecture patterns. Database-per-tenant, schema-per-tenant, shared-schema with tenant ID, row-level security, tenant resolution, and data isolation. USE WHEN: user mentions "multi-tenant", "multitenancy", "SaaS architecture", "tenant isolation", "row-level security", "tenant ID", "subdomain routing" DO NOT USE FOR: general database design - use database skills; authentication - use auth skills

GitHub
Install command
npx skhub add claude-dev-suite/multitenancy
Markdown
SKILL.md

Multi-Tenant Architecture

Isolation Strategies

StrategyIsolationComplexityCost
Database per tenantHighestHighHigh
Schema per tenantHighMediumMedium
Shared schema (tenant_id column)MediumLowLow
Row-level security (RLS)Medium-HighMediumLow

Shared Schema with Tenant ID (most common)

// Middleware: resolve tenant from subdomain or header
function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
  const host = req.hostname; // acme.myapp.com
  const subdomain = host.split('.')[0];
  const tenant = await tenantRepo.findBySubdomain(subdomain);
  if (!tenant) return res.status(404).json({ error: 'Tenant not found' });
  req.tenantId = tenant.id;
  next();
}

// Always filter by tenant
app.get('/api/products', async (req, res) => {
  const products = await db.product.findMany({
    where: { tenantId: req.tenantId },
  });
  res.json(products);
});

Prisma with Tenant Scoping

// Extension to auto-apply tenant filter
const prisma = new PrismaClient().$extends({
  query: {
    $allOperations({ args, query, operation }) {
      if (['findMany', 'findFirst', 'count', 'updateMany', 'deleteMany'].includes(operation)) {
        args.where = { ...args.where, tenantId: getCurrentTenantId() };
      }
      if (['create', 'createMany'].includes(operation)) {
        args.data = { ...args.data, tenantId: getCurrentTenantId() };
      }
      return query(args);
    },
  },
});

PostgreSQL Row-Level Security

-- Enable RLS
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

-- Policy: users see only their tenant's data
CREATE POLICY tenant_isolation ON products
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Set tenant context per request
SET app.tenant_id = 'tenant-uuid-here';
SELECT * FROM products; -- auto-filtered
// Set tenant context on each request
pool.on('connect', async (client) => {
  // Set after getting connection from pool
});

async function withTenant<T>(tenantId: string, fn: () => Promise<T>): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query(`SET app.tenant_id = $1`, [tenantId]);
    return await fn();
  } finally {
    await client.query('RESET app.tenant_id');
    client.release();
  }
}

Schema-Per-Tenant

// Dynamic schema selection
function getTenantSchema(tenantId: string): string {
  return `tenant_${tenantId.replace(/-/g, '_')}`;
}

async function createTenantSchema(tenantId: string) {
  const schema = getTenantSchema(tenantId);
  await db.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
  await db.query(`SET search_path TO ${schema}`);
  await runMigrations(); // Apply schema migrations
}

Tenant Resolution Strategies

StrategyExampleBest For
Subdomainacme.myapp.comB2B SaaS
Path prefixmyapp.com/acme/...Simpler setup
Custom headerX-Tenant-ID: acmeAPI-first
JWT claim{ tenantId: "acme" }Authenticated APIs

Anti-Patterns

Anti-PatternFix
No tenant filter on queriesUse middleware or ORM extension to auto-apply
Tenant ID from client without validationDerive from auth token or subdomain
No tenant data isolation testingWrite tests that verify cross-tenant isolation
Shared cache without tenant prefixPrefix all cache keys with tenant ID
No tenant-aware rate limitingRate limit per tenant, not globally

Production Checklist

  • Tenant resolution middleware on all routes
  • Data isolation verified with automated tests
  • Cache keys prefixed with tenant ID
  • Rate limiting per tenant
  • Tenant-scoped background jobs
  • Tenant provisioning and deprovisioning flow
  • Cross-tenant query prevention (RLS or ORM enforcement)
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/architecture/multitenancy

Default branch

main

Latest commit

9496306

Tree SHA

fe4e2f1