Auth is Clerk. The frontend uses @clerk/nextjs (ClerkProvider in app/layout.tsx, route protection in proxy.ts — Next 16 renames middleware.tsproxy.ts). The backend uses @clerk/express (clerkMiddleware + requireAuth).

Onboarding

A signed-in user with no active org is routed by proxy.ts to /onboarding, which renders Clerk’s <CreateOrganization>. The dashboard then calls POST /api/orgs/sync (idempotent), which upserts the active org into the Postgres organizations table. This is on-demand sync — no webhooks. A Svix-verified Clerk webhook is the production upgrade if orgs are ever created outside the app.

Teams, roles & members

Team, roles, and members are Clerk-native — there are no DB tables for them.
  • Roles are Clerk’s defaults: org:admin and org:member.
  • The dashboard (/) shows a read-only People list via useOrganization({ memberships }).
  • /team renders <OrganizationProfile> for full management — member list, role changes, invites, and approving join requests. Clerk gates admin-only actions by role.
  • The header has <OrganizationSwitcher> for org switching and the request-to-join button.
“Request to join” needs verified domains (enabled on the instance via clerk enable orgs --domains). An org admin adds/verifies a domain and sets enrollment mode to “Automatic suggestion” in /team. In dev, a domain auto-verifies if the admin’s own email is on it.