---
title: Multi-tenancy
description: How organizations act as the tenant boundary - the organization model, lifecycle, memberships, and the active-organization context.
---

Multi-tenancy is the backbone of Africa OS. An **organization** is the tenant: the unit that owns data, the unit that applications get enabled for, and the boundary every permission is scoped to. This page explains the model in code.

### The core model

```mermaid
flowchart TB
    User["platform.users - a platform profile"]
    Identity["public.user - the identity (Better Auth)"]
    Org["platform.organizations - a tenant"]
    Member["platform.organization_members - who belongs"]
    Role["platform.roles - what members can do"]
    App["platform.applications - registry"]
    OrgApp["platform.organization_applications - which apps a tenant can use"]

    Identity -->|identity_id| User
    User -->|user_id| Member
    Org -->|organization_id| Member
    Member -->|member_id| Role
    App -->|application_id| OrgApp
    Org -->|organization_id| OrgApp
```

Two separate notions of "user" exist, and it is important to keep them straight:

- **`public.user`** is the identity row that Better Auth owns (login, sessions, accounts). Its `id` is the identity id.
- **`platform.users`** is the platform profile, keyed by `identity_id`. Its `id` is the platform user id that memberships, roles, and permissions reference.

The request runtime bridges the two: it authenticates the identity, then upserts the platform profile, and everything downstream works with the platform user id.

### The organization lifecycle

Every organization moves through a state machine, defined in `platform/organizations/src/lifecycle.ts`:

```mermaid
stateDiagram-v2
    [*] --> pending : created
    pending --> reviewing : submitted for review
    pending --> suspended : rejected early
    reviewing --> verified : approved
    reviewing --> suspended : rejected
    verified --> suspended : suspended
    suspended --> reviewing : back to review
    suspended --> [*]
```

The transitions table:

```ts
export const organizationTransitions = {
  pending: ["reviewing", "suspended"],
  reviewing: ["verified", "suspended"],
  verified: ["suspended"],
  suspended: ["reviewing"],
};
```

Verification is a manual administrative step performed in the admin console. Any transition not listed fails with `IllegalOrganizationTransition`. New organizations are created as `pending`, and the creator becomes an `active` member holding the `owner` role.

### Memberships

A membership is the link between a user and an organization, and it has its own lifecycle (`platform/organizations/src/membership.ts`):

```mermaid
stateDiagram-v2
    [*] --> invited : invited by email
    invited --> active : accepted / activated
    invited --> removed : invite revoked
    active --> suspended : suspended
    active --> removed : removed
    suspended --> active : reactivated
    suspended --> removed : removed
    removed --> [*]
```

The transitions table:

```ts
export const membershipTransitions = {
  invited: ["active", "removed"],
  active: ["suspended", "removed"],
  suspended: ["active", "removed"],
  removed: [],
};
```

A removed member cannot come back through the state machine - they must be re-invited. `MembershipService.invite` looks the user up by email on `public.user`, refuses duplicate memberships with `AlreadyMember`, and inserts the membership as `invited`.

### Creating an organization, transactionally

`OrganizationService.create` runs inside `withTransaction`, so the whole creation is atomic. Inside the transaction it:

1. Slugifies the name, deduplicating until the slug is free (`name`, `name-2`, `name-3`, ...).
2. Inserts the organization with status `pending`.
3. Inserts the creator as an `active` member.
4. Creates an `owner` role scoped to the organization, wired to **every registered permission**.
5. Grants the owner role to the creator's membership.

The result: the founder can do anything inside their organization until it builds its own access model.

```ts
const organization = yield* OrganizationService.create({
  name: "Acme Retail",
  ownerUserId: platformUserId,
});
```

### Listing a user's organizations

`OrganizationService.listForUser(userId)` returns every organization where the user has an **active** membership, ordered by creation. This is what the web app's organization switcher shows, and it is the input to active-organization resolution.

### The active organization

A user can belong to many organizations, but a request happens in one. The request runtime resolves the active organization (`platform/request-context/src/runtime.ts`):

1. A `:organization` slug in the URL wins, but only if it resolves to a real organization that is one of the user's active memberships (`NotOrganizationMember` otherwise).
2. Otherwise, the `africaos.active_org` cookie wins if it names an active membership.
3. Otherwise, the first active membership.

The resolved organization is provided to programs as `CurrentOrganization`. A user with no memberships carries `Option.none()`, meaning no tenant context.

### Tenancy rules that never change

- **The tenant id always comes from the request runtime**, never from client input. Server functions receive `CurrentOrganization` and use its `id`; client-supplied organization ids are rejected or ignored.
- **Platform queries are scoped by the caller.** The database layer provides `orgWhere`/`orgScope` helpers to keep queries tenant-scoped; see [Database](/platform/database).
- **Application availability is an organization concern.** Whether an org can use an application is decided by `organization_applications`, checked by `RegistryService.getForOrganization`.
- **Permissions are resolved per organization.** The same user may hold different capabilities in different organizations.

### Next steps

- [Application registry](/architecture/application-registry) - how organizations enable and launch applications.
- [Permissions](/architecture/permissions) - how memberships become capabilities.
- [Organizations (platform)](/platform/organizations) - the full service surface.