Organizations
How StarterApp manages personal and shared workspaces
StarterApp treats every authenticated user as part of at least one organization. On first sign-in we create a personal workspace automatically; as teams grow, users can join or create shared organizations. This page explains how that lifecycle works and how the active organization flows through the app.
Why it matters
Nearly every dashboard view depends on which organization is active—billing, resources, navigation, and Convex permissions all key off the organisation ID.
Feature toggle
ORG_MODEcontrols whether shared organizations are available.ORG_MODE=true→ full organization features (shared workspaces, membership management, organization switcher).ORG_MODE=false(default) → “shadow” mode where every user gets a personal workspace but cannot invite others or create additional orgs.
- The value is read in
convex/lib/auth.tsand determines whether BetterAuth allows users to create organizations and how the dashboard UI behaves.
Automatic onboarding
-
Session created
BetterAuth finalises the login (OAuth or credentials) and calls the Convex session trigger. -
User record synced (
convex/auth.ts)
The trigger links the BetterAuth user with the Convexuserdocument, creating it if needed. -
Personal workspace provisioned
If the user has no memberships,ensureActiveOrganizationForSessioncreates a personal organization, marks the user as owner, and stores the org metadata on the user document (activeOrganizationId,activeOrganizationSlug, etc.). -
Dashboard redirect
The/auth/afterroute waits until the active organization is set before redirecting to the requested page.
Active organization state
- Stored on the Convex
userdocument under:activeOrganizationIdactiveOrganizationSlugactiveOrganizationNameactiveOrganizationRoles
- Exposed to the client through:
getCurrentSession()/requireUser()(server)useSession()in Reactapi.auth.getCurrentUserConvex query
- When a membership changes,
ensureActiveOrganizationForSessionupdates the active org to keep everything consistent.
Working with organizations in code
-
Server components / pages
UsegetCurrentSession()orrequireUser()and readuser.activeOrganizationIdto scope data requests. -
Convex functions
Use the identity helpers (getUserIdOrThrow,assertOwnerByUserId) and load organization data via Convex queries. TheviewerIdprovided byuserQuery/userMutationbuilders corresponds to the active BetterAuth user. -
Client components
Rely onuseSession()oruseQuery(api.auth.getCurrentUser)to render the active organization name, slug, or available roles. -
Billing
checkFeatureAccessand Autumn usage checks accept both the user ID and the organization ID; pass the active organization when billing is tied to a workspace instead of an individual.
Switching organizations (shared workspaces)
When users belong to multiple organizations:
- The UI raises an organization switcher (see dashboard shell).
- A mutation updates the Convex user document and BetterAuth session metadata.
- The next render pulls the new
activeOrganization*fields through the standard helpers.
Because the active organization is centralised, every downstream feature (billing, navigation, resource loading) automatically reflects the new workspace.
Key files
Prop
Type
Quick checklist for org-related work
- Always read
activeOrganizationIdfrom the session/user helpers instead of guessing. - When creating organization-specific records, store the organization ID along with the user ID.
- After mutations that change memberships, call the Convex mutation that updates the active organization state if needed.
- Keep personal workspace behaviour intact—users should never get stuck without an active organization.