Multi-Tenancy

Isolated workspaces, members, and invites - resolved per request with withTenant.

A tenant is an isolated workspace. Membership links users to tenants with a role. Requests select the active tenant via the X-TENANT-ID header.

Resolving the Tenant - withTenant

import { withTenant } from "@/plugins/Huruf-Tech/thunder-core/utils/withTenant.ts";

const { tenant, member, user, owner, getParentMember } = await withTenant(req);

const maybe = await withTenant(req, true); // silent mode

withTenant resolves the auth session, requires X-TENANT-ID, then loads the tenant plus the caller's membership. It throws Response.forbidden() if the user is not a member or the tenant doesn't exist.

For non-owner members it also returns:

  • owner - the tenant creator.
  • getParentMember() - a lazy lookup of the inviter's membership, used for RBAC role cascading.

Pair withTenant with createCRUD's isolationFields to scope a collection per tenant automatically. See the Posts CMS example for a complete multi-tenant CRUD walkthrough.

Tenancy Routes

Read each file for endpoints and exact behavior:

Route fileResponsibility
routes/tenants.tsTenants. Creating one atomically creates an owner member; deleting one removes its members.
routes/tenantMembers.tsMembers. The create handler consumes a tenantInvite.
routes/tenantInvites.tsInvites. Validates the invited role against the caller's subRoles, renders the invite URL, and emails it.
routes/tenantMemberships.tsLists the current user's memberships with the joined tenant document.

The X-TENANT-ID header is allow-listed for CORS by the essentials hook, so browser clients can send it cross-origin.


On this page