Blog
Explainer

What is multi-tenancy, and why the tenant is whoever pays

Multi-tenancy is one app serving many customers with their data kept apart. The decision that costs you later is which object in your schema is the tenant.

Dharmendra Jagodana8 min read

In short

Multi-tenancy means one running copy of an application serves many customers, called tenants, and no tenant can see another tenant's data. The decision that outlives the database layout is which object is the tenant: the one that owns the rows, holds the subscription and is counted against the quota.

Multi-tenancy means one running copy of an application serves many customers, called tenants, and no tenant can see another tenant's data. GitHub is the familiar example: every organization runs on the same service, and each one owns its own repositories.

Most explanations then spend the rest of the page on database layouts. The layout matters. It is also the part you can change later. What you cannot cheaply change is which object in your schema is the tenant: the thing that owns the rows, holds the subscription and gets counted against the quota. Get that wrong and the fix is a migration across every table you own.

Why it exists

The alternative is one deployment per customer. That is single-tenancy, the classic installed-software model: each customer gets their own copy, their own database and their own upgrade schedule. Fifty customers means fifty upgrades, and fifty chances for one customer's copy to drift away from everyone else's.

Multi-tenancy collapses that to one deployment. AWS's SaaS Architecture Fundamentals whitepaper, which AWS now marks as historical reference, calls every tenant running the same version "one of the fundamental distinguishing attributes of a SaaS environment." The price is that isolation stops being a fact about your infrastructure and becomes a rule your code has to keep on every query.

The tenant is whoever pays

The tenant is the customer: the account that signs up, holds the card and owns the data. The AWS SaaS Lens puts it as "any customer that you sign up to use your SaaS environment is one of the tenants of your system." The whitepaper quoted above adds the sentence that causes all the trouble: a tenant "could be a company with many users, or it could correlate directly to an individual user."

At signup those two look identical. One person arrives, enters a card, creates some data. Nothing distinguishes "this user is the customer" from "this user is the first member of a customer." So the plan column goes on the users table, every row gets a user_id, and it all works until a second person from the same company signs up and asks to see the first one's work.

So ask who pays. In a consumer product the payer is a person, and the person can be the tenant. In a B2B product the payer is a company. The people are members who join, change roles and leave while the company goes on paying, which makes the tenant a separate object - an organization, a team, an account, a workspace, whatever your product calls it - with users attached to it through a membership.

How it works: three things, one owner

A multi-tenant schema has one tenant table, and three things point at it: the data, the subscription and the usage counters. A membership table connects users to tenants. Every request works out which tenant it is acting for, checks that the user is a member of it, and filters every query on that tenant's ID.

tenants        (id, name, stripe_customer_id)
users          (id, email)
memberships    (tenant_id, user_id, role)
subscriptions  (id, tenant_id, plan, status)
projects       (id, tenant_id, created_by, name)
usage          (tenant_id, metric, period, count)

Look at what is missing. There is no plan on users, and projects records who created a row without that person owning it. Anything the customer paid for reaches a user only through memberships. The tenant ID can arrive in the URL, but nothing trusts it until the server has checked membership - in an Express API that is one middleware that refuses non-members before any handler runs.

The failures worth knowing about come from pointing the three at different objects. Each one looks fine in a demo.

Rows on the user, plan on the team. A member leaves. Their projects leave with them, or sit orphaned, while the team keeps paying for a plan that no longer shows its own work.

Plan on the user, rows on the team. The person who entered the card leaves. The team's data now hangs off a subscription nobody left in the team can manage, and closing that one account cancels it for everyone.

Quota on the user, plan on the team. A plan that includes 10,000 API calls a month quietly becomes 10,000 per member. Inviting a viewer is a free upgrade.

Key takeaway

Multi-tenancy is one deployment serving many customers with their data kept apart. The decision that outlives the database layout is which object is the tenant. Make it whoever pays, then point the data, the subscription and the quota at that same object.

Once users are members rather than owners, one person can belong to several tenants, and in B2B sooner or later somebody does: the agency, the founder with two companies. GitHub's docs say it flatly - "each user can be a member of multiple organizations." That turns "which tenant am I in right now" into state the server resolves on every request.

GitHub also shows the other honest answer to "who is the tenant", which is both. A personal account and an organization can each own repositories, each with its own plan. It works, and it means every question about who owns a repository has two possible answers. Choose that on purpose or not at all.

When it is the wrong choice

Multi-tenancy is the wrong call in two separate situations: when a customer's contract rules out sharing, and when the tenant would be one person who stays one person.

A shared deployment is wrong when the contract says so. Some customers require that their data sits on infrastructure no other customer touches, or inside their own network. For them you are in single-tenant territory whatever the rest of your customers get. Self-hosted BuildBase is that shape: the tenant server runs on the customer's own infrastructure, and per our docs the central server never sees their application data.

A team-shaped tenant is wrong when the payer is a person and will stay one. A journaling app, a personal budget tracker. There the membership table is a join nobody needs, and a workspace switcher would have one entry forever. Let the user be the tenant and move on.

The catch is "will stay one." If two people could plausibly end up on one bill - a couple sharing a budget, a freelancer who hires an assistant - build the tenant object now. Adding it later means rewriting every table where user_id quietly means "owner", and that is a migration rather than a feature.

Where we drew the line

In BuildBase the tenant object is called a workspace, and the decision is already made: the workspace owns the bill. The workspace record carries the Stripe customer ID, the reference to its subscription, its quota usage, its feature flags and its credit balance. Members join it with a role (admin, editor and viewer are built in). Every subscription, usage and credit call in the server SDK that acts on an account takes a workspace ID first, never a user ID:

const { subscription, plan } = await bb.subscription.get(workspaceId);

await bb.usage.record(workspaceId, { quotaSlug: 'api-calls', quantity: 1 });
await bb.credits.consume(workspaceId, { amount: 50 });

A new user gets a workspace on first sign-in by default, so someone signing up alone is a team of one rather than a user with a plan column. The first teammate they invite joins a tenant that already had the right shape. Whether that first workspace is created automatically is configurable.

Two of the products we run on it are built around this. AgentCenter is built on multi-project workspaces with role-based permissions and tiered subscriptions; PlugNode on team workspaces with usage-based credit metering.

One rule in that model is "a workspace has at most one live subscription." The application refuses a second checkout, and a unique index in the database is meant to back that refusal up, because two live subscriptions on one workspace is one customer charged twice.

For a while the backstop did not exist. It shared a default name with a plain index on the same field, the database refused the second one, and the code that builds indexes logged the refusal and carried on. Until the index got a name of its own, the application check was the only thing in the way, and a name only makes it buildable: the index still has to be built on every tenant database.

The lesson is not that indexes are fiddly. A rule about who owns a bill deserves a database constraint, and somebody has to go and check the constraint exists.

Our own customers sit one level up. Each org building on BuildBase gets its own database, and the workspaces inside it share that org's schema. Per-tenant at the level where each tenant is a company building a product; shared at the level where tenants are many and self-serve.

What multi-tenancy is not

It is not a database layout. Shared tables, a schema per tenant and a database per tenant are three ways to isolate tenants once you know what a tenant is, and the choice between them turns mostly on how many times you are willing to run each migration. Database-per-tenant vs shared schema argues that fork, and what database-per-tenant actually costs prices the per-tenant end of it.

It is not access control either. Roles decide what a member may do inside a tenant. Tenancy decides which tenant's data a request can reach at all, and an admin role that is not scoped to a tenant is an admin of every customer you have.

And the quota hanging off the tenant is not the same thing as a credit balance, even though both belong to it. One resets with the subscription; the other is a balance the customer already paid for. Quotas are not credits sorts out which one a plan needs.

The working-code version of all this, with the workspace as the tenant from the first commit, is multi-tenant workspaces in an afternoon. The API surface is in the workspaces docs.

tenancy
multi-tenant
workspaces
architecture

Frequently Asked Questions

What is the difference between single-tenant and multi-tenant?

Single-tenant gives each customer its own deployment: its own application instance and its own database. Multi-tenant runs one deployment for every customer and keeps their data apart in software. Single-tenant isolates by infrastructure and costs one upgrade per customer. Multi-tenant upgrades everyone at once and moves the isolation into your queries.

What is a tenant in SaaS?

A tenant is a customer of the service: the account that signs up, pays and owns data. In consumer software that is usually one person. In B2B software it is a company or a team, and the people in it are members who can join, change role and leave without the tenant changing.

How is tenant data isolated in a multi-tenant database?

Three common ways: shared tables where every row carries a tenant ID and every query filters on it, a separate schema per tenant, or a separate database per tenant. Shared tables are the default for self-serve products. The per-tenant layouts buy stronger separation and pay for it by running every migration once per tenant.

What is an example of a multi-tenant SaaS application?

GitHub. Every organization runs on the same service and owns its own repositories, and one person can be a member of several organizations. GitHub also lets a personal account own repositories, so it has two kinds of tenant rather than one.

Put this into practice

The modules in this post are one console away. 7-day free trial, no credit card.

7-day free trialNo credit card requiredCancel anytime