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:
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:
| Role | Can |
|---|---|
owner | Everything an admin can, and delete the organization. Must choose a new owner before leaving. |
admin | Open the organization profile from the switcher. Edit the name, slug and logo. Invite people, cancel invitations, change members’ roles and remove members, except owners. |
member | Work 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
/.
Link to the active organization
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-organizationbecomes/acme/active-organization/settingsbecomes/acme/settings/dashboard/active-org/billingbecomes/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.tsxredirects 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 rewritesactive-organd 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.
It returns these properties. The checks are false while the role is loading and when the user isn’t a member.
| Property | True when |
|---|---|
hasOwnerRole | The user is an owner. |
hasAdminRole | The user is an admin. |
hasOwnerOrAdminRole | The user is an owner or an admin. |
isMember | The user has any role in the organization. |
role | The 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,$effector an event handler fails. - The returned object is reactive. Read
roles.hasOwnerRolein markup or in$derived; don’t copy it into a plain variable. initialDatais the role that+layout.server.ts(from thebase/ssritem) loads for the active organization, so the first render already knows it. Pass it only when you check the active organization.orgIdchecks another organization. The query reads it each time it runs, so to follow a value that changes, pass a getter:
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.
Related
- <OrganizationSwitcher />, <OrganizationProfile /> and <CreateOrganization />: the components and how to mount them.
- Sending emails: the invitation email.
- Configure features: every flag and environment variable.