All specs
infrastructurehigh complexity

Multi-tenancy & Workspaces

Used in 2 specs

Serve multiple customers / organizations from one product while keeping their data, config, and users isolated.

Options

Data Isolation Model*

Every table has a tenant_id column; every query filters on it.
One Postgres schema per tenant; tables are replicated across schemas.
Each tenant gets an isolated database (sometimes isolated VPC).

Tenant Identification*

Tenant context derived from hostname; clean URLs and cookie scoping.
Tenant context read from the URL path.
One URL; user picks active tenant after login and it persists in their session.

Per-tenant Configuration

Enable/disable features for specific tenants (beta access, custom enterprise features).
Logo, colors, and optionally custom domain in tenant-facing UI.
Auth policy, session duration, data retention, allowed domains — configured per tenant.

Decision Points

Do your target customers contractually require data residency or physical data isolation?

If yes

Plan database-per-tenant (or at minimum region-pinned schemas) from the start.

If no

Shared DB + tenant_id is the right starting point — do not over-isolate prematurely.

Do you expect one tenant to be >20% of total load?

If yes

Consider giving large tenants their own database to prevent noisy-neighbor impact.

If no

Shared-DB + resource limits per tenant is sufficient.

Shared DB with tenant_id, schema-per-tenant, or DB-per-tenant?

If yes

Default to shared DB + tenant_id for 95% of SaaS. Move to schema-per-tenant only as a transitional step, and DB-per-tenant only when contracts demand physical isolation.

If no

If unsure, stay with shared DB + tenant_id and invest in defense-in-depth (RLS, typed repos) instead of heavier isolation.

Do you need per-tenant custom domains (vanity domains like customer.com)?

If yes

Use subdomain routing with an ACME automation layer (e.g. Caddy, cert-manager, or a managed service like Approximated) to issue TLS certs on demand.

If no

Wildcard subdomain TLS is enough — skip the cert automation complexity until a customer actually asks.

Do customers contractually require tenant-level encryption key separation (BYOK)?

If yes

Plan DB-per-tenant or envelope encryption with per-tenant KMS keys (AWS KMS, GCP KMS). Budget significant engineering for key rotation and recovery.

If no

A single application-managed encryption key is sufficient and dramatically simpler to operate.

Do tenants need branded UI (logo, colors, custom domain)?

If yes

Enable custom-branding under features-per-tenant and store theme config per tenant. Standard for B2B2C.

If no

Skip branding entirely — one less surface to test, one less config to migrate.

Do enterprise contracts require isolated backups per tenant?

If yes

Plan DB-per-tenant or schema-per-tenant so pg_dump produces a clean per-tenant artifact. Shared-schema backups cannot satisfy this.

If no

One backup pipeline across all tenants is sufficient and cheaper to operate.

Do internal admins need cross-tenant reporting (aggregate metrics across tenants)?

If yes

Shared DB makes this trivial. With DB-per-tenant, plan a separate analytics warehouse (Snowflake, BigQuery) that ingests from all tenant DBs.

If no

No extra infrastructure needed — per-tenant queries are the only access pattern.

Do tenants need self-serve data export (GDPR portability)?

If yes

Build a per-tenant export job that streams their data as JSON/CSV. Add tenant-settings to expose the trigger. Required for GDPR compliance.

If no

Skip for now — handle ad-hoc export requests manually until a customer asks.

Do your customers need to share resources across tenants (e.g. a partner sharing a project)?

If yes

Requires explicit cross-tenant ACL modeling — avoid shared-schema tenant_id filtering assumptions. Consider ReBAC (SpiceDB, OpenFGA).

If no

Strict tenant_id isolation is the right default — never cross the boundary.

Do enterprise customers ask for tenant-scoped feature flags?

If yes

Enable feature-flags under features-per-tenant. Use LaunchDarkly, Unleash, or a simple tenant-id-keyed table — inevitable by month 6.

If no

Global flags are fine initially; add per-tenant scoping when the first enterprise deal demands it.

Do you need per-tenant rate limits (prevent noisy neighbors at the API layer)?

If yes

Key rate-limit buckets on tenant_id in Redis. Expose current usage in tenant-settings so admins can see their budget.

If no

Per-user or global limits are sufficient at your current scale.

Is tenant signup self-serve or sales-gated?

If yes

Build a provisioning flow that creates tenant context, default admin, and any per-tenant resources (schemas, S3 prefixes) atomically.

If no

Manual provisioning via internal admin is fine for enterprise-only products — skip the self-serve UX.

Do you need a tenant suspension/deactivation workflow (non-payment, abuse)?

If yes

Add a tenant status field (active/suspended/deleted) checked in middleware; persist data for the grace period before hard-delete.

If no

Hard-delete on offboarding is simpler but loses any recovery window — revisit once you have paying customers.

Will you need to migrate tenants between clusters/regions (data residency, scale)?

If yes

Design tenant data as a portable unit from day one — schema-per-tenant or DB-per-tenant makes this a dump+restore rather than a surgical export.

If no

Shared-schema is fine; cross-tenant migrations become painful but not impossible.

Do you need per-tenant observability (metrics, logs tagged by tenant)?

If yes

Tag every log line, metric, and trace with tenant_id. Use Grafana/Datadog tenant dashboards so support can diagnose individual tenants.

If no

Aggregate observability is enough for small tenant counts; add tagging once you have >50 tenants.

Do you need per-tenant audit logs for GDPR data-subject requests?

If yes

Combine with the audit-logging module — ensure every audit entry carries tenant_id and build a per-tenant export endpoint.

If no

Central audit logs with tenant_id filtering cover most cases until a DSR lands.

Do you need per-tenant queues/workers for noisy-neighbor isolation in background jobs?

If yes

Partition your queue by tenant_id (separate BullMQ/SQS queues per tenant, or weighted fair scheduling). Required for any tenant running bulk imports.

If no

A shared queue with per-tenant concurrency caps is simpler and sufficient for balanced workloads.

Tradeoffs

ComplexityShared DB, shared schema

Cheapest ops and easiest queries, but a single missed tenant_id filter is a data leak

CostDatabase-per-tenant

Operational cost scales with tenant count; migrations must run against every tenant DB

ComplexitySubdomain routing

Wildcard TLS cert and DNS configuration required; vanity domains compound that

ComplexityPer-tenant feature flags

Testing surface expands — every change must consider flag combinations and tenant configs

Go Deeper

Dependencies

user-system

Implementation Examples

Supabase

Reference for row-level-security-driven multi-tenancy in Postgres.

WorkOS

Handles many enterprise multi-tenant needs (org directory, SSO-per-tenant, audit).

Citus / pg_partman

Sharding Postgres by tenant_id when a single shared DB runs out of headroom.

Used by specs

SaaS StarterAI-Powered SaaS