StarterApp Docs
Authentication

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_MODE controls 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.ts and determines whether BetterAuth allows users to create organizations and how the dashboard UI behaves.

Automatic onboarding

  1. Session created
    BetterAuth finalises the login (OAuth or credentials) and calls the Convex session trigger.

  2. User record synced (convex/auth.ts)
    The trigger links the BetterAuth user with the Convex user document, creating it if needed.

  3. Personal workspace provisioned
    If the user has no memberships, ensureActiveOrganizationForSession creates a personal organization, marks the user as owner, and stores the org metadata on the user document (activeOrganizationId, activeOrganizationSlug, etc.).

  4. Dashboard redirect
    The /auth/after route waits until the active organization is set before redirecting to the requested page.

Active organization state

  • Stored on the Convex user document under:
    • activeOrganizationId
    • activeOrganizationSlug
    • activeOrganizationName
    • activeOrganizationRoles
  • Exposed to the client through:
    • getCurrentSession() / requireUser() (server)
    • useSession() in React
    • api.auth.getCurrentUser Convex query
  • When a membership changes, ensureActiveOrganizationForSession updates the active org to keep everything consistent.

Working with organizations in code

  • Server components / pages
    Use getCurrentSession() or requireUser() and read user.activeOrganizationId to scope data requests.

  • Convex functions
    Use the identity helpers (getUserIdOrThrow, assertOwnerByUserId) and load organization data via Convex queries. The viewerId provided by userQuery/userMutation builders corresponds to the active BetterAuth user.

  • Client components
    Rely on useSession() or useQuery(api.auth.getCurrentUser) to render the active organization name, slug, or available roles.

  • Billing
    checkFeatureAccess and 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:

  1. The UI raises an organization switcher (see dashboard shell).
  2. A mutation updates the Convex user document and BetterAuth session metadata.
  3. 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

  • Always read activeOrganizationId from 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.