Multi-tenancy is one application serving many customers, where each sees only their own data. Your application has to enforce that separation in seven places.

A portal for 40 property owners

You manage 400 units for 40 property owners, and you want a portal that gives each customer their own statements. It has 62 logins, takes 200 documents a month, and four of your own staff have to be able to open any account. Three of the 62 work for two owners each. One is a bookkeeper with a single email address, and her login has to reach two accounts.

The seven parts of a multi-tenant app

The seven parts of a multi-tenant app Seven multi-tenancy controls grouped by where each one is enforced: identity, tenancy and permissions in the session on every request; isolation and support access in the database and storage layer; provisioning and billing with offboarding in the account lifecycle. A dashed route shows the service role key reaching the data layer without passing through the database policy. Every request Enforced in the session The data layer Enforced by Postgres + the bucket Account lifecycle Enforced by process Exception Service role key Bypasses RLS Identity Tenancy Permissions Isolation Support access Provisioning Billing & offboarding

1. Identity

The login proves a person is who they claim to be. It tells you nothing about the other six parts.

2. Tenancy

The bookkeeper is one person and two customers. A login that belongs to one customer covers 59 of your 62 persons and fails for the other 3. Use a membership table instead: one record per person, per account. Put the current account in the URL, not in a session variable that follows her between tabs.

3. Permissions

Permissions do not travel between accounts. The bookkeeper reads statements for both customers. If one of them makes her an administrator, that stops at their account. Put the role on the membership record, not on the user.

4. Isolation

Row-level security puts the check inside Postgres. You attach a policy to each table, and it runs on every query, comparing the login session against a tenant column in the row. Your application can ask for all 400 units and get back 12. Those policies cover rows only. Your 200 documents a month live in a storage bucket, which needs policies of its own and inherits nothing from the tables. Ask to see them.

5. Support access

Four of your staff answer the phone and need to see what the customer sees. Give the exception to those four, and log the name, the account, and the time on every visit. The risk is the administrator key, which Supabase calls the service role key and which ignores every policy you wrote. Find out which parts of your application use it.

6. Provisioning

Adding a customer means creating the account, inviting their users, and loading their first documents. You either do that yourself, or your developer does it every time. A table added next year can ship with no policy on it. Ask for a test that fails when one does.

7. Billing and offboarding

Attach the plan and the invoice to the account, not to a person. You also need an export and a delete that run for one customer and leave the other 39 alone. A cascade takes the rows and leaves the files. The delete has to reach the bucket as well.

Code-enforced or database-enforced isolation

Filters in the application code work until a developer writes one query without them. Nothing errors and nothing alerts, and the report carries another customer's rows. A policy in the database returns no rows instead, and the developer sees an empty screen. Row-level security has one gap. A process that runs without a login has no session for the policy to check, so it uses the administrator key. The overnight statement job and the payment webhook run that way.

Two objections

Owners raise two objections. One is that multi-tenancy belongs to software companies, and it belongs to every business that gives its customers a login. The other is "we filter by customer", which appears in most proposals. That is application-code filtering, and it is correct today.

Seven questions to send this week

Send these to your developer. The answer you want follows each one.

  1. If one person works for two customers, is that one login with two memberships, or two logins?

    One login with two memberships.

  2. Is the role on the membership record, or on the user?

    On the membership record.

  3. Does row-level security enforce the separation, or do filters in the application code, and does it cover the files?

    Row-level security, and no: the storage bucket needs policies of its own. Ask to see a request for another customer's rows come back empty.

  4. Which staff can open a customer account, is that logged, and where is the administrator key used?

    A named group, every access logged, and the key limited to processes you can list.

  5. What do I do to add customer 41, and what fails if a developer adds a table with no policy on it?

    A form you fill in yourself, and a test that fails.

  6. What is attached to the customer for billing, and what is attached to the person?

    The plan and the invoice to the customer, and nothing that matters to the person.

  7. What happens when a customer asks for all their data, or asks me to delete it?

    An export and a delete that run per customer, and the delete removes the files as well as the rows.