Organization
Organizations simplifies user access and permissions management. Assign roles and permissions to streamline project management, team coordination, and partnerships.
Installation
Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
See the Schema section to add the fields manually.
Add the client plugin
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
Prop | Description | Type |
---|---|---|
name | The organization name. | string |
slug | The organization slug. | string |
logo? | The organization logo. | string |
metadata? | The metadata of the organization. | Record<string, any> |
keepCurrentActiveOrganization? | Whether to keep the current active organization active after creating a new one. | boolean |
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
.
Check if organization slug is taken
To check if an organization slug is taken or not you can use the checkSlug
function provided by the client. The function takes an object with the following properties:
Prop | Description | Type |
---|---|---|
slug | The organization slug to check. | string |
Organization Creation Hooks
You can customize the organization creation process using hooks that run before and after an organization is created.
The beforeCreate
hook runs before an organization is created. It receives:
organization
: The organization data (without ID)user
: The user creating the organizationrequest
: The HTTP request object (optional)
Return an object with data
property to modify the organization data that will be created.
The afterCreate
hook runs after an organization is successfully created. It receives:
organization
: The created organization (with ID)member
: The member record for the creatoruser
: The user who created the organizationrequest
: The HTTP request object (optional)
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.
Or alternatively, you can call organization.list
if you don't want to use a hook.
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.
In some applications, you may want the ability to un-set an active organization.
In this case, you can call this endpoint with organizationId
set to null
.
Prop | Description | Type |
---|---|---|
organizationId? | The organization id to set as active. It can be null to unset the active organization. | string | null |
organizationSlug? | The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided. | string |
To set active organization when a session is created you can use database hooks.
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.
Get Full Organization
To get the full details of an organization, you can use the getFullOrganization
function.
By default, if you don't pass any properties, it will use the active organization.
Prop | Description | Type |
---|---|---|
organizationId? | The organization id to get. By default, it will use the active organization. | string |
organizationSlug? | The organization slug to get. | string |
Update Organization
To update organization info, you can use organization.update
Prop | Description | Type |
---|---|---|
data | A partial list of data to update the organization. | Object |
data.name? | The name of the organization. | string |
data.slug? | The slug of the organization. | string |
data.logo? | The logo of the organization. | string |
data.metadata? | The metadata of the organization. | Record<string, any> | null |
organizationId? | The organization ID. to update. | string |
Delete Organization
To remove user owned organization, you can use organization.delete
Prop | Description | Type |
---|---|---|
organizationId | The organization id to delete. | string |
If the user has the necessary permissions (by default: role is owner) in the specified organization, all members, invitations and organization information will be removed.
You can configure how organization deletion is handled through organizationDeletion
option:
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 provide 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.
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:
Prop | Description | Type |
---|---|---|
email | The email address of the user to invite. | string |
role | The role(s) to assign to the user. It can be admin , member , or guest . | string | string[] |
organizationId? | The organization ID to invite the user to. Defaults to the active organization. | string |
resend? | Resend the invitation email, if the user is already invited. | boolean |
teamId? | The team ID to invite the user to. | string |
- If the user is already a member of the organization, the invitation will be canceled.
- If the user is already invited to the organization, unless
resend
is set totrue
, the invitation will not be sent again. - If
cancelPendingInvitationsOnReInvite
is set totrue
, the invitation will be canceled if the user is already invited to the organization and a new invitation is sent.
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.
Prop | Description | Type |
---|---|---|
invitationId | The ID of the invitation to accept. | string |
Cancel Invitation
If a user has sent out an invitation, you can use this method to cancel it.
If you're looking for how a user can reject an invitation, you can find that here.
Prop | Description | Type |
---|---|---|
invitationId | The ID of the invitation to cancel. | string |
Reject Invitation
If this user has received an invitation, but wants to decline it, this method will allow you to do so by rejecting it.
Prop | Description | Type |
---|---|---|
invitationId | The ID of the invitation to reject. | string |
Get Invitation
To get an invitation you can use the organization.getInvitation
function provided by the client. You need to provide the invitation id as a query parameter.
Prop | Description | Type |
---|---|---|
id | The ID of the invitation to get. | string |
List Invitations
To list all invitations for a given organization you can use the listInvitations
function provided by the client.
Prop | Description | Type |
---|---|---|
organizationId? | An optional ID of the organization to list invitations for. If not provided, will default to the users active organization. | string |
List user invitations
To list all invitations for a given user you can use the listUserInvitations
function provided by the client.
On the server, you can pass the user ID as a query parameter.
The email
query parameter is only available on the server to query for invitations for a specific user.
Members
Remove Member
To remove you can use organization.removeMember
Prop | Description | Type |
---|---|---|
memberIdOrEmail | The ID or email of the member to remove. | string |
organizationId? | The ID of the organization to remove the member from. If not provided, the active organization will be used. | string |
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.
Prop | Description | Type |
---|---|---|
role | The new role to be applied. This can be a string or array of strings representing the roles. | string | string[] |
memberId | The member id to apply the role update to. | string |
organizationId? | An optional organization ID which the member is a part of to apply the role update. If not provided, you must provide session headers to get the active organization. | string |
Get Active Member
To get the current member of the active organization you can use the organization.getActiveMember
function. This function will return the user's member details in their active organization.
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.
Prop | Description | Type |
---|---|---|
userId? | The user Id which represents the user to be added as a member. If null is provided, then it's expected to provide session headers. | string | null |
role | The role(s) to assign to the new member. | string | string[] |
organizationId? | An optional organization ID to pass. If not provided, will default to the user's active organization. | string |
teamId? | An optional team ID to add the member to. | string |
Leave Organization
To leave organization you can use organization.leave
function. This function will remove the current user from the organization.
Prop | Description | Type |
---|---|---|
organizationId | The organization Id for the member to leave. | string |
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.
A user can have multiple roles. Multiple roles are stored as string separated by comma (",").
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 has full control over all the resources and actions. The admin has full control over all the resources except for deleting the organization or changing the owner. The member has no control over any of those actions other than reading the data.
Custom Permissions
The plugin provides an easy way to define your own set of permissions 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.
Create Roles
Once you have created the access controller you can create roles with the permissions you have defined.
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 defaultStatements
and merge it with your new statement, plus merge the roles' permissions set with the default roles.
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.
You also need to pass the access controller and the roles to the client plugin.
Access Control Usage
Has Permission:
You can use the hasPermission
action provided by the api
to check the permission of the user.
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.
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.
Teams
Teams allow you to group members within an organization. The teams feature provides additional organization structure and can be used to manage permissions at a more granular level.
Enabling Teams
To enable teams, pass the teams
configuration option to both server and client plugins:
Managing Teams
Create Team
Create a new team within an organization:
Prop | Description | Type |
---|---|---|
name | The name of the team. | string |
organizationId? | The organization ID which the team will be created in. Defaults to the active organization. | string |
List Teams
Get all teams in an organization:
Prop | Description | Type |
---|---|---|
organizationId? | The organization ID which the teams are under to list. Defaults to the users active organization. | string |
Update Team
Update a team's details:
Prop | Description | Type |
---|---|---|
teamId | The ID of the team to be updated. | string |
data | A partial object containing options for you to update. | Object |
data.name? | The name of the team to be updated. | string |
data.organizationId? | The organization ID which the team falls under. | string |
data.createdAt? | The timestamp of when the team was created. | Date |
data.updatedAt? | The timestamp of when the team was last updated. | Date |
Remove Team
Delete a team from an organization:
Prop | Description | Type |
---|---|---|
teamId | The team ID of the team to remove. | string |
organizationId? | The organization ID which the team falls under. If not provided, it will default to the user's active organization. | string |
Set Active Team
Sets the given team as the current active team. If teamId
is null
the current active team is unset.
Prop | Description | Type |
---|---|---|
teamId? | The team ID of the team to set as the current active team. | string |
List User Teams
List all teams that the current user is a part of.
List Team Members
List the members of the given team.
Prop | Description | Type |
---|---|---|
teamId? | The team whose members we should return. If this is not provided the members of the current active team get returned. | string |
Add Team Member
Add a member to a team.
Prop | Description | Type |
---|---|---|
teamId | The team the user should be a member of. | string |
userId | The user Id which represents the user to be added as a member. | string |
Remove Team Member
Remove a member from a team.
Prop | Description | Type |
---|---|---|
teamId | The team the user should be removed from. | string |
userId | The user which should be removed from the team. | string |
Team Permissions
Teams follow the organization's permission system. To manage teams, users need the following permissions:
team:create
- Create new teamsteam:update
- Update team detailsteam:delete
- Remove teams
By default:
- Organization owners and admins can manage teams
- Regular members cannot create, update, or delete teams
Team Configuration Options
The teams feature supports several configuration options:
-
maximumTeams
: Limit the number of teams per organization -
allowRemovingAllTeams
: Control whether the last team can be removed
Team Members
When inviting members to an organization, you can specify a team:
The invited member will be added to the specified team upon accepting the invitation.
Database Schema
When teams are enabled, new team
and teamMember
tables are added to the database.
Table Name: team
Field Name | Type | Key | Description |
---|---|---|---|
id | string | Unique identifier for each team | |
name | string | - | The name of the team |
organizationId | string | The ID of the organization | |
createdAt | Date | - | Timestamp of when the team was created |
updatedAt | Date | Timestamp of when the team was created |
Table Name: teamMember
Field Name | Type | Key | Description |
---|---|---|---|
id | string | Unique identifier for each team member | |
teamId | string | Unique identifier for each team | |
userId | string | The ID of the user | |
createdAt | Date | - | Timestamp of when the team member was created |
Schema
The organization plugin adds the following tables to the database:
Organization
Table Name: organization
Field Name | Type | Key | Description |
---|---|---|---|
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 Name | Type | Key | Description |
---|---|---|---|
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 Name | Type | Key | Description |
---|---|---|---|
id | string | Unique identifier for each invitation | |
string | - | The email address of the user | |
inviterId | string | The ID of the inviter | |
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 two more fields to the session table to store the active organization ID and the active team ID.
Field Name | Type | Key | Description |
---|---|---|---|
activeOrganizationId | string | The ID of the active organization | |
activeTeamId | string | The ID of the active team |
Teams (optional)
Table Name: team
Field Name | Type | Key | Description |
---|---|---|---|
id | string | Unique identifier for each team | |
name | string | - | The name of the team |
organizationId | string | The ID of the organization | |
createdAt | Date | - | Timestamp of when the team was created |
updatedAt | Date | Timestamp of when the team was created |
Table Name: teamMember
Field Name | Type | Key | Description |
---|---|---|---|
id | string | Unique identifier for each team member | |
teamId | string | Unique identifier for each team | |
userId | string | The ID of the user | |
createdAt | Date | - | Timestamp of when the team member was created |
Table Name: invitation
Field Name | Type | Key | Description |
---|---|---|---|
teamId | string | The ID of the team |
Customizing the Schema
To change the schema table name or fields, you can pass schema
option to the organization plugin.
Additional Fields
Starting with Better Auth v1.3, you can easily add custom fields to the organization
, invitation
, member
, and team
tables.
Refer to the example above to learn how to define additional fields in your schema configuration.
When you add extra fields to a model, the relevant API endpoints will automatically
accept and return these new properties. For instance, if you add a custom field to the organization
table,
the createOrganization
endpoint will include this field in its request and response payloads as needed.
What about the metadata
field?
The metadata
field is still supported for backward compatibility,
and provides a convenient way to attach arbitrary data to a row.
To infer additional fields on the client, you must pass the auth
instance to the organizationClient
function like this:
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).
cancelPendingInvitationsOnReInvite: boolean
- Whether to cancel pending invitations if the user is already invited to the organization. By default, it's false
.
invitationLimit: number
| ((user: User) => Promise<boolean> | boolean)
- The maximum number of invitations allowed for a user. By default, it's 100
. You can set it to any number you want or a function that returns a boolean.