组织

组织插件允许您管理组织的成员和团队。

组织简化了用户访问和权限管理。分配角色和权限以简化项目管理、团队协调和合作。

安装

将插件添加到您的 auth 配置中

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        organization() 
    ]
})

迁移数据库

运行迁移或生成模式,以将必要的字段和表添加到数据库。

npx auth migrate
npx auth generate

查看 Schema 部分,了解如何手动添加字段。

添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
    plugins: [
        organizationClient() 
    ]
})

用法

安装插件后,您就可以开始使用组织插件来管理组织的成员和团队。客户端插件会在 organization 命名空间下提供方法,服务器端的 api 会提供管理组织所需的接口,并让您更轻松地调用后端函数。

组织

创建组织

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,    userId: "some_user_id",    keepCurrentActiveOrganization: false,});
Parameters
namestringrequired

组织名称。

slugstringrequired

组织短名称(slug)。

logostring

组织徽标。

metadataRecord<string, any>

组织元数据。

userIdstring

组织创建者的用户 ID。 @serverOnly - 如果提供会话头,则忽略此字段。

keepCurrentActiveOrganizationboolean

新建组织后是否保持当前活跃组织。

互斥参数

userId 和会话头不能同时使用:

  • 使用会话头时: 组织为认证会话用户创建。userId 字段将被静默忽略
  • 不使用会话头(仅服务器端): 组织为指定的 userId 创建。

管理员说明: 若需代表其他用户创建组织,必须在服务器端调用 API,且不传递会话头

限制谁可以创建组织

默认情况下,任何用户都可以创建组织。若要限制,设置 allowUserToCreateOrganization 选项为返回布尔值的函数,或直接为 truefalse

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

检查组织短名称是否已被占用

您可以使用客户端提供的 checkSlug 函数检查组织短名称是否已被占用。此函数接受一个包含以下属性的对象:

POST/organization/check-slug
const { data, error } = await authClient.organization.checkSlug({    slug: "my-org", // required});
Parameters
slugstringrequired

要检查的组织短名称。

组织钩子

您可以使用钩子来自定义组织操作,钩子会在各种组织相关活动的前后运行。Better Auth 提供了两种配置钩子方式:

  1. 遗留的 organizationCreation 钩子(已弃用,改用 organizationHooks
  2. 现代的 organizationHooks(推荐)- 提供对所有组织活动的全面控制

组织创建与管理钩子

控制组织生命周期操作:

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        // 组织创建前钩子
        beforeCreateOrganization: async ({ organization, user }) => {
          // 创建组织前运行自定义逻辑
          // 可选地修改组织数据
          return {
            data: {
              ...organization,
              metadata: {
                customField: "value",
              },
            },
          };
        },

        afterCreateOrganization: async ({ organization, member, user }) => {
          // 创建组织后运行自定义逻辑
          // 例如,创建默认资源,发送通知
          await setupDefaultResources(organization.id);
        },

        // 组织更新前钩子
        beforeUpdateOrganization: async ({ organization, user, member }) => {
          // 验证更新,应用业务规则
          return {
            data: {
              ...organization,
              name: organization.name?.toLowerCase(),
            },
          };
        },

        afterUpdateOrganization: async ({ organization, user, member }) => {
          // 同步更改到外部系统
          await syncOrganizationToExternalSystems(organization);
        },
      },
    }),
  ],
});

遗留的 organizationCreation 钩子仍受支持但已弃用。 新项目请使用 organizationHooks.beforeCreateOrganizationorganizationHooks.afterCreateOrganization

成员钩子

控制组织内成员操作:

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        // 添加成员前
        beforeAddMember: async ({ member, user, organization }) => {
          // 自定义验证或修改
          console.log(`Adding ${user.email} to ${organization.name}`);

          // 可选地修改成员数据
          return {
            data: {
              ...member,
              role: "custom-role", // 覆盖角色
            },
          };
        },

        // 添加成员后
        afterAddMember: async ({ member, user, organization }) => {
          // 发送欢迎邮件,创建默认资源等
          await sendWelcomeEmail(user.email, organization.name);
        },

        // 移除成员前
        beforeRemoveMember: async ({ member, user, organization }) => {
          // 清理用户资源,发送通知等
          await cleanupUserResources(user.id, organization.id);
        },

        // 移除成员后
        afterRemoveMember: async ({ member, user, organization }) => {
          await logMemberRemoval(user.id, organization.id);
        },

        // 更新成员角色前
        beforeUpdateMemberRole: async ({
          member,
          newRole,
          user,
          organization,
        }) => {
          // 验证角色变更权限
          if (newRole === "owner" && !hasOwnerUpgradePermission(user)) {
            throw new Error("Cannot upgrade to owner role");
          }

          // 可选地修改角色
          return {
            data: {
              role: newRole,
            },
          };
        },

        // 更新成员角色后
        afterUpdateMemberRole: async ({
          member,
          previousRole,
          user,
          organization,
        }) => {
          await logRoleChange(user.id, previousRole, member.role);
        },
      },
    }),
  ],
});

邀请钩子

控制邀请流程:

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        // 创建邀请前
        beforeCreateInvitation: async ({
          invitation,
          inviter,
          organization,
        }) => {
          // 自定义验证或过期逻辑
          const customExpiration = new Date(
            Date.now() + 1000 * 60 * 60 * 24 * 7
          ); // 7 天

          return {
            data: {
              ...invitation,
              expiresAt: customExpiration,
            },
          };
        },

        // 创建邀请后
        afterCreateInvitation: async ({
          invitation,
          inviter,
          organization,
        }) => {
          // 发送自定义邀请邮件,统计指标等
          await sendCustomInvitationEmail(invitation, organization);
        },

        // 接受邀请前
        beforeAcceptInvitation: async ({ invitation, user, organization }) => {
          // 接受前额外验证
          await validateUserEligibility(user, organization);
        },

        // 接受邀请后
        afterAcceptInvitation: async ({
          invitation,
          member,
          user,
          organization,
        }) => {
          // 设置用户账户,分配默认资源
          await setupNewMemberResources(user, organization);
        },

        // 拒绝邀请前后
        beforeRejectInvitation: async ({ invitation, user, organization }) => {
          // 记录拒绝原因,通知邀请者等
        },

        afterRejectInvitation: async ({ invitation, user, organization }) => {
          await notifyInviterOfRejection(invitation.inviterId, user.email);
        },

        // 取消邀请前后
        beforeCancelInvitation: async ({
          invitation,
          cancelledBy,
          organization,
        }) => {
          // 验证取消权限
        },

        afterCancelInvitation: async ({
          invitation,
          cancelledBy,
          organization,
        }) => {
          await logInvitationCancellation(invitation.id, cancelledBy.id);
        },
      },
    }),
  ],
});

团队钩子

控制团队操作(启用团队时生效):

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      teams: { enabled: true },
      organizationHooks: {
        // 创建团队前
        beforeCreateTeam: async ({ team, user, organization }) => {
          // 验证团队名称,应用命名规范
          return {
            data: {
              ...team,
              name: team.name.toLowerCase().replace(/\s+/g, "-"),
            },
          };
        },

        // 创建团队后
        afterCreateTeam: async ({ team, user, organization }) => {
          // 创建默认团队资源、频道等
          await createDefaultTeamResources(team.id);
        },

        // 更新团队前
        beforeUpdateTeam: async ({ team, updates, user, organization }) => {
          // 验证更新,应用业务规则
          return {
            data: {
              ...updates,
              name: updates.name?.toLowerCase(),
            },
          };
        },

        // 更新团队后
        afterUpdateTeam: async ({ team, user, organization }) => {
          await syncTeamChangesToExternalSystems(team);
        },

        // 删除团队前
        beforeDeleteTeam: async ({ team, user, organization }) => {
          // 备份团队数据,通知成员
          await backupTeamData(team.id);
        },

        // 删除团队后
        afterDeleteTeam: async ({ team, user, organization }) => {
          await cleanupTeamResources(team.id);
        },

        // 团队成员操作
        beforeAddTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          // 验证团队成员限制,权限
          const memberCount = await getTeamMemberCount(team.id);
          if (memberCount >= 10) {
            throw new Error("Team is full");
          }
        },

        afterAddTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          await grantTeamAccess(user.id, team.id);
        },

        beforeRemoveTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          // 备份用户的团队专属数据
          await backupTeamMemberData(user.id, team.id);
        },

        afterRemoveTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          await revokeTeamAccess(user.id, team.id);
        },
      },
    }),
  ],
});

钩子错误处理

所有钩子支持错误处理。在 before 钩子中抛出错误将阻止操作执行:

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { APIError } from "better-auth/api";

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        beforeAddMember: async ({ member, user, organization }) => {
          // 检查用户是否存在未处理违规
          const violations = await checkUserViolations(user.id);
          if (violations.length > 0) {
            throw new APIError("BAD_REQUEST", {
              message:
                "User has pending violations and cannot join organizations",
            });
          }
        },

        beforeCreateTeam: async ({ team, user, organization }) => {
          // 验证团队名称唯一性
          const existingTeam = await findTeamByName(team.name, organization.id);
          if (existingTeam) {
            throw new APIError("BAD_REQUEST", {
              message: "Team name already exists in this organization",
            });
          }
        },
      },
    }),
  ],
});

列出用户所在组织

要列出用户所在的所有组织,可以使用 useListOrganizations 钩子。它实现了响应式地获取用户成员组织列表的功能。

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>)
}
page.svelte
<script lang="ts">
  import { authClient } from "$lib/auth-client";
  const organizations = authClient.useListOrganizations();
</script>

<h1>组织</h1>

{#if $organizations.isPending}

  <p>加载中...</p>
{:else if !$organizations.data?.length}
  <p>未找到组织。</p>
{:else}
  <ul>
    {#each $organizations.data as organization}
      <li>{organization.name}</li>
    {/each}
  </ul>
{/if}
organization.vue
<script lang="ts">;
export default {
    setup() {
        const organizations = authClient.useListOrganizations()
        return { organizations };
    }
};
</script>

<template>
    <div>
        <h1>组织</h1>
        <div v-if="organizations.isPending">加载中...</div>
        <div v-else-if="organizations.data === null">未找到组织。</div>
        <ul v-else>
            <li v-for="organization in organizations.data" :key="organization.id">
                {{ organization.name }}
            </li>
        </ul>
    </div>
</template>

或者,如果不想使用钩子,也可以调用 organization.list

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

活跃组织

活跃组织是用户当前工作的工作区。默认情况下,用户登录时活跃组织设置为 null。您可以为用户会话设置活跃组织。

并非所有情况下都需要在会话中持久化活跃组织。 您可以仅在客户端管理活跃组织。例如, 多个标签页可以有不同的活跃组织。

设置活跃组织

您可以调用 organization.setActive 函数设置活跃组织。它会将活跃组织设置到用户会话中。

在某些应用中,您可能希望能取消设置活跃组织。 这时可以调用此接口,将 organizationId 设置为 null

POST/organization/set-active
const { data, error } = await authClient.organization.setActive({    organizationId: "org-id",    organizationSlug: "org-slug",});
Parameters
organizationIdstring | null

要设置的活跃组织 ID。可以为 null 以取消活跃组织。

organizationSlugstring

要设置的活跃组织短名称。如果未传递 organizationId,也可设置为 null 取消活跃组织。

要在会话创建时自动设置活跃组织,可以使用 数据库钩子。您需要实现逻辑,以确定初始活跃组织。

auth.ts
export const auth = betterAuth({
  import { betterAuth } from "better-auth";

  databaseHooks: {
    session: {
      create: {
        before: async (session) => {
          // 自定义设置初始活跃组织逻辑
          const organization = await getInitialOrganization(session.userId);
          return {
            data: {
              ...session,
              activeOrganizationId: organization?.id,
            },
          };
        },
      },
    },
  },
});

使用活跃组织

要获取用户的活跃组织,可以调用 useActiveOrganization 钩子。该钩子返回用户的活跃组织并会在其变化时重新评估。

client.tsx
import { authClient } from "@/lib/auth-client"

function App(){
    const { data: activeOrganization } = authClient.useActiveOrganization()
    return (
        <div>
            {activeOrganization ? <p>{activeOrganization.name}</p> : null}
        </div>
    )
}
client.tsx
<script lang="ts">
import { authClient } from "$lib/auth-client";
const activeOrganization = authClient.useActiveOrganization();
</script>

<h2>活跃组织</h2>

{#if $activeOrganization.isPending}
<p>加载中...</p>
{:else if $activeOrganization.data === null}
<p>未找到活跃组织。</p>
{:else}
<p>{$activeOrganization.data.name}</p>
{/if}
organization.vue
<script lang="ts">;
export default {
    setup() {
        const activeOrganization = authClient.useActiveOrganization();
        return { activeOrganization };
    }
};
</script>

<template>
    <div>
        <h2>活跃组织</h2>
        <div v-if="activeOrganization.isPending">加载中...</div>
        <div v-else-if="activeOrganization.data === null">无活跃组织。</div>
        <div v-else>
            {{ activeOrganization.data.name }}
        </div>
    </div>
</template>

获取完整组织信息

要获取组织的完整详情,可以使用 getFullOrganization 函数。默认情况下,如果不传任何属性,将使用活跃组织。

GET/organization/get-full-organization
const { data, error } = await authClient.organization.getFullOrganization({    query: {        organizationId: "org-id",        organizationSlug: "org-slug",        membersLimit: 100,    },});
Parameters
organizationIdstring

要获取的组织 ID。默认使用活跃组织。

organizationSlugstring

要获取的组织短名称。

membersLimitnumber

要获取的成员数量限制。默认使用 membershipLimit 选项,默认值为 100。

更新组织

要更新组织信息,可以使用 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",});
Parameters
dataObjectrequired

要更新组织的部分数据。

namestring

组织名称。

slugstring

组织短名称。

logostring

组织徽标。

metadataRecord<string, any> | null

组织元数据。

organizationIdstring

要更新的组织 ID。

删除组织

要删除用户拥有的组织,可以使用 organization.delete

POST/organization/delete
const { data, error } = await authClient.organization.delete({    organizationId: "org-id", // required});
Parameters
organizationIdstringrequired

要删除的组织 ID。

如果用户在指定组织中拥有必要权限(默认角色为所有者),则会删除所有成员、邀请和组织信息。

您可以通过 organizationDeletion 选项配置组织删除的处理方式:

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

const auth = betterAuth({
  plugins: [
    organization({
      disableOrganizationDeletion: true, // 完全禁用删除功能
      organizationHooks: {
        beforeDeleteOrganization: async (data, request) => {
          // 删除组织前回调
        },
        afterDeleteOrganization: async (data, request) => {
          // 删除组织后回调
        },
      },
    }),
  ],
});

邀请

要添加成员到组织,首先需要向用户发送邀请。用户会收到包含邀请链接的邮件或短信。用户接受邀请后,将被添加到组织中。

设置邀请邮件

要实现成员邀请功能,首先需要为 better-auth 实例配置 sendInvitationEmail。此函数负责向用户发送邀请邮件。

您需要构造并发送邀请链接给用户,该链接应包含邀请 ID,用户点击时用于调用 acceptInvitation

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,
        });
      },
    }),
  ],
});

发送邀请

要邀请用户加入组织,可以使用客户端提供的 invite 函数。此函数接受如下对象:

POST/organization/invite-member
const { data, error } = await authClient.organization.inviteMember({    email: "example@gmail.com", // required    role: "member", // required    organizationId: "org-id",    resend: true,    teamId: "team-id",});
Parameters
emailstringrequired

受邀用户的邮箱地址。

rolestring | string[]required

指定赋予用户的角色,支持 adminmemberowner

organizationIdstring

要邀请用户加入的组织 ID,默认活跃组织。

resendboolean

若用户已被邀请,是否重发邀请邮件。

teamIdstring

要邀请用户加入的团队 ID。

  • 若用户已是组织成员,邀请将被取消。- 若用户已被邀请(非重发),邀请邮件不会重复发送。- 若设置了 cancelPendingInvitationsOnReInvitetrue,则会取消已存在邀请并发送新邀请。

接受邀请

用户收到邀请邮件后,可点击邀请链接接受邀请。邀请链接应包含邀请 ID,接受时用于调用对应接口。

请确保用户登录后调用 acceptInvitation

POST/organization/accept-invitation
const { data, error } = await authClient.organization.acceptInvitation({    invitationId: "invitation-id", // required});
Parameters
invitationIdstringrequired

要接受的邀请 ID。

邮箱验证要求

如果组织配置中启用了 requireEmailVerificationOnInvitation,则用户必须验证邮箱后才能接受邀请,确保只有验证过邮箱的用户能加入组织。

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      requireEmailVerificationOnInvitation: true, 
      async sendInvitationEmail(data) {
        // 邮件发送逻辑
      },
    }),
  ],
});

取消邀请

若发出邀请后需要取消,可以调用此方法。

若用户想要拒绝邀请,详见拒绝邀请

POST/organization/cancel-invitation
await authClient.organization.cancelInvitation({    invitationId: "invitation-id", // required});
Parameters
invitationIdstringrequired

要取消的邀请 ID。

拒绝邀请

用户收到邀请但想拒绝,可调用此方法。

POST/organization/reject-invitation
await authClient.organization.rejectInvitation({    invitationId: "invitation-id", // required});
Parameters
invitationIdstringrequired

要拒绝的邀请 ID。

与接受邀请类似,启用 requireEmailVerificationOnInvitation 后,拒绝邀请也需要邮箱验证。未验证邮箱的用户拒绝时会报错。

获取邀请

您可以使用客户端的 organization.getInvitation 获取邀请信息。需提供邀请 ID。

GET/organization/get-invitation
const { data, error } = await authClient.organization.getInvitation({    query: {        id: "invitation-id", // required    },});
Parameters
idstringrequired

要获取的邀请 ID。

列出邀请

要列出特定组织的所有邀请,可使用客户端的 listInvitations

GET/organization/list-invitations
const { data, error } = await authClient.organization.listInvitations({    query: {        organizationId: "organization-id",    },});
Parameters
organizationIdstring

可选组织 ID,默认活跃组织。

列出用户邀请

要列出给特定用户的所有邀请,可使用客户端的 listUserInvitations

import { authClient } from "@/lib/auth-client"

const invitations = await authClient.organization.listUserInvitations();

服务器端可通过查询参数传递用户邮箱。

list-user-invitations.ts
const invitations = await auth.api.listUserInvitations({
  query: {
    email: "user@example.com",
  },
});

查询参数 email 仅在服务器端可用,用于查询指定用户的邀请。

成员

列出成员

您可以使用 listMembers 函数列出组织的所有成员。

GET/organization/list-members
const { data, error } = await authClient.organization.listMembers({    query: {        organizationId: "organization-id",        limit: 100,        offset: 0,        sortBy: "createdAt",        sortDirection: "desc",        filterField: "createdAt",        filterOperator: "eq",        filterValue: "value",    },});
Parameters
organizationIdstring

可选的组织 ID,默认活跃组织。

limitnumber

返回成员数量限制。

offsetnumber

起始偏移量。

sortBystring

排序字段。

sortDirection"asc" | "desc"

排序方向。

filterFieldstring

过滤字段。

filterOperator"eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with"

过滤操作符。

filterValuestring | number | boolean | string[] | number[]

过滤值。

移除成员

您可以调用 organization.removeMember 来移除成员。

POST/organization/remove-member
const { data, error } = await authClient.organization.removeMember({    memberIdOrEmail: "user@example.com", // required    organizationId: "org-id",});
Parameters
memberIdOrEmailstringrequired

成员的 ID 或邮箱。

organizationIdstring

要移除成员的组织 ID,默认活跃组织。

更新成员角色

您可以使用 organization.updateMemberRole 来更新组织内成员的角色。若当前用户有权限,角色将被更新。

POST/organization/update-member-role
await authClient.organization.updateMemberRole({    role: ["admin", "sale"], // required    memberId: "member-id", // required    organizationId: "organization-id",});
Parameters
rolestring | string[]required

新角色,支持字符串或字符串数组。

memberIdstringrequired

要更新的成员 ID。

organizationIdstring

可选组织 ID。若不传,则使用会话头中的活跃组织。

获取当前活跃成员

获取当前活跃组织的成员详细信息,调用 organization.getActiveMember

GET/organization/get-active-member
const { data: member, error } = await authClient.organization.getActiveMember();

获取当前活跃成员角色

获取当前活跃组织的成员角色,调用 organization.getActiveMemberRole

GET/organization/get-active-member-role
const { data: { role }, error } = await authClient.organization.getActiveMemberRole();

添加成员

如果想直接添加成员到组织(不发送邀请),可以使用只能在服务器调用的 addMember 接口。

const data = await auth.api.addMember({    body: {        userId: "user-id",        role: ["admin", "sale"], // required        organizationId: "org-id",        teamId: "team-id",    },});
Parameters
userIdstring | null

要添加的用户 ID。如果为 null,则需传递会话头。

rolestring | string[]required

指定赋予成员的角色。

organizationIdstring

可选组织 ID。默认使用活跃组织。

teamIdstring

可选团队 ID。

退出组织

调用 organization.leave 函数,让当前用户退出组织。

POST/organization/leave
await authClient.organization.leave({    organizationId: "organization-id", // required});
Parameters
organizationIdstringrequired

要退出的组织 ID。

访问控制

组织插件提供了灵活的访问控制系统。您可以根据用户在组织中的角色来控制其访问权限。您可以基于角色定义自己的权限集。

角色

默认情况下,组织中有三种角色:

owner(所有者):默认创建组织的用户,拥有组织的完全控制权,并可执行任何操作。

admin(管理员):拥有除删除组织和更改所有者外的完全控制权。

member(成员):权限受限,仅能读取组织数据,无权限创建、更新或删除资源。

用户可拥有多个角色。多个角色以逗号(",")分隔的字符串存储。

权限

默认定义了三个资源,每个资源具有两到三个操作。

organization

update delete

member

create update delete

invitation

create cancel

所有者拥有所有资源和操作的完整控制。管理员拥有除删除组织和更改所有者权之外的完整控制。成员仅有读取权限。

自定义权限

插件提供了简单的方式,为每个角色定义自定义权限。

创建访问控制

先调用 createAccessControl 函数并传入声明对象。声明对象以资源名作为key,动作数组作为值。

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";

/**
 * 请使用 `as const` 以便 TypeScript 正确推断类型。
 */
const statement = { 
    project: ["create", "share", "update", "delete"], 
} as const; 

const ac = createAccessControl(statement); 

为减小包体积,请从 better-auth/plugins/access 导入,而非 better-auth/plugins

创建角色

创建访问控制后,可以根据权限声明创建角色。

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

创建自定义角色时会覆盖预设权限。若想保留原有权限,需要引入 defaultStatements,并将其和新声明、角色权限合并。

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, 
});

将角色传递给插件

创建角色后,将它们同时传给服务端和客户端的组织插件。

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
            }
        }),
    ],
});

同时也需传递访问控制和角色给客户端插件。

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

访问控制使用

权限检查

可调用 api 提供的 hasPermission 方法检测用户权限。

has-permission.ts
import { auth } from "@/lib/auth"

await auth.api.hasPermission({
  headers: await headers(),
  body: {
    permissions: {
      project: ["create"], // 需与访问控制结构一致
    },
  },
});

// 可同时检查多个资源权限
await auth.api.hasPermission({
  headers: await headers(),
  body: {
    permissions: {
      project: ["create"],
      sale: ["create"],
    },
  },
});

若需要在客户端从服务器检查权限,可使用客户端的 hasPermission

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

// 同时检查多个权限
const canCreateProjectAndCreateSale =
  await authClient.organization.hasPermission({
    permissions: {
      project: ["create"],
      sale: ["create"],
    },
  });

检查角色权限

定义好角色权限后,客户端可用 checkRolePermission 方法同步检查权限,避免每次请求服务器。

auth-client.ts
import { authClient } from "@/lib/auth-client"

const canCreateProject = authClient.organization.checkRolePermission({
  permissions: {
    organization: ["delete"],
  },
  role: "admin",
});

// 同时检查多个权限
const canCreateProjectAndCreateSale =
  authClient.organization.checkRolePermission({
    permissions: {
      organization: ["delete"],
      member: ["delete"],
    },
    role: "admin",
  });

该方法不会包含任何动态角色,因其是客户端同步执行。 如需包含动态角色权限,请使用 hasPermission API。


动态访问控制

动态访问控制允许您在运行时为组织创建角色。通过在数据库表中存储组织关联的角色及权限实现。

启用动态访问控制

要启用动态访问控制,请向服务器和客户端插件传入 dynamicAccessControl 配置项,设置 enabled: true

确保服务器端的 ac 实例已预定义,因其用于推断可用权限。

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { ac } from "@/auth/permissions";

export const auth = betterAuth({
    plugins: [
        organization({ 
            ac, // 必须定义以支持动态访问控制
            dynamicAccessControl: { 
              enabled: true, 
            }, 
        }) 
    ]
})
auth-client.ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
    plugins: [
        organizationClient({ 
            dynamicAccessControl: { 
              enabled: true, 
            }, 
        }) 
    ]
})

启用后,需要迁移新增数据库中 organizationRole 表。

authClient.organization.checkRolePermission 函数仍不会包含动态角色,因其是客户端同步执行。 请使用 hasPermission API 以包含动态角色权限检测。

创建角色

运行时为组织创建新角色,可调用 createRole

仅具备 ac 资源中 create 权限的角色可创建新角色。 默认只有管理员和所有者拥有该权限。 且无法添加超出自身权限的权限。

POST/organization/create-role
// 使用自定义资源或权限时,确保在组织的 `ac` 配置中定义。const permission = {  project: ["create", "update", "delete"]}await authClient.organization.createRole({    role: "my-unique-role", // required    permission: permission,    organizationId: "organization-id",});
Parameters
rolestringrequired

角色名,需唯一。

permissionRecord<string, string[]>

赋予角色的权限。

organizationIdstring

创建角色的组织 ID,默认活跃组织。

之后可随意调用 updateMemberRole,将新角色赋予成员!

删除角色

删除角色时调用 deleteRole,需提供 roleNameroleId 以及 organizationId

POST/organization/delete-role
await authClient.organization.deleteRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",});
Parameters
roleNamestring

角色名,或使用 roleId 替代。

roleIdstring

角色 ID,或使用 roleName 替代。

organizationIdstring

删除角色的组织 ID,默认活跃组织。

列出角色

调用 listOrgRoles 列出组织内的所有角色。 该操作需要成员具有 ac 资源中的 read 权限。

GET/organization/list-roles
const { data: roles, error } = await authClient.organization.listRoles({    query: {        organizationId: "organization-id",    },});
Parameters
organizationIdstring

组织 ID,默认活跃组织。

获取指定角色

调用 getOrgRole 获取特定角色,需传入 roleNameroleId。 该操作需要成员具有 ac 资源中的 read 权限。

GET/organization/get-role
const { data: role, error } = await authClient.organization.getRole({    query: {        roleName: "my-role",        roleId: "role-id",        organizationId: "organization-id",    },});
Parameters
roleNamestring

角色名,或使用 roleId 替代。

roleIdstring

角色 ID,或使用 roleName 替代。

organizationIdstring

组织 ID,默认活跃组织。

更新角色

调用 updateOrgRole 更新角色,需传入 roleNameroleId

POST/organization/update-role
const { data: updatedRole, error } = await authClient.organization.updateRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",    data: { // required        permission: { project: ["create", "update", "delete"] },        roleName: "my-new-role",    },});
Parameters
roleNamestring

角色名,或使用 roleId 替代。

roleIdstring

角色 ID,或使用 roleName 替代。

organizationIdstring

组织 ID,默认活跃组织。

dataObjectrequired

待更新数据

permissionRecord<string, string[]>

可选更新角色权限。

roleNamestring

可选更新角色名。

配置选项

以下是可传递给 dynamicAccessControl 对象的选项列表。

enabled

启用或禁用动态访问控制,默认禁用。

organization({
  dynamicAccessControl: {
    enabled: true
  }
})

maximumRolesPerOrganization

限制每个组织可创建角色数量。

默认无限制。

organization({
  dynamicAccessControl: {
    maximumRolesPerOrganization: 10
  }
})

也可以传入返回数字的函数。

organization({
  dynamicAccessControl: {
    maximumRolesPerOrganization: async (organizationId) => { 
      const organization = await getOrganization(organizationId); 
      return organization.plan === "pro" ? 100 : 10; 
    } 
  }
})

额外字段

要在 organizationRole 表中添加额外字段,可传递 additionalFields 配置项。

organization({
  schema: {
    organizationRole: {
      additionalFields: {
        // 角色颜色!
        color: {
          type: "string",
          defaultValue: "#ffffff",
        },
        //... 其他字段
      },
    },
  },
})

若未使用 inferOrgAdditionalFields 推断额外字段,可以使用它推断。

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient, inferOrgAdditionalFields } from "better-auth/client/plugins"
import type { auth } from "@/lib/auth" // 仅导入 auth 对象类型

export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            schema: inferOrgAdditionalFields<typeof auth>()
        })
    ]
})

否则,可以直接传递 schema 值,和服务器端插件相同。

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            schema: {
                organizationRole: {
                    additionalFields: {
                        color: {
                            type: "string",
                            defaultValue: "#ffffff",
                        }
                    }
                }
            }
        })
    ]
})

团队

团队允许您在组织内对成员进行分组。团队功能提供了额外的组织结构,可用于更细粒度地管理权限。

启用团队

传递 teams 配置项给服务器和客户端插件启用团队:

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      teams: {
        enabled: true,
        maximumTeams: 10, // 可选:限制团队数量
        allowRemovingAllTeams: false, // 可选:禁止移除最后一个团队
      },
    }),
  ],
});
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,
      },
    }),
  ],
});

团队管理

创建团队

在组织内创建新团队:

POST/organization/create-team
const { data, error } = await authClient.organization.createTeam({    name: "my-team", // required    organizationId: "organization-id",});
Parameters
namestringrequired

团队名称。

organizationIdstring

团队所属组织 ID,默认活跃组织。

列出团队

获取组织内所有团队:

GET/organization/list-teams
const { data, error } = await authClient.organization.listTeams({    query: {        organizationId: "organization-id",    },});
Parameters
organizationIdstring

组织 ID,默认活跃组织。

更新团队

更新团队详情:

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(),    },});
Parameters
teamIdstringrequired

要更新的团队 ID。

dataObjectrequired

部分更新数据。

namestring

团队名称。

organizationIdstring

归属组织 ID。

createdAtDate

创建时间。

updatedAtDate

更新时间。

移除团队

从组织删除团队:

POST/organization/remove-team
const { data, error } = await authClient.organization.removeTeam({    teamId: "team-id", // required    organizationId: "organization-id",});
Parameters
teamIdstringrequired

要移除的团队 ID。

organizationIdstring

所属组织 ID,默认活跃组织。

设置活跃团队

设置当前活跃团队;teamIdnull 时取消活跃团队。

POST/organization/set-active-team
const { data, error } = await authClient.organization.setActiveTeam({    teamId: "team-id",});
Parameters
teamIdstring

要设置为活跃的团队 ID。

列出用户团队

列出当前用户所属的所有团队。

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

列出团队成员

列出指定团队的成员。

POST/organization/list-team-members
const { data, error } = await authClient.organization.listTeamMembers({    query: {        teamId: "team-id",    },});
Parameters
teamIdstring

团队 ID。不传时返回活跃团队成员。

添加团队成员

添加成员到团队。

POST/organization/add-team-member
const { data, error } = await authClient.organization.addTeamMember({    teamId: "team-id", // required    userId: "user-id", // required});
Parameters
teamIdstringrequired

所属团队。

userIdstringrequired

用户 ID。

移除团队成员

从团队移除成员。

POST/organization/remove-team-member
const { data, error } = await authClient.organization.removeTeamMember({    teamId: "team-id", // required    userId: "user-id", // required});
Parameters
teamIdstringrequired

所属团队。

userIdstringrequired

用户 ID。

团队权限

团队使用组织权限体系。管理团队需要以下权限:

  • team:create - 创建团队
  • team:update - 更新团队
  • team:delete - 删除团队

默认情况下:

  • 组织所有者和管理员可以管理团队
  • 普通成员无管理权限

团队配置选项

团队功能支持以下配置:

  • maximumTeams: 限制每组织最大团队数

    teams: {
      enabled: true,
      maximumTeams: 10 // 固定数目
      // OR
      maximumTeams: async ({ organizationId, session }, ctx) => {
        // 基于组织计划动态限制
        const plan = await getPlan(organizationId)
        return plan === 'pro' ? 20 : 5
      },
      maximumMembersPerTeam: 10 // 固定数目
      // OR
      maximumMembersPerTeam: async ({ teamId, session, organizationId }, ctx) => {
        // 基于团队计划动态限制
        const plan = await getPlan(organizationId, teamId)
        return plan === 'pro' ? 50 : 10
      },
    }
  • allowRemovingAllTeams: 是否允许移除最后一个团队

    teams: {
      enabled: true,
      allowRemovingAllTeams: false // 禁止移除最后一个团队
    }

团队成员

邀请成员加入组织时可指定团队:

await authClient.organization.inviteMember({
  email: "user@example.com",
  role: "member",
  teamId: "team-id",
});

用户接受邀请后会加入指定团队。

数据库模式

启用团队时,数据库中新增 teamteamMember 表。

表名:team

Table
字段
类型
描述
id
string
pk
团队唯一标识
name
string
团队名称
organizationId
string
fk
所属组织 ID
createdAt
Date
团队创建时间
updatedAt
Date
?
更新时间

表名:teamMember

Table
字段
类型
描述
id
string
pk
团队成员唯一标识
teamId
string
fk
所属团队 ID
userId
string
fk
用户 ID
createdAt
Date
团队成员加入时间

模式

组织插件向数据库添加以下表:

组织

表名:organization

Table
字段
类型
描述
id
string
pk
组织唯一标识
name
string
组织名称
slug
string
组织短名称
logo
string
?
组织徽标
metadata
string
?
组织额外元数据
createdAt
Date
组织创建时间

成员

表名:member

Table
字段
类型
描述
id
string
pk
成员唯一标识
userId
string
fk
用户 ID
organizationId
string
fk
组织 ID
role
string
用户在组织中的角色
createdAt
Date
成员加入组织时间

邀请

表名:invitation

Table
字段
类型
描述
id
string
pk
邀请唯一标识
email
string
用户邮箱地址
inviterId
string
fk
邀请者 ID
organizationId
string
fk
组织 ID
role
string
用户在组织中的角色
status
string
邀请状态
createdAt
Date
邀请创建时间
expiresAt
Date
邀请过期时间

启用团队时,需要在邀请表添加如下字段:

Table
字段
类型
描述
teamId
string
?
团队 ID

会话

表名:session

需向会话表中添加两个字段,用于存储活跃组织 ID 和活跃团队 ID。

Table
字段
类型
描述
activeOrganizationId
string
?
活跃组织 ID
activeTeamId
string
?
活跃团队 ID

组织角色(可选)

表名:organizationRole

Table
字段
类型
描述
id
string
组织角色唯一标识
organizationId
string
fk
组织 ID
role
string
角色名
permission
string
角色权限
createdAt
Date
创建时间
updatedAt
Date
更新时间

团队(可选)

表名:team

Table
字段
类型
描述
id
string
pk
团队唯一标识
name
string
团队名称
organizationId
string
fk
组织 ID
createdAt
Date
创建时间
updatedAt
Date
?
更新时间

表名:teamMember

Table
字段
类型
描述
id
string
pk
团队成员唯一标识
teamId
string
fk
团队 ID
userId
string
fk
用户 ID
createdAt
Date
成员加入时间

表名:invitation

Table
字段
类型
描述
teamId
string
?
团队 ID

自定义模式

若需更改表名或字段,可传递 schema 选项给组织插件。

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

const auth = betterAuth({
  plugins: [
    organization({
      schema: {
        organization: {
          modelName: "organizations", // 将 organization 表映射为 organizations
          fields: {
            name: "title", // 将 name 字段映射为 title
          },
          additionalFields: {
            // 为组织表添加新字段
            myCustomField: {
              type: "string",
              input: true,
              required: false,
            },
          },
        },
      },
    }),
  ],
});

额外字段

Better Auth v1.3 起,您可轻松为 organizationinvitationmemberteam 表添加自定义字段。

添加额外字段后,相应 API 端点会自动接收和返回这些字段。比如在 organization 表中添加字段,createOrganization 接口会支持请求体和响应中包含该字段。

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

const auth = betterAuth({
  plugins: [
    organization({
      schema: {
        organization: {
          additionalFields: {
            myCustomField: {
              type: "string", 
              input: true, 
              required: false, 
            }, 
          },
        },
      },
    }),
  ],
});

若要推断额外字段,可使用 inferOrgAdditionalFields 函数。它会根据 auth 对象类型推断。

auth-client.ts
import { createAuthClient } from "better-auth/client";
import {
  inferOrgAdditionalFields,
  organizationClient,
} from "better-auth/client/plugins";
import type { auth } from "@/lib/auth" // 仅导入 auth 类型

const client = createAuthClient({
  plugins: [
    organizationClient({
      schema: inferOrgAdditionalFields<typeof auth>(),
    }),
  ],
});

若无法导入 auth 类型,也可传入 schema 对象来推断。

auth-client.ts
import { createAuthClient } from "better-auth/client";
import {
  inferOrgAdditionalFields,
  organizationClient,
} from "better-auth/client/plugins";

const client = createAuthClient({
  plugins: [
    organizationClient({
      schema: inferOrgAdditionalFields({
        organization: {
          additionalFields: {
            newField: {
              type: "string", 
            }, 
          },
        },
      }),
    }),
  ],
});

示例用法

await client.organization.create({
  name: "Test",
  slug: "test",
  newField: "123", // 允许
  //@ts-expect-error - 不存在字段
  unavailableField: "123", // 不允许
});

选项

allowUserToCreateOrganization: boolean | ((user: User) => Promise<boolean> | boolean) - 决定用户是否可创建组织的函数。默认 true。设置 false 禁止创建。

organizationLimit: number | ((user: User) => Promise<boolean> | boolean) - 用户允许创建的最大组织数。默认无限制。传函数时,函数应返回 true 表示已达限制(禁止新建),返回 false 表示未达限制(允许新建)。

creatorRole: admin | owner - 创建组织的用户角色,默认 owner,可设为 admin

membershipLimit: number | ((user: User, organization: Organization) => Promise<number> | number) - 组织内允许的最大成员数。默认 100。

sendInvitationEmail: async (data) => Promise<void> - 发送邀请邮件的异步函数。

invitationExpiresIn : number - 邀请链接有效时间(秒),默认 48 小时(2 天)。

cancelPendingInvitationsOnReInvite: boolean - 如果用户已邀请,是否取消待处理邀请。默认 false

invitationLimit: number | ((user: User) => Promise<boolean> | boolean) - 用户允许的最大邀请数。默认 100。

requireEmailVerificationOnInvitation: boolean - 是否要求用户在接受或拒绝邀请前先验证邮箱。默认 false。开启后,未验证邮箱的用户会被拒绝操作邀请。