Multi-tenant applications: one product, many organizations
A clinic, a merchant, an instructor, a supplier branch — each is an organization inside one product. Multi-tenancy is how that stays one codebase without sharing data.

A multi-tenant application serves many organizations from one product. Each organization is a tenant. Users belong to tenants. Data does not cross the wall. That is not a Microsoft Entra setting, and it is not “SaaS” as a slogan. SaaS is a business model. Multitenancy is an architecture. They often travel together. They are not the same word.
Fluids builds multi-tenant products so clinics, merchants, instructors, and suppliers share a codebase without sharing data. The mobile client, the admin, and the backend are one system. Isolation lives in the backend.
What a tenant actually is
A tenant is a logical customer boundary — usually an organization — not a user. Users are global. Memberships attach them to tenants. Authorization is evaluated inside the tenant. A user can belong to more than one. “Admin” is meaningless until it is admin of this organization.
Isolation is a spectrum, not a boolean. You can share the application and isolate the data. You can isolate a single noisy tenant later without rewriting the product. Physical isolation can change. Logical tenancy cannot. If tenant is a comment in a ticket, it is already too late.
Four things that must be isolated
- Data — a clinic must not see another clinic’s appointments.
- Roles — owner, staff, and reviewer are scoped to the organization.
- Branding — name, mark, locale. Configuration, not a fork.
- Operations — restore, a kill switch, logs, and rate limits tagged by tenant.
Three isolation patterns
The names vary. The objects do not.
Shared schema and a tenant key
One database, one schema. Every business row carries a tenant id. Isolation is a policy. Onboarding is a row. One migration ships to everyone. This is the default for a new product. The cost of being wrong is a missing filter — a leak — and a noisy neighbor. Per-tenant restore is hard.
Schema per tenant
One instance, one namespace per tenant. Useful when you need a per-tenant restore or light schema drift, and you are not heading toward tens of thousands of tenants. Migrations become N operations. The catalogue of schemas becomes an ops product.
Database per tenant
A dedicated database, sometimes a dedicated stack. Right when a contract, a regulator, or a single tenant’s load demands a wall. Often a later escape hatch for a few tenants, not the default. Onboarding is provisioning. Analytics is a project. Cost grows with the count.
Hybrid is a first-class model: most tenants pooled, one enterprise tenant in its own database, same application, same tenant id in the code. Changing the mapping later is often costly. Design so a tenant can be moved. Do not design so the product can only live in one shape.
Row-level security, in the stack we ship
Postgres row-level security is an automated WHERE the engine applies. It belongs on the shared-schema pattern. It does not replace the tenant column, the index, or the unique constraint. It is defense in depth for the day a developer forgets the filter.
On Supabase, a table in an exposed schema without RLS is readable and writable by anyone with the publishable key. Enabling RLS with no policies means no rows are visible. The table owner bypasses RLS unless you force it. The application must not connect as the owner. The service-role key bypasses RLS and is server-side only.
Roles and tenant membership do not belong in a field the user can edit. They belong in server-owned claims or a membership table. A JWT goes stale. A membership change is not live until refresh. A wrong policy fails quietly: too open returns extra rows; too tight returns nothing and raises no error. Test both directions.
When to design for it on day one
If the customer is an organization, model the organization in v1. Isolation can be the cheap pattern — shared schema, tenant column, RLS. Do not provision a database per tenant because a blog said “enterprise.” Do not build custom roles for a hypothesis.
If the first ten customers are one company, you can still put an organization id on the rows. You can see a second organization from the chair. That column is cheap. Retrofitting it after the schema hardens is not.
Admin, mobile, backend — one system
Tenant context lives in the backend. The React admin is two surfaces: the platform owner who onboards and suspends organizations, and the organization admin who runs their own. The Flutter client receives branding and membership. It does not invent isolation. A fifth tenant is configuration, not a repository.
What this looks like when it is shipped
Naqrah.app — clinics as organizations on a booking platform in Oman. Patients search and book. Each clinic manages its own appointments.
T-CODE — merchants with branches. The shopper app and the merchant dashboard are one product. A branch can be shown or hidden without touching another store.
Oxygen Academy — instructors, students, ambassadors. Roles, not three apps. Bilingual. Payouts sit with the instructor, not in a shared pot.
WOSTA — buyers and suppliers. A supplier manages more than one warehouse or branch, each with its own requests, offers, and invoices. Arabic is the primary voice. Sign-up is phone OTP.
Tenancy done well is invisible
It is invisible to the organization using the product. It is visible only to the people who operate the platform. If you are about to build a product whose customer is an organization, the model is day one. The silo is not. Write to us with the organizations in mind, not only the first user.
Tell us what you’re building.
Write to us with the product you want built. You will get a considered reply from the people who would do the work.
Start a conversation