Organizations
Organizations are the multi-tenant boundary in both SaaS starters. Users belong to organizations, invitations bring new
members in, and API routes scope work to the active organizationId on the request context.
Credential-free setup notes live on the starter pack pages. This page is the domain model and code paths.
What ships with a starter
| Concept | Role |
|---|---|
| Organization | Tenant boundary for data and settings |
| Membership + role | Ties a user to an org with a permission preset (admin, member, ...) |
| Invitation | Email invite into an organization |
| Permissions (CASL) | Server-side hasAccess checks mirrored in the UI |
| Onboarding | First-time flow to create or join an org before the private app |
Where the code lives
| Piece | Path pattern |
|---|---|
| Organization features | apps/backend/src/features/organizations/modules/ |
| Organization routes | apps/backend/src/routes/organizations/ |
| Invitation sockets | apps/backend/src/sockets/invitations/ |
| Permission presets / subjects | packages/enums (or the enums package your stack installs) |
hasAccess middleware | apps/backend/src/middleware/permissions/has-access.ts |
| Frontend org settings | apps/frontend/src/features/settings/modules/organization-tab/ |
| Onboarding routes | apps/frontend/src/routes/onboarding/ |
WorkOS vs Better Auth membership storage
| WorkOS starter | Better Auth starter | |
|---|---|---|
| Memberships | WorkOS organization memberships API | Local Prisma Member + Organization models |
| Invitations | Local OrganizationInvitation plus email | Same invitation feature modules; Better Auth's own Invitation model is unused by the app flow |
| Org settings / logos | Local Prisma + storage uploads | Same |
Both starters expose the same product UX: create org, invite members, manage roles, enforce hasAccess on routes.
Permission flow
From session to route gate
withAuthUser
Resolves the signed-in user and active organization membership.
resolvePermissionsForMembership
Loads the role permissions for that membership (WorkOS API or Prisma, depending on starter).
buildAbilityFromPermissions
Turns the permission list into a CASL ability on the Hono context.
hasAccess({ action, subject })
Route middleware denies the request when ability.can fails.
When you add a new domain (for example teams or projects), register subjects and grant them on the right roles. The teams pack notes that you may need to add CASL subjects manually after install.
Invitations
Organization invitation modules handle send, accept, and resend. Accepting an invite attaches the user to the org and continues into the private app. Email delivery needs Resend configured; see Mailing.
Teams pack relationship
pack-teams adds teams inside an organization (Team, TeamMember, team
invitations). It does not replace org membership. Org role still gates who can manage teams; team membership is a second
layer for collaboration inside one tenant.
pack-projects is the same idea for projects and project members.
Practical guidance
- Scope queries by
organizationIdfrom context. Do not trust a client-supplied org id without membership checks. - Keep new permission subjects in the shared enums package so frontend and backend stay aligned.
- For admin-wide views across all orgs, use the admin app, not the customer frontend.
Related
Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!
