One Database, Five Products: Designing Multi-Tenancy Without a Framework
How we isolated five client products in a single PostgreSQL database using row-level tenant_id and shared schema — and why we skipped off-the-shelf multi-tenant frameworks.
Context
At Metarune Labs, we needed to serve multiple client products from one platform. Each client has their own users, data, and configuration, but the core product logic is identical. The business needed fast onboarding of new clients without spinning up new infrastructure. We had one small engineering team and a tight timeline to ship the first three tenants.
Constraints
- Single small team — no bandwidth to evaluate and integrate a heavy multi-tenant framework
- Existing PostgreSQL database with schema already in use by the first product
- Clients required strict data isolation with no possibility of cross-tenant data leakage
Architecture
We chose a shared-schema, row-level isolation model. Every table that holds tenant-specific data has a tenant_id column. All queries are scoped by tenant_id — enforced at the application layer with a middleware that injects the tenant context from the authenticated session. We added a tenants table for per-tenant configuration (feature flags, branding, API limits). PostgreSQL row-level security was considered but we opted for application-level enforcement to keep query patterns explicit and avoid surprises. The Next.js API layer validates tenant access on every request before any data access.
Alternatives considered
- Schema-per-tenant (separate PostgreSQL schemas for each client): Would have required migrations to run N times for N tenants, made cross-tenant analytics painful, and complicated connection pooling. We'd need it at much larger scale.
- Database-per-tenant (separate PostgreSQL instance per client): Operational overhead of managing multiple databases, higher cost, and harder to share connection pools. Overkill for our current tenant count and growth rate.
Lessons learned
- Row-level isolation with tenant_id is simple and works well until you hit hundreds of tenants or need regulatory isolation guarantees.
- Application-level tenant scoping is easier to reason about than RLS — but you must be disciplined. One missed WHERE clause is a data leak.
- A central tenants config table pays off quickly. We use it for feature flags, rate limits, and branding — all without code changes per client.