Multi-Tenant Architecture

SkillDatabases & data

Multi-tenant architecture patterns. Database-per-tenant, schema-per-tenant, shared-schema with tenant ID, row-level security, tenant resolution, and data isolation.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Multi-Tenant Architecture skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/architecture/multitenancy/SKILL.md and read by ahel’s review.

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)

Signals

GitHub stars
33
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
multitenancy
Source
github.com/claude-dev-suite/claude-dev-suite