Multi-Tenant Design
Use this skill when building SaaS or any application serving multiple tenants, workspaces, organizations, or customers.
Isolation ladder
Choose the lowest isolation level that satisfies security, compliance, scale, and operational needs.
- Row-level isolation: shared D1/database tables with
tenant_idon every row. - Schema/table/database per tenant group: stronger blast-radius control and data movement options.
- Durable Object per tenant/entity: serialized coordination and private per-object state.
- Separate buckets/namespaces/resources: higher isolation and operational overhead.
- Separate Workers/accounts/platform model: strongest isolation, highest management complexity.
Mandatory tenant rules
- Every request must resolve a principal with
tenantIdbefore data access. - Every D1 table that stores tenant data must include
tenant_idunless physically isolated. - Every query must include a tenant predicate or use a tenant-specific database/binding.
- Every R2 key should include a tenant prefix or tenant-isolated bucket.
- Every KV key must include tenant and schema/version dimensions.
- Durable Object names should include tenant ID for tenant-scoped coordination.
D1 query pattern
async function getProject(env: Env, tenantId: string, projectId: string) {
return env.DB.prepare(
`SELECT id, tenant_id, name, created_at
FROM projects
WHERE tenant_id = ? AND id = ?`
).bind(tenantId, projectId).first<Project>();
}
R2 key pattern
const key = `tenants/${tenantId}/files/${fileId}`;
Durable Object key pattern
const object = env.TENANT_COORDINATOR.getByName(`tenant:${tenantId}`);
Noisy-neighbor controls
- Rate-limit by tenant and user.
- Partition Durable Objects by entity, not just tenant, if a tenant can be very large.
- Track per-tenant storage, queue, workflow, and AI usage.
- Add quotas before enterprise customers demand them.
- Use separate resources for large/high-compliance tenants when required.
Review checklist
- Tenant ID is derived from authenticated identity, not trusted from request body.
- All D1 queries include tenant scope.
- Cache keys include tenant scope.
- R2 object access checks metadata/DB authorization before download.
- Queue messages include tenant ID and are validated.
- AI prompts/retrieval cannot include another tenant's data.
- Logs avoid cross-tenant sensitive data leakage.
Anti-patterns
- Frontend hides other tenants but backend queries do not scope by tenant.
- KV key
settingsshared across all tenants. - R2 object key generated from user-supplied filename only.
- Agent or AI tool can search across tenants without hard filters.
- One Durable Object serializes all tenants.