Docs

Organization

Organizations simplifies user access and permissions management. Assign roles and permissions to streamline project management, team coordination, and partnerships.

Installation

Add the plugin to your auth config

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [ 
        organization() 
    ] 
})

Migrate the database

Run the migration or generate the schema to add the necessary fields and tables to the database.

npx @better-auth/cli migrate

See the Schema section to add the fields manually.

Add the client plugin

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
 
const client = createAuthClient({
    plugins: [ 
        organizationClient() 
    ] 
})

Usage

Once you've installed the plugin, you can start using the organization plugin to manage your organization's members and teams. The client plugin will provide you methods under the organization namespace. And the server api will provide you with the necessary endpoints to manage your organization and gives you easier way to call the functions on your own backend.

Organization

Create an organization

To create an organization, you need to provide:

  • name: The name of the organization.
  • slug: The slug of the organization.
  • logo: The logo of the organization. (Optional)
auth-client.ts
await authClient.organization.create({
    name: "My Organization",
    slug: "my-org",
    logo: "https://example.com/logo.png"
})

Restrict who can create an organization

By default, any user can create an organization. To restrict this, set the allowUserToCreateOrganization option to a function that returns a boolean, or directly to true or false.

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
const auth = betterAuth({
    //...
    plugins: [
        organization({
            allowUserToCreateOrganization: async (user) => { 
                const subscription = await getSubscription(user.id) 
                return subscription.plan === "pro"
            } 
        })
    ]
})

List User's Organizations

To list the organizations that a user is a member of, you can use useListOrganizations hook. It implements a reactive way to get the organizations that the user is a member of.

client.tsx
import { client } from "@/auth/client"
 
function App(){
    const { data: organizations } = client.useListOrganizations()
    return (
        <div>
            {organizations.map(org => <p>{org.name}</p>)}
        </div>
    )
} 

Active Organization

Active organization is the workspace the user is currently working on. By default when the user is signed in the active organization is set to null. You can set the active organization to the user session.

It's not always you want to persist the active organization in the session. You can manage the active organization in the client side only. For example, multiple tabs can have different active organizations.

Set Active Organization

You can set the active organization by calling the organization.setActive function. It'll set the active organization for the user session.

auth-client.ts
import { client } from "@/lib/auth-client";
 
await authClient.organization.setActive({
  organizationId: "organization-id"
})
 
// you can also use organizationSlug instead of organizationId
authClient.organization.setActive({
  organizationSlug: "organization-slug"
})

To set active organization when a session is created you can use database hooks.

auth.ts
export const auth = betterAuth({
  databaseHooks: {
      session: {
          create: {
              before: async(session)=>{
                  const organization = await getActiveOrganization(session.userId)
                  return {
                    data: {
                      ...session,
                      activeOrganizationId: organization.id
                    }
                  }
              }
          }
      }
  }
})

Use Active Organization

To retrieve the active organization for the user, you can call the useActiveOrganization hook. It returns the active organization for the user. Whenever the active organization changes, the hook will re-evaluate and return the new active organization.

client.tsx
import { client } from "@/auth/client"
 
function App(){
    const { data: activeOrganization } = client.useActiveOrganization()
    return (
        <div>
            {activeOrganization ? <p>{activeOrganization.name}</p> : null}
        </div>
    )
} 

Get Full Organization

To get the full details of an organization, you can use the getFullOrganization function provided by the client. The function takes an object with the following properties:

  • organizationId: The id of the organization. (Optional) – By default, it will use the active organization.
  • organizationSlug: The slug of the organization. (Optional) – To get the organization by slug.
auth-client.ts
const organization = await authClient.organization.getFullOrganization({
    organizationId: "organization-id" // optional, by default it will use the active organization
})
//you can also use organizationSlug instead of organizationId
const organization = await authClient.organization.getFullOrganization({
    organizationSlug: "organization-slug"
})

Invitations

To add a member to an organization, we first need to send an invitation to the user. The user will receive an email/sms with the invitation link. Once the user accepts the invitation, they will be added to the organization.

Setup Invitation Email

For member invitation to work we first need to provider sendInvitationEmail to the better-auth instance. This function is responsible for sending the invitation email to the user.

You'll need to construct and send the invitation link to the user. The link should include the invitation ID, which will be used with the acceptInvitation function when the user clicks on it.

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { sendOrganizationInvitation } from "./email"
export const auth = betterAuth({
	plugins: [
		organization({
			async sendInvitationEmail(data) {
                const inviteLink = `https://example.com/accept-invitation/${data.id}`
				sendOrganizationInvitation({
					    email: data.email,
						invitedByUsername: data.inviter.user.name,
						invitedByEmail: data.inviter.user.email,
						teamName: data.organization.name,
						inviteLink
					})
			},
		}),
	],
});

Send Invitation

To invite users to an organization, you can use the invite function provided by the client. The invite function takes an object with the following properties:

  • email: The email address of the user.
  • role: The role of the user in the organization. It can be admin, member, or guest.
  • organizationId: The id of the organization. this is optional by default it will use the active organization. (Optional)
invitation.ts
await authClient.organization.inviteMember({
    email: "[email protected]",
    role: "admin",
})

Accept Invitation

When a user receives an invitation email, they can click on the invitation link to accept the invitation. The invitation link should include the invitation ID, which will be used to accept the invitation.

Make sure to call the acceptInvitation function after the user is logged in.

auth-client.ts
await authClient.organization.acceptInvitation({
    invitationId: "invitation-id"
})

Update Invitation Status

To update the status of invitation you can use the acceptInvitation, cancelInvitation, rejectInvitation functions provided by the client. The functions take the invitation id as an argument.

auth-client.ts
//cancel invitation
await authClient.organization.cancelInvitation({
    invitationId: "invitation-id"
})
 
//reject invitation (needs to be called when the user who received the invitation is logged in)
await authClient.organization.rejectInvitation({
    invitationId: "invitation-id"
})

Get Invitation

To get an invitation you can use the getInvitation function provided by the client. You need to provide the invitation id as a query parameter.

auth-client.ts
client.organization.getInvitation({
    query: {
        id: params.id
    }
})

Members

Remove Member

To remove you can use organization.removeMember

auth-client.ts
//remove member
await authClient.organization.removeMember({
    memberId: "member-id"
})

Update Member Role

To update the role of a member in an organization, you can use the organization.updateMemberRole. If the user has the permission to update the role of the member, the role will be updated.

auth-client.ts
await authClient.organization.updateMemberRole({
    memberId: "member-id",
    role: "admin"
})

Get Active Member

To get the current member of the organization you can use the organization.getActiveMember function. This function will return the current active member.

auth-client.ts
const member = await authClient.organization.getActiveMember()

Add Member

If you want to add a member directly to an organization without sending an invitation, you can use the addMember function which can only be invoked on the server.

api.ts
import { auth } from "@/auth";
 
auth.api.addMember({
  body: {
      userId: "user-id",
      organizationId: "organization-id",
      role: "admin"
  }
})

Access Control

The organization plugin providers a very flexible access control system. You can control the access of the user based on the role they have in the organization. You can define your own set of permissions based on the role of the user.

Roles

By default, there are three roles in the organization:

owner: The user who created the organization by default. The owner has full control over the organization and can perform any action.

admin: Users with the admin role have full control over the organization except for deleting the organization or changing the owner.

member: Users with the member role have limited control over the organization. They can create projects, invite users, and manage projects they have created.

Permissions

By default, there are three resources, and these have two to three actions.

organization:

update delete

member:

create update delete

invitation:

create cancel

The owner have full control over all the resources and actions. The admin have full control over all the resources except for deleting the organization or changing the owner. The member have no control over any of those action other than reading the data.

Custom Permissions

the plugin providers easy way to define your own set of permission for each role.

Create Access Control

You first need to create access controller by calling createAccessControl function and passing the statement object. The statement object should have the resource name as the key and the array of actions as the value.

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
 
/**
 * make sure to use `as const` so typescript can infer the type correctly
 */
const statement = { 
    project: ["create", "share", "update", "delete"], 
} as const; 
 
const ac = createAccessControl(statement); 

Create Roles

Once you have created the access controller you can create roles with the permissions you have defined.

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
 
const statement = { 
    project: ["create", "share", "update", "delete"],
} as const; 
 
const ac = createAccessControl(statement); 
 
const member = ac.newRole({ 
    project: ["create"], 
}); 
 
const admin = ac.newRole({ 
    project: ["create", "update"], 
}); 
 
const owner = ac.newRole({ 
    project: ["create", "update", "delete"], 
}); 
 
const myCustomRole = ac.newRole({ 
    project: ["create", "update", "delete"], 
    organization: ["update"], 
}); 

When you create custom roles for existing roles, the predefined permissions for those roles will be overridden. To add the existing permissions to the custom role, you need to import defaultStatement and merge it with your new statement, plus merge the roles' permissions set with the default roles.

permissions.ts
import { createAccessControl, defaultStatement, adminAc } from "better-auth/plugins/access";
 
const statement = { 
    ...defaultStatements, 
    project: ["create", "share", "update", "delete"],
} as const;
 
const ac = createAccessControl(statement);
 
const admin = ac.newRole({
    project: ["create", "update"],
    ...adminAc.statements, 
});

Pass Roles to the Plugin

Once you have created the roles you can pass them to the organization plugin both on the client and the server.

auth.ts
import { ac, owner, admin, member } from "@/auth/permissions"
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [
        organization({
            ac: ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        }),
    ],
});

You also need to pass the access controller and the roles to the client plugin.

auth-client
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
import { ac } from "@/auth/permissions"
 
export const client = createAuthClient({
    plugins: [
        organizationClient({
            ac: ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        })
    ]
})

Access Control Usage

Has Permission:

You can use the hasPermission action provided by the api to check the permission of the user.

api.ts
import { auth } from "@/auth";
    auth.api.hasPermission({
        headers: await headers(),
        body: {
            permission: {
                project: ["create"] // This must match the structure in your access control
            }
        }
    });

If you want to check the permission of the user on the client from the server you can use the hasPermission function provided by the client.

auth-client.ts
const canCreateProject = await authClient.organization.hasPermission({
    permission: {
        project: ["create"]
    }
})

Check Role Permission:

Once you have defined the roles and permissions to avoid checking the permission from the server you can use the checkRolePermission function provided by the client.

auth-client.ts
const canCreateProject = client.organization.checkRolePermission({
	permission: {
		organization: ["delete"],
	},
	role: "admin",
});

Schema

The organization plugin adds the following tables to the database:

Organization

Table Name: organization

Field NameTypeKeyDescription
id
string
Unique identifier for each organization
name
string
-The name of the organization
slug
string
-The slug of the organization
logo
string
The logo of the organization
metadata
string
Additional metadata for the organization
createdAt
Date
-Timestamp of when the organization was created

Member

Table Name: member

Field NameTypeKeyDescription
id
string
Unique identifier for each member
userId
string
The id of the user
organizationId
string
The id of the organization
role
string
-The role of the user in the organization
createdAt
Date
-Timestamp of when the member was added to the organization

Invitation

Table Name: invitation

Field NameTypeKeyDescription
id
string
Unique identifier for each invitation
email
string
-The email address of the user
organizationId
string
The id of the organization
role
string
-The role of the user in the organization
status
string
-The status of the invitation
expiresAt
Date
-Timestamp of when the invitation expires
createdAt
Date
-Timestamp of when the invitation was created

Session

Table Name: session

You need to add one more field to the session table to store the active organization id.

Field NameTypeKeyDescription
activeOrganizationId
string
The id of the active organization

Customizing the Schema

To change the schema table name or fields, you can pass schema option to the organization plugin.

auth.ts
const auth = betterAuth({
  plugins: [organization({
    schema: {
      organization: {
        modelName: "organizations",  //map the organization table to organizations
        fields: { 
          name: "title" //map the name field to title
        }
      }
    }
  })]
})

Options

allowUserToCreateOrganization: boolean | ((user: User) => Promise<boolean> | boolean) - A function that determines whether a user can create an organization. By default, it's true. You can set it to false to restrict users from creating organizations.

organizationLimit: number | ((user: User) => Promise<boolean> | boolean) - The maximum number of organizations allowed for a user. By default, it's 5. You can set it to any number you want or a function that returns a boolean.

creatorRole: admin | owner - The role of the user who creates the organization. By default, it's owner. You can set it to admin.

membershipLimit: number - The maximum number of members allowed in an organization. By default, it's 100. You can set it to any number you want.

sendInvitationEmail: async (data) => Promise<void> - A function that sends an invitation email to the user.

invitationExpiresIn : number - How long the invitation link is valid for in seconds. By default, it's 48 hours (2 days).