HelloJohn / docs
Multi-tenancy

Multi-tenancy Overview

How HelloJohn's multi-tenancy model works — tenants, organizations, isolated user pools, and per-tenant configuration.

HelloJohn is built multi-tenant from the ground up. Every user, session, and configuration lives inside a tenant — a fully isolated workspace.

The tenancy model

HelloJohn Instance
└── Tenant A (your SaaS customer "Acme Corp")
│     ├── Users
│     ├── Auth config (own OAuth apps, MFA policy, etc.)
│     └── Organizations (optional sub-groups within the tenant)
└── Tenant B (your SaaS customer "Globex Inc")
│     ├── Users
│     ├── Auth config
│     └── Organizations
└── Tenant C (your internal app)
      └── Users

What is a tenant?

A tenant is an isolated namespace for a group of users. Think of it as a workspace, organization, or account in your SaaS product.

Tenants are fully isolated:

  • A user in Tenant A cannot authenticate to Tenant B (unless explicitly invited)
  • Tokens issued for Tenant A are rejected by Tenant B
  • Auth config (OAuth apps, MFA rules, allowed domains) is independent per tenant

What are organizations?

Organizations are sub-groups within a tenant. Use them when your tenants need their own internal structure:

Tenant: Acme Corp
├── Org: Engineering
├── Org: Sales
└── Org: Design

Organizations have their own members and roles. A user can belong to multiple orgs within a tenant.

Organizations are optional. If your SaaS product doesn't need sub-groups within a customer account, you don't need to use organizations at all.

Tenant ID in JWTs

Every access token includes the user's active tenant:

{
  "sub": "usr_01HX...",
  "tenant_id": "ten_01HX...",
  "org_id": "org_01HX...",  // if in an org
  "roles": ["admin"]
}

Your backend uses tenant_id to scope data — always filter queries by tenant:

// Good — scoped to tenant
users, err := db.Query("SELECT * FROM users WHERE tenant_id = $1", claims.TenantID)

// Bad — returns data from all tenants
users, err := db.Query("SELECT * FROM users")

Common patterns

B2B SaaS (one tenant per customer)

Each customer gets their own tenant. You create the tenant when they sign up:

POST /v2/admin/tenants
{
  "name": "Acme Corp",
  "slug": "acme-corp",
  "owner_email": "admin@acme.com"
}

Users at Acme sign up/in under that tenant. Their data is isolated from all other tenants.

Single-tenant app

Create one tenant for all your users. Most apps start here and add multi-tenancy later.

Consumer app with workspaces

Each workspace is a tenant. Users can switch between workspaces (tenants) using different refresh tokens.

Switching tenants

A user can have sessions in multiple tenants. The SDK handles tenant switching:

const { switchTenant } = useAuth()
await switchTenant('ten_01HY...')
// User is now authenticated in the new tenant
// access_token has new tenant_id claim

Next steps

On this page