Every aggregate hangs off a workspace. Workspace slugs in the URL, branded WorkspaceId everywhere, transfer of ownership built in. There is no leakage between tenants because the type system won't let you write a query that crosses one.
Most SaaS templates bolt tenancy on as an afterthought. Orbit treats the workspace as the tenant root: every domain object belongs to a workspace, every server route is workspace-scoped, every realtime channel is keyed by workspace. Add nested teams, two-scope PBAC, and an audit log and you have what enterprise customers actually ask for.
Every aggregate hangs off a workspace. Workspace slugs in the URL, branded WorkspaceId everywhere, transfer of ownership built in. There is no leakage between tenants because the type system won't let you write a query that crosses one.
Optional second tier of grouping. A team lives inside a workspace, carries its own members and roles, and adds a second permission scope. Useful for departments, sub-projects, or customer-managed groups.
WorkspaceRole ↔ WorkspacePermission and TeamRole ↔ TeamPermission. System roles (owner / admin / member) plus arbitrary custom roles. Server middleware (requirePermission, requireTeamPermission) and client hooks (useCan, useCanTeam) share one vocabulary.
An in-process WebSocket hub broadcasts domain events to workspace channels. Presence tracker keeps a 30-second grace window. The frontend store applies events as they arrive — your UI stays consistent across tabs and members.
Workspace-scoped log for tenant admins (members invited, roles changed, billing updated) and an app-wide log for platform moderation (bans, impersonations). Materialised by a post-commit projector listening to domain events.
Single Postgres database, row-level tenancy via WorkspaceId. Branded prefixed UUIDv7 IDs (newId('team')) make it impossible to mix scopes. DDD bounded contexts keep the code organised as the product grows.