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"
 
export const authClient = 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

POST
/organization/create
const metadata = { someKey: "someValue" };
 
const { data, error } = await authClient.organization.create({
    name: "My Organization", // required
    slug: "my-org", // required
    logo: "https://example.com/logo.png",
    metadata,
    keepCurrentActiveOrganization: false,
});
PropDescriptionType
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.

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"
            } 
        })
    ]
})

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:

POST
/organization/check-slug
const { data, error } = await authClient.organization.checkSlug({
    slug: "my-org", // required
});
PropDescriptionType
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.

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [
        organization({
            organizationCreation: {
                disabled: false, // Set to true to disable organization creation
                beforeCreate: async ({ organization, user }, request) => {
                    // Run custom logic before organization is created
                    // Optionally modify the organization data
                    return {
                        data: {
                            ...organization,
                            metadata: {
                                customField: "value"
                            }
                        }
                    }
                },
                afterCreate: async ({ organization, member, user }, request) => {
                    // Run custom logic after organization is created
                    // e.g., create default resources, send notifications
                    await setupDefaultResources(organization.id)
                }
            }
        })
    ]
})

The beforeCreate hook runs before an organization is created. It receives:

  • organization: The organization data (without ID)
  • user: The user creating the organization
  • request: 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 creator
  • user: The user who created the organization
  • request: 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.

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

Or alternatively, you can call organization.list if you don't want to use a hook.

GET
/organization/list
const { data, error } = await authClient.organization.list();

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.

POST
/organization/set-active
const { data, error } = await authClient.organization.setActive({
    organizationId: "org-id",
    organizationSlug: "org-slug",
});
PropDescriptionType
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.

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 { authClient } from "@/lib/auth-client"
 
function App(){
    const { data: activeOrganization } = authClient.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. By default, if you don't pass any properties, it will use the active organization.

GET
/organization/get-full-organization
const { data, error } = await authClient.organization.getFullOrganization({
    organizationId: "org-id",
    organizationSlug: "org-slug",
});
PropDescriptionType
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

POST
/organization/update
const { data, error } = await authClient.organization.update({
    data: { // required
        name: "updated-name",
        slug: "updated-slug",
        logo: "new-logo.url",
        metadata: { customerId: "test" },
    },
    organizationId: "org-id",
});
PropDescriptionType
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

POST
/organization/delete
const { data, error } = await authClient.organization.delete({
    organizationId: "org-id", // required
});
PropDescriptionType
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:

const auth = betterAuth({
  plugins: [
    organization({
      organizationDeletion: {
        disabled: true, //to disable it altogether
        beforeDelete: async (data, request) => {
          // a callback to run before deleting org
        },
        afterDelete: async (data, request) => {
          // a callback to run after deleting org
        },
      },
    }),
  ],
});

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.

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:

POST
/organization/invite-member
const { data, error } = await authClient.organization.inviteMember({
    email: "[email protected]", // required
    role: "member", // required
    organizationId: "org-id",
    resend: true,
    teamId: "team-id",
});
PropDescriptionType
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 to true, the invitation will not be sent again.
  • If cancelPendingInvitationsOnReInvite is set to true, 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.

POST
/organization/accept-invitation
const { data, error } = await authClient.organization.acceptInvitation({
    invitationId: "invitation-id", // required
});
PropDescriptionType
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.

POST
/organization/cancel-invitation
await authClient.organization.cancelInvitation({
    invitationId: "invitation-id", // required
});
PropDescriptionType
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.

POST
/organization/reject-invitation
await authClient.organization.rejectInvitation({
    invitationId: "invitation-id", // required
});
PropDescriptionType
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.

GET
/organization/get-invitation
const { data, error } = await authClient.organization.getInvitation({
    id: "invitation-id", // required
});
PropDescriptionType
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.

GET
/organization/list-invitations
const { data, error } = await authClient.organization.listInvitations({
    organizationId: "organization-id",
});
PropDescriptionType
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.

auth-client.ts
const invitations = await authClient.organization.listUserInvitations()

On the server, you can pass the user ID as a query parameter.

api.ts
const invitations = await auth.api.listUserInvitations({
    query: {
        email: "[email protected]"
    }
})

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

POST
/organization/remove-member
const { data, error } = await authClient.organization.removeMember({
    memberIdOrEmail: "[email protected]", // required
    organizationId: "org-id",
});
PropDescriptionType
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.

POST
/organization/update-member-role
await authClient.organization.updateMemberRole({
    role: ["admin", "sale"], // required
    memberId: "member-id", // required
    organizationId: "organization-id",
});
PropDescriptionType
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.

GET
/organization/get-active-member
const { data: member, error } = 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.

POST
/organization/add-member
const data = await auth.api.addMember({
    body: {
        userId: "user-id",
        role: ["admin", "sale"], // required
        organizationId: "org-id",
        teamId: "team-id",
    },
});
PropDescriptionType
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.

POST
/organization/leave
await authClient.organization.leave({
    organizationId: "organization-id", // required
});
PropDescriptionType
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.

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 defaultStatements and merge it with your new statement, plus merge the roles' permissions set with the default roles.

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements, adminAc } from 'better-auth/plugins/organization/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 { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { ac, owner, admin, member } from "@/auth/permissions"
 
export const auth = betterAuth({
    plugins: [
        organization({
            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, owner, admin, member, myCustomRole } from "@/auth/permissions"
 
export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            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";
 
await auth.api.hasPermission({
  headers: await headers(),
    body: {
      permissions: {
        project: ["create"] // This must match the structure in your access control
      }
    }
});
 
// You can also check multiple resource permissions at the same time
await auth.api.hasPermission({
  headers: await headers(),
    body: {
      permissions: {
        project: ["create"], // This must match the structure in your access control
        sale: ["create"]
      }
    }
});

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({
    permissions: {
        project: ["create"]
    }
})
 
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = await authClient.organization.hasPermission({
    permissions: {
        project: ["create"],
        sale: ["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 = authClient.organization.checkRolePermission({
	permissions: {
		organization: ["delete"],
	},
	role: "admin",
});
 
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = authClient.organization.checkRolePermission({
	permissions: {
		organization: ["delete"],
    member: ["delete"]
	},
	role: "admin",
});

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:

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [
        organization({
            teams: {
                enabled: true,
                maximumTeams: 10, // Optional: limit teams per organization
                allowRemovingAllTeams: false // Optional: prevent removing the last team
            }
        })
    ]
})
auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
 
export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            teams: {
                enabled: true
            }
        })
    ]
})

Managing Teams

Create Team

Create a new team within an organization:

POST
/organization/create-team
const { data, error } = await authClient.organization.createTeam({
    name: "my-team", // required
    organizationId: "organization-id",
});
PropDescriptionType
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:

GET
/organization/list-teams
const { data, error } = await authClient.organization.listTeams({
    organizationId: "organziation-id",
});
PropDescriptionType
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:

POST
/organization/update-team
const { data, error } = await authClient.organization.updateTeam({
    teamId: "team-id", // required
    data: { // required
        name: "My new team name",
        organizationId: "My new organization ID for this team",
        createdAt: new Date(),
        updatedAt: new Date(),
    },
});
PropDescriptionType
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:

POST
/organization/remove-team
const { data, error } = await authClient.organization.removeTeam({
    teamId: "team-id", // required
    organizationId: "organization-id",
});
PropDescriptionType
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.

POST
/organization/set-active-team
const { data, error } = await authClient.organization.setActiveTeam({
    teamId: "team-id",
});
PropDescriptionType
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.

GET
/organization/list-user-teams
const { data, error } = await authClient.organization.listUserTeams();

List Team Members

List the members of the given team.

POST
/organization/list-team-members
const { data, error } = await authClient.organization.listTeamMembers({
    teamId: "team-id",
});
PropDescriptionType
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.

POST
/organization/add-team-member
const { data, error } = await authClient.organization.addTeamMember({
    teamId: "team-id", // required
    userId: "user-id", // required
});
PropDescriptionType
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.

POST
/organization/remove-team-member
const { data, error } = await authClient.organization.removeTeamMember({
    teamId: "team-id", // required
    userId: "user-id", // required
});
PropDescriptionType
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 teams
  • team:update - Update team details
  • team: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

    teams: {
      enabled: true,
      maximumTeams: 10 // Fixed number
      // OR
      maximumTeams: async ({ organizationId, session }, request) => {
        // Dynamic limit based on organization plan
        const plan = await getPlan(organizationId)
        return plan === 'pro' ? 20 : 5
      },
      maximumMembersPerTeam: 10 // Fixed number
      // OR
      maximumMembersPerTeam: async ({ teamId, session, organizationId }, request) => {
        // Dynamic limit based on team plan
        const plan = await getPlan(organizationId, teamId)
        return plan === 'pro' ? 50 : 10
      },
    }
  • allowRemovingAllTeams: Control whether the last team can be removed

    teams: {
      enabled: true,
      allowRemovingAllTeams: false // Prevent removing the last team
    }

Team Members

When inviting members to an organization, you can specify a team:

await authClient.organization.inviteMember({
    email: "[email protected]",
    role: "member",
    teamId: "team-id"
})

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 NameTypeKeyDescription
idstringUnique identifier for each team
namestring-The name of the team
organizationIdstringThe ID of the organization
createdAtDate-Timestamp of when the team was created
updatedAtDateTimestamp of when the team was created

Table Name: teamMember

Field NameTypeKeyDescription
idstringUnique identifier for each team member
teamIdstringUnique identifier for each team
userIdstringThe ID of the user
createdAtDate-Timestamp of when the team member was created

Schema

The organization plugin adds the following tables to the database:

Organization

Table Name: organization

Field NameTypeKeyDescription
idstringUnique identifier for each organization
namestring-The name of the organization
slugstring-The slug of the organization
logostringThe logo of the organization
metadatastringAdditional metadata for the organization
createdAtDate-Timestamp of when the organization was created

Member

Table Name: member

Field NameTypeKeyDescription
idstringUnique identifier for each member
userIdstringThe ID of the user
organizationIdstringThe ID of the organization
rolestring-The role of the user in the organization
createdAtDate-Timestamp of when the member was added to the organization

Invitation

Table Name: invitation

Field NameTypeKeyDescription
idstringUnique identifier for each invitation
emailstring-The email address of the user
inviterIdstringThe ID of the inviter
organizationIdstringThe ID of the organization
rolestring-The role of the user in the organization
statusstring-The status of the invitation
expiresAtDate-Timestamp of when the invitation expires
createdAtDate-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 NameTypeKeyDescription
activeOrganizationIdstringThe ID of the active organization
activeTeamIdstringThe ID of the active team

Teams (optional)

Table Name: team

Field NameTypeKeyDescription
idstringUnique identifier for each team
namestring-The name of the team
organizationIdstringThe ID of the organization
createdAtDate-Timestamp of when the team was created
updatedAtDateTimestamp of when the team was created

Table Name: teamMember

Field NameTypeKeyDescription
idstringUnique identifier for each team member
teamIdstringUnique identifier for each team
userIdstringThe ID of the user
createdAtDate-Timestamp of when the team member was created

Table Name: invitation

Field NameTypeKeyDescription
teamIdstringThe ID of the team

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
          },
          additionalFields: {
            // Add a new field to the organization table
            myCustomField: {
              type: "string",
              input: true,
              required: false
            }
          }
        }
      }
    })
  ]
})

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:

auth-client.ts
createAuthClient({
  plugins: [organizationClient({ $inferAuth: {} as typeof auth })]
})

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.