Skip to content

Organizations

Organizations group users into shared workspaces. Each member has a role, owners and admins invite people by email, and a user can belong to several organizations and switch between them. Turn them on when your data belongs to a team rather than to one user. They run on Better Auth’s organization plugin.

Turn it on

Organizations need sendEmails: true for invitations, and TypeScript rejects the flag without it:

src/convex/auth.constants.ts

Set up Resend as described in Sending emails. The root layout from the base registry item already mounts the organization switcher and the organization profile dialog when organizations is on, so the scaffold needs nothing else. To place them in your own layout, see <OrganizationSwitcher /> and <OrganizationProfile />.

Personal organization and active organization

Every user who signs up while organizations is on gets an organization named “Personal Organization” and becomes its owner. Its slug is personal-organization- followed by the letters of the user’s name, lowercased and joined with hyphens (personal-organization-ada-lovelace), or personal-organization when the name has no letters. Users who signed up before you turned organizations on get none; they can create one from the switcher.

Each session has an active organization. A new session starts with one, and the switcher changes it. Queries such as the role check below read the active organization unless you pass another id.

Roles

There are three roles. The components show each role only what it may do:

RoleCan
ownerEverything an admin can, and delete the organization. Must choose a new owner before leaving.
adminOpen the organization profile from the switcher. Edit the name, slug and logo. Invite people, cancel invitations, change members’ roles and remove members, except owners.
memberWork in the organization and leave it from the switcher. The organization profile isn’t offered to members.

New invitations are for the member or admin role. The components only hide actions; Better Auth’s organization plugin checks permissions on the server with its default roles, where only an owner can delete an organization and members can’t invite or manage anyone.

Invitations

Owners and admins invite people from the Members tab of the organization profile. Each address gets the invitation email, “Join <organization> on <brandName>”, with a link to:

The organizations/routes registry item installs that page for both frameworks.

  • The link’s origin comes from the request that sent the invitation. Its host must be in BETTER_AUTH_ALLOWED_HOSTS, or sending fails with “Unable to resolve an allowed invitation base URL from the current request.”
  • The page requires a session. A signed-out recipient is sent to sign-in and comes back to the link.
  • The recipient must be signed in with the invited address. Better Auth refuses anyone else with “You are not the recipient of the invitation”.
  • Invitations expire after 48 hours, Better Auth’s default.
  • Once accepted, the page goes to /.

Put active-organization (or the shorter active-org) in a URL where your routes expect an organization slug, and it’s replaced with the slug of the user’s active organization:

  • /active-organization becomes /acme
  • /active-organization/settings becomes /acme/settings
  • /dashboard/active-org/billing becomes /dashboard/acme/billing

Use it in emails, support articles and onboarding, where you can’t know the user’s organization. How the replacement happens depends on the framework:

  • SvelteKit. <OrganizationSwitcher /> rewrites the URL on the client once the active organization has loaded. There’s no server route, so the switcher must be mounted on the page the link opens, as the root layout does.
  • Next.js. src/app/active-organization/[[...all]]/page.tsx redirects on the server for paths that start with /active-organization. A user with no active organization goes to /organization/create, then back. The mounted switcher also rewrites active-org and segments deeper in the path on the client.

When a user switches organizations and the current path contains the old slug as a segment, the switcher swaps in the new one: /acme/products becomes /globex/products.

Check roles with useRoles

useRoles tells a component the current user’s role in the active organization, or in another organization you name. Use it to hide controls; the server still decides what the user may do.

src/routes/settings/+page.svelte
src/components/ManageMembersButton.tsx

It returns these properties. The checks are false while the role is loading and when the user isn’t a member.

PropertyTrue when
hasOwnerRoleThe user is an owner.
hasAdminRoleThe user is an admin.
hasOwnerOrAdminRoleThe user is an owner or an admin.
isMemberThe user has any role in the organization.
roleThe role itself, 'owner', 'admin' or 'member', or null/undefined when there is none.

SvelteKit

  • Call it once, at the top level of the component’s <script>. It reads the auth context and subscribes to a Convex query, and both only work while the component is being set up. Calling it inside $derived, $effect or an event handler fails.
  • The returned object is reactive. Read roles.hasOwnerRole in markup or in $derived; don’t copy it into a plain variable.
  • initialData is the role that +layout.server.ts (from the base/ssr item) loads for the active organization, so the first render already knows it. Pass it only when you check the active organization.
  • orgId checks another organization. The query reads it each time it runs, so to follow a value that changes, pass a getter:
src/lib/OrganizationRow.svelte

Next.js

It’s a React hook, so call it in a client component inside the app’s ConvexClientProvider. For the active organization it starts from the role the root layout loaded on the server. useRoles({ orgId }) checks another organization, and re-renders when orgId changes.