Source: https://www.hype-stack.dev/docs/backend/organizations

# Organizations

Organizations are the multi-tenant boundary in every starter. Users belong to organizations, invitations bring new
members in, and API routes scope work to the active `organizationId` on the request context.

The free [auth starter](/docs/packs-templates/packs/starter-auth-betterauth) has the same model underneath but hides it:
every account gets one organization named `Personal workspace` at creation, and there is no switcher, no members page,
and no way to send an invitation. That is what lets every feature pack, all of which scope data to the active
organization, install on it unchanged. Swap to a SaaS starter later and the personal workspace becomes the user's first
team.

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**

1. **withAuthUser** — Resolves the signed-in user and active organization membership.
2. **resolvePermissionsForMembership** — Loads the role permissions for that membership (WorkOS API or Prisma, depending on starter).
3. **buildAbilityFromPermissions** — Turns the permission list into a CASL ability on the Hono context.
4. **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](/docs/backend/mailing).

## Feature flags

The two SaaS starters ship per-organization feature flags. Flags are defined in code, in
`packages/enums/src/feature-flags/catalog.ts`, so a new flag is a typed key reviewed in a pull request, not a row
someone typed into a dashboard:

```ts
export const FEATURE_FLAGS = [
  {
    key: "beta-reports",
    name: "Reports",
    description: "The new reporting screens",
    kind: FeatureFlagKind.BETA,
    defaultEnabled: false,
  },
] as const satisfies readonly FeatureFlagDefinition[];
```

The catalog ships empty. The database stores only per-organization overrides (`OrganizationFeatureFlag`), and resolution
is one pure function: an override wins, otherwise the default, and a key no longer in the catalog is ignored. Retiring a
flag is deleting its entry, with no migration.

| Piece              | Where                                                                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| Resolved flags API | `GET /organizations/feature-flags`                                                                                                     |
| Backend gate       | `withFeature("beta-reports")` middleware, or `isFeatureEnabled` inside a module                                                        |
| Frontend gate      | `useFeatureFlag("beta-reports")` or `<FeatureGate flag="beta-reports">` in `features/feature-flags/`, same on mobile and the extension |
| Admin              | A **Flags** tab on every organization's detail page, with set and reset per flag                                                       |

There are no percentage rollouts and no per-user targeting. The unit is the organization, which is also the billing
unit, so an entitlement flag and a plan line up. The free auth starter does not ship flags.

The admin org page is extensible the same way: feature packs add tabs next to Flags through the `orgDetailsTabs`
manifest field. The AI chat pack uses it for its per-organization usage tab.

## Teams pack relationship

[`pack-teams`](/docs/packs-templates/packs/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`](/docs/packs-templates/packs/pack-projects) is the same idea for projects and project members.

## Practical guidance

- Scope queries by `organizationId` from 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](/docs/packs-templates/packs/admin), not the customer
  frontend.

## Related

- [Authentication](/docs/backend/authentication)
- [Admin app](/docs/packs-templates/packs/admin)
- [Teams pack setup](/docs/packs-templates/packs/pack-teams)
