组织

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

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

安装

将插件添加到您的 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: "我的组织", // required    slug: "my-org", // required    logo: "https://example.com/logo.png",    metadata,    userId: "some_user_id",    keepCurrentActiveOrganization: false,});
Parameters
namestringrequired

组织名称。

slugstringrequired

组织短名称。

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:
                "用户有待处理的违规,无法加入组织",
            });
          }
        },

        beforeCreateTeam: async ({ team, user, organization }) => {
          // 验证团队名称唯一性
          const existingTeam = await findTeamByName(team.name, organization.id);
          if (existingTeam) {
            throw new APIError("BAD_REQUEST", {
              message: "此组织中已存在相同的团队名称",
            });
          }
        },
      },
    }),
  ],
});

列出用户所在组织

要列出用户所在的所有组织,可以使用 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
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  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。

  • 如果用户已经是该组织成员,邀请将被取消。
  • 如果用户已经被邀请到该组织,除非将 resend 设为 true,否则不会再次发送邀请。
  • 如果将 cancelPendingInvitationsOnReInvite 设为 true,当用户已被邀请到组织且发送新邀请时,之前的邀请将被取消。

接受邀请

用户收到邀请邮件后,可点击邀请链接接受邀请。邀请链接应包含邀请 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 函数并传入 statement 对象来创建访问控制器。statement 对象应以资源名作为键,以操作数组作为值。

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。默认为用户当前活跃组织。

获取指定角色

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。默认为当前活跃组织。

更新角色

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 值,就像在服务器端的 org 插件中一样。

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 | null

要设置为当前活跃团队的团队 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

我们应返回其成员的团队。如果未提供此项,则返回当前活跃团队的成员。

添加团队成员

添加成员到团队。

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

应从团队中移除的用户。

团队权限

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

  • team:create - 创建新团队
  • team:update - 更新团队详情
  • team:delete - 移除团队

默认情况下:

  • 组织所有者和管理员可以管理团队
  • 普通成员不能创建、更新或删除团队

团队配置选项

团队功能支持以下配置:

  • maximumTeams: 限制每个组织的团队数量

    teams: {
      enabled: true,
      maximumTeams: 10 // 固定数目
      // 或
      maximumTeams: async ({ organizationId, session }, ctx) => {
        // 基于组织计划动态限制
        const plan = await getPlan(organizationId)
        return plan === 'pro' ? 20 : 5
      },
      maximumMembersPerTeam: 10 // 固定数目
      // 或
      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
Unique identifier for each team
name
string
-
The name of the team
organizationId
string
FK
The ID of the organization
createdAt
Date
-
Timestamp of when the team was created
updatedAt ?
Date
-
Timestamp of when the team was created

表名:teamMember

Table
字段
类型
描述
id
string
PK
Unique identifier for each team member
teamId
string
FK
Unique identifier for each team
userId
string
FK
The ID of the user
createdAt ?
Date
-
Timestamp of when the team member was created

模式

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

组织

表名:organization

Table
字段
类型
描述
id
string
PK
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
字段
类型
描述
id
string
PK
Unique identifier for each member
userId
string
FK
The ID of the user
organizationId
string
FK
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
字段
类型
描述
id
string
PK
Unique identifier for each invitation
email
string
-
The email address of the user
inviterId
string
FK
The ID of the inviter
organizationId
string
FK
The ID of the organization
role ?
string
-
The role of the user in the organization
status
string
-
The status of the invitation
createdAt
Date
-
Timestamp of when the invitation was created
expiresAt
Date
-
Timestamp of when the invitation expires

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

Table
字段
类型
描述
teamId ?
string
-
The ID of the team

会话

表名:session

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

Table
字段
类型
描述
activeOrganizationId ?
string
-
The ID of the active organization
activeTeamId ?
string
-
The ID of the active team

Organization Roles (Optional)

Table name: organizationRole

Table
字段
类型
描述
id
string
-
Unique identifier for each organization role
organizationId
string
FK
The ID of the organization
role
string
-
The name of the role
permission
string
-
The permission of the role
createdAt
Date
-
Timestamp of when the organization role was created
updatedAt ?
Date
-
Timestamp of when the organization role was updated

团队(可选)

表名:team

Table
字段
类型
描述
id
string
PK
Unique identifier for each team
name
string
-
The name of the team
organizationId
string
FK
The ID of the organization
createdAt
Date
-
Timestamp of when the team was created
updatedAt ?
Date
-
Timestamp of when the team was created

表名:teamMember

Table
字段
类型
描述
id
string
PK
Unique identifier for each team member
teamId
string
FK
Unique identifier for each team
userId
string
FK
The ID of the user
createdAt ?
Date
-
Timestamp of when the team member was created

表名:invitation

Table
字段
类型
描述
teamId ?
string
-
The ID of the team

自定义模式

若需更改表名或字段,可传递 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。开启后,未验证邮箱的用户会被拒绝操作邀请。