"Let's ship the MVP first and think about multi-tenancy later" is a sentence we hear a lot. The trouble is that the tenant concept touches every layer of the system, from the database to authorisation, from caching to billing. Adding it later is like replacing a building's foundations after the floors are up.
These are the five decisions we made on day one of Tarlamapp, the platform we built for farm advisors and cooperatives.
1. Isolation model: shared tables or separate schemas?
There are three common models: a database per tenant, a schema per tenant, or shared tables with a tenantId on every row. With hundreds to thousands of tenants and no legal requirement for physical separation, shared tables are the cheapest to operate.
We chose that model, on one condition: the tenantId filter must never depend on a developer remembering it.
2. Apply the tenant filter in exactly one place
The most dangerous bug is forgetting where tenantId = ? in a single query; one cooperative's data shows up for another. To prevent it, we resolve the tenant context at the start of each request and apply it automatically across the data-access layer. On Postgres, Row Level Security is a valuable second safety net.
alter table field enable row level security;
create policy tenant_isolation on field
using (tenant_id = current_setting('app.tenant_id')::uuid);We also write an automatic "does not return another tenant's data" test for every list endpoint.
3. Plan limits do not belong in the code
Rules such as "3 fields on the free plan, 50 on Pro" start as an if on day one and spread everywhere. Keep limits as data instead: plan → feature → limit. The application asks a single service "may this tenant do this?", and when marketing designs a new plan, no code changes.
4. Keep billing separate from usage
We store usage events (a photo uploaded, an analysis run) as immutable records; billing reads them at the end of each period. Even when prices change, past usage is calculated correctly, and "why did I pay this much?" can be answered line by line.
5. Observability per tenant
When a tenant says "the system is slow", global averages do not help. Add tenantId to logs, metrics and traces. Seeing which tenant uses the most resources, or which one is hitting errors, in a single query cuts both support time and infrastructure cost.
Summary
Choose the isolation model by scale and legal requirements.
Automate the tenant filter and lock it at the database level too.
Keep limits and prices as data, not code.
Record usage as immutable events.
Tag every record with its tenant.
These five decisions added a few days to the first sprint, and saved us a rewrite that would have taken months.


