Going from one client to many is harder than it looks
When I first built PlusTurk, it was a simple tool written for a single law firm. When more firms wanted to use it, I thought, "It already works, I'll just adapt it a bit." I was wrong. Moving to a multi-tenant architecture is less about writing code and more about rebuilding your assumptions. Here are the most common mistakes I've seen, and made myself.
Mistake 1: Adding the tenant ID later
If you design your tables for a single tenant and add a tenantId column afterwards, you have to review every existing query one by one. Miss one, and one company's data can leak to another. The right way is to add tenantId to every table from day one, and ideally use row-level security at the database level.
model CaseFile {
id String @id @default(cuid())
tenantId String // required from the start
// ...
@@index([tenantId])
}Mistake 2: Relying on writing the tenant filter by hand in every query
Adding where: { tenantId } by hand to every findMany call will be forgotten at some point. It's much safer to route data access through a single layer (a repository or service) and enforce the tenant filter there.
Mistake 3: Isolating shared resources (SMS, payments) badly
In PlusTurk, each company connects its own SMS and virtual POS account. If we had done this through one shared account, one company's SMS bill could land on another company, or one company's payment problem could affect the rest. Isolating resources between tenants matters as much as isolating data.
Mistake 4: Writing report queries without thinking about scale
In a single-tenant system, a "count all records" query is fine. With hundreds of tenants, the same query can lock up the database. Report and dashboard queries need to be designed from the start to be indexed and to run per tenant.
// Risky: scans the whole table
const total = await prisma.caseFile.count();
// Right: tenant-specific query that uses the index
const total = await prisma.caseFile.count({ where: { tenantId } });Mistake 5: Handling customisation with code branches
When a client says "I need this to work differently for me", it's tempting to write if (tenantId === "xyz") in the code. That's quick in the short term, but in the long run it turns into a pile of code nobody can maintain. The right way is to model customisation as configuration stored in the database. Just like the Site Settings in this site's admin panel, you change the behaviour by changing data, not code.
Conclusion
Multi-tenant architecture isn't "selling the same code to several clients". It means building isolation, security and scalability into the design from the start. My biggest lesson from PlusTurk: fixing these decisions later costs far more than getting them right at the beginning.
Cantürk Çelikok
Freelance full stack developer based in Ankara, Türkiye. He built live products such as PlusTurk, Nerdesin, Ayda POS and Songnaria Coffee end to end, and shares the notes from those projects here.
