# Tenants, Users and Memberships — Multi-Tenant SaaS Architecture

Source: https://www.skillbyai.com/en/multi-tenant-saas/mt-tenant-vs-user

> Model tenants, users and roles correctly so one person can belong to several tenants, and avoid the classic mistake of hanging data off the user.

## Users live inside tenants (usually)

A **user** is a person who logs in; a **tenant** is the organisation that owns data. The relationship between them is often many-to-many: a chartered accountant may work in the accounts of several client companies, and a freelancer may join many workspaces. So most B2B SaaS products keep three things: a `tenants` table, a `users` table (one identity per person) and a **membership** table holding `(tenant_id, user_id, role)`. Business data such as invoices or projects carries a `tenant_id`, not a `user_id`, because the company owns it even after an employee leaves. When a user signs in, the app either asks which tenant to open or picks a default, and from then on every request runs *as that user inside that tenant*. Some consumer products are simpler: each user is their own tenant. Decide this early, because changing it later means rewriting authorisation and data ownership.

## Modelling memberships

One person, two tenants, a different role in each. Roles belong to the membership, not to the user.

```python
from dataclasses import dataclass, field

@dataclass
class Tenant:
    id: str
    name: str
    plan: str

@dataclass
class User:
    email: str
    memberships: dict = field(default_factory=dict)  # tenant_id -> role

acme = Tenant("t_acme", "Acme Pvt Ltd", "pro")
zen = Tenant("t_zen", "Zen Foods", "free")

priya = User("priya@example.com")
priya.memberships[acme.id] = "admin"
priya.memberships[zen.id] = "viewer"   # one person, two tenants

for tenant_id, role in priya.memberships.items():
    print(f"{priya.email} is {role} in {tenant_id}")
```

Output:

```
priya@example.com is admin in t_acme
priya@example.com is viewer in t_zen
```

## Data belongs to the tenant

A frequent design bug is `projects.owner_user_id` as the only ownership column. When that employee leaves and their account is deleted, the company's projects become orphans. Store `tenant_id` on every business row, and keep `created_by` only as an audit fact.

**Quiz:** In a B2B SaaS, which column should decide who owns an invoice row?

- [ ] The id of the user who created it
- [ ] The id of the user currently logged in
- [ ] The email domain of the creator
- [x] The tenant_id of the organisation it belongs to

*Answer:* The tenant_id of the organisation it belongs to. The organisation owns business data. Users come and go, and email domains are not reliable identifiers, so ownership and isolation are keyed on tenant_id.
