Organizations

Organizations

Teams, workspaces or companies, with members, invitations and teams.
An organization's page: its members with their roles, and tabs for invitations, teams, roles and SSO

Better Auth's Organization plugin, as it is, for whatever your app uses organizations for: teams, workspaces, companies.

Turning it on

Enable it

nuxt.config.ts
export default defineNuxtConfig({
  betterAuthUi: {
    organization: { enabled: true }
  }
})

Run Better Auth's migrations

The plugin adds organization, member and invitation tables, and the session's activeOrganizationId. Teams and dynamic roles add their own tables too.

Send invitations by email (optional)

Invitations are emailed when emails.send is set, with the organization-invitation template. Their links can be copied either way.

What people get

On the account page

An "Organizations" tab with their organizations (and their teams in each), the active one marked, switching to another, and creating one: a name, a slug made from it and checked as they type, and your own fields. Invitations sent to them show at the top, with Accept and Decline. Better Auth lists them only for a verified email.

The organization's page

/organization/<id>, for signed-in members. Each part shows to those whose role allows it, from Better Auth's own permission check, so your roles and dynamic roles count:

TabWhat it doesPermissions
GeneralName, slug, logo and your fields; leaving; deletingorganization:update, organization:delete
MembersA page at a time, with your member fields; changing roles (several each); removingmember:update, member:delete
InvitationsInvite by email with roles, a team and your fields; send again, copy the link, cancelinvitation:create, invitation:cancel
TeamsCreate, rename and remove teams; add and remove members; set your active teamteam:*
RolesThe app's roles and what each allows; the organization's own rolesac:*
SSOThe organization's identity providers, for owners and adminsSee Single sign-on

The invitation page

The email's link (/accept-invitation/<id>, signed in) shows who invited them, to which organization and as what, then Accept or Decline.

In your app

The module gives you a composable and a component for your own layout:

<template>
  <!-- Switch organization and team, open its page, create one. -->
  <AuthOrganizationSwitcher />
</template>

Check permissions on your server with Better Auth's own API:

server/api/projects.post.ts
export default defineEventHandler(async (event) => {
  const auth = serverAuth(event)
  const { success } = await auth.api.hasPermission({
    headers: event.headers,
    body: { permissions: { project: ['create'] } }
  })
  if (!success) throw createError({ statusCode: 403 })
  // The member's role in the active organization, if you need it:
  const { role } = await auth.api.getActiveMemberRole({ headers: event.headers })
})

In the admin

  • An "Organizations" page lists every organization, with a search by name and member counts.
  • Each organization's page shows its members (linking to their user pages), pending invitations, teams and own roles.
  • A user's page lists their organizations.

Staff who can update users (user: update) change organizations through the calls Better Auth allows from the server, without a member's session. Its hooks and limits apply, and each change is recorded in the audit log:

Staff canThrough Better Auth's
Create an organization for a user, who becomes its ownercreateOrganization
Add a member, with roles and a teamaddMember
Create and remove teamscreateTeam, removeTeam

Better Auth needs one of the organization's members for everything else: renaming or deleting it, changing members' roles or removing them, invitations, and its own roles. With impersonation on, the organization's admin page offers Sign in as its owner, which opens the organization's own page as them. The impersonation is recorded in the audit log, and the banner leads back.

Options

enabled
booleandefault: false
page
stringdefault: '/organization'
The organization's own page: <page>/<organization id>.
invitationPage
stringdefault: '/accept-invitation'
The invitation page: <page>/<invitation id>.
allowUserToCreateOrganization
booleandefault: true
Whether users can create organizations.
organizationLimit
number
Organizations a user can create. Unlimited by default.
membershipLimit
numberdefault: 100
Members an organization can have.
creatorRole
'owner' | 'admin'default: 'owner'
The role of whoever creates an organization.
invitationExpiresIn
numberdefault: 172800
Seconds an invitation lasts (48 hours).
invitationLimit
numberdefault: 100
Pending invitations a user can send.
requireEmailVerificationOnInvitation
booleandefault: false
Invitations need a verified email to see, accept or decline.
cancelPendingInvitationsOnReInvite
booleandefault: false
Inviting someone again cancels their pending invitation.
disableOrganizationDeletion
booleandefault: false
Nobody can delete an organization.
teams
boolean | objectdefault: false
Teams within organizations. { maximumTeams, maximumMembersPerTeam, allowRemovingAllTeams } to set limits.
statements
Record<string, string[]>
Your own resources and actions. See Roles and permissions.
roles
Record<string, Record<string, string[]>>
Organization roles and their permissions. See Roles and permissions.
dynamicRoles
boolean | objectdefault: false
Organizations create their own roles. See Roles and permissions.
schema
object
Better Auth's schema options: table and field names, and your own fields (additionalFields) on organization, member, invitation, team and organizationRole. The organization's page shows and edits them.
serverOptions
string
A file with more of the plugin's options, for those that are functions. See below.

Options that are functions

Some of Better Auth's options are functions, like organizationHooks, or organizationLimit worked out per user. They can't go in nuxt.config.ts, so put them in a file and point serverOptions at it:

export default defineNuxtConfig({
  betterAuthUi: {
    organization: {
      enabled: true,
      serverOptions: './server/auth/organization.ts'
    }
  }
})

Its options are added last, so they win over the module's. It's loaded with your auth config, also at build time, so keep Nitro-only imports out of its top level.

The pages use Better Auth's words (organization, owner, admin, member). Call organizations "teams" or anything else with messages.