Organization

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

安装

将插件添加到您的 auth 配置

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

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

迁移数据库

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

npx @better-auth/cli migrate
npx @better-auth/cli 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,    keepCurrentActiveOrganization: false,});
PropDescriptionType
name
组织的名称。
string
slug
组织的 slug。
string
logo?
组织的标志。
string
metadata?
组织的元数据。
Record<string, any>
keepCurrentActiveOrganization?
创建新组织后是否保持当前活动组织活跃。
boolean

限制谁可以创建组织

默认情况下,任何用户都可以创建组织。要限制此操作,请将 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"; 
      }, 
    }),
  ],
});

检查组织 slug 是否已被占用

要检查组织 slug 是否已被占用,您可以使用客户端提供的 checkSlug 函数。该函数接受一个具有以下属性的对象:

POST
/organization/check-slug
const { data, error } = await authClient.organization.checkSlug({    slug: "my-org", // required});
PropDescriptionType
slug
要检查的组织 slug。
string

组织钩子

您可以使用在各种组织相关活动之前和之后运行的钩子来自定义组织操作。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
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
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 { 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>Organizations</h1>

{#if $organizations.isPending}

  <p>Loading...</p>
{:else if !$organizations.data?.length}
  <p>No organizations found.</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>Organizations</h1>
        <div v-if="organizations.isPending">Loading...</div>
        <div v-else-if="organizations.data === null">No organizations found.</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();

活动组织

活动组织是用户当前正在使用的 workspace。默认情况下,当用户登录时,活动组织设置为 null。您可以将活动组织设置到用户会话中。

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

设置活动组织

您可以通过调用 organization.setActive 函数来设置活动组织。它将为用户会话设置活动组织。

在某些应用程序中,您可能希望能够取消设置活动 组织。在这种情况下,您可以调用此端点并将 organizationId 设置为 null

POST
/organization/set-active
const { data, error } = await authClient.organization.setActive({    organizationId: "org-id",    organizationSlug: "org-slug",});
PropDescriptionType
organizationId?
要设置为活动的组织 ID。它可以是 null 以取消设置活动组织。
string | null
organizationSlug?
要设置为活动的组织 slug。如果未提供 organizationId,则它可以是 null 以取消设置活动组织。
string

在创建会话时设置活动组织,您可以使用 数据库钩子

auth.ts
export const auth = betterAuth({
  databaseHooks: {
    session: {
      create: {
        before: async (session) => {
          const organization = await getActiveOrganization(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>Active Organization</h2>

{#if $activeOrganization.isPending}
<p>Loading...</p>
{:else if $activeOrganization.data === null}
<p>No active organization found.</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>Active organization</h2>
        <div v-if="activeOrganization.isPending">Loading...</div>
        <div v-else-if="activeOrganization.data === null">No active organization.</div>
        <div v-else>
            {{ activeOrganization.data.name }}
        </div>
    </div>
</template>

获取完整组织

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

GET
/organization/get-full-organization
const { data, error } = await authClient.organization.getFullOrganization({    organizationId: "org-id",    organizationSlug: "org-slug",    membersLimit: 100,});
PropDescriptionType
organizationId?
要获取的组织 ID。默认情况下,它将使用活动组织。
string
organizationSlug?
要获取的组织 slug。
string
membersLimit?
要获取的成员限制。默认情况下,它使用 membershipLimit 选项,默认值为 100。
number

更新组织

要更新组织信息,您可以使用 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
要更新组织的数据的部分列表。
Object
data.name?
组织的名称。
string
data.slug?
组织的 slug。
string
data.logo?
组织的标志。
string
data.metadata?
组织的元数据。
Record<string, any> | null
organizationId?
要更新的组织 ID。
string

删除组织

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

POST
/organization/delete
const { data, error } = await authClient.organization.delete({    organizationId: "org-id", // required});
PropDescriptionType
organizationId
要删除的组织 ID。
string

如果用户在指定组织中具有必要的权限(默认:角色为 owner),则所有成员、邀请和组织信息将被移除。

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

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

邀请

要将成员添加到组织,我们首先需要向用户发送邀请。用户将收到带有邀请链接的电子邮件/SMS。一旦用户接受邀请,他们将被添加到组织中。

设置邀请电子邮件

为了使成员邀请正常工作,我们首先需要向 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 函数。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",});
PropDescriptionType
email
要邀请的用户的电子邮件地址。
string
role
要分配给用户的角色。它可以是 adminmemberguest
string | string[]
organizationId?
要邀请用户加入的组织 ID。默认使用活动组织。
string
resend?
如果用户已被邀请,则重新发送邀请电子邮件。
boolean
teamId?
要邀请用户加入的团队 ID。
string
  • 如果用户已经是组织的成员,邀请将被 取消。 - 如果用户已被邀请加入组织,除非 resend 设置为 true,否则邀请不会再次发送。 - 如果 cancelPendingInvitationsOnReInvite 设置为 true,如果用户已被邀请加入组织,则邀请将被 取消并发送新邀请。

接受邀请

当用户收到邀请电子邮件时,他们可以点击邀请链接接受邀请。邀请链接应包含邀请 ID,用于接受邀请。

确保用户登录后调用 acceptInvitation 函数。

POST
/organization/accept-invitation
const { data, error } = await authClient.organization.acceptInvitation({    invitationId: "invitation-id", // required});
PropDescriptionType
invitationId
要接受的邀请 ID。
string

电子邮件验证要求

如果在您的组织配置中启用了 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) {
        // ... 您的电子邮件发送逻辑
      },
    }),
  ],
});

邀请被接受回调

您可以配置 Better Auth 在邀请被接受时执行回调函数。这对于记录事件、更新分析、发送通知或其他任何自定义逻辑很有用,这些逻辑需要在有人加入您的组织时运行。

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

export const auth = betterAuth({
  plugins: [
    organization({
      async sendInvitationEmail(data) {
        // ... 您的邀请电子邮件逻辑
      },
      async onInvitationAccepted(data) {
        // 当邀请被接受时触发此回调
      },
    }),
  ],
});

回调接收以下数据:

  • id: 邀请 ID
  • role: 分配给用户的角色
  • organization: 用户加入的组织
  • invitation: 邀请对象
  • inviter: 发送邀请的成员(包括用户详细信息)
  • acceptedUser: 接受邀请的用户

取消邀请

如果用户已发送邀请,您可以使用此方法取消它。

如果您想了解用户如何拒绝邀请,请参阅 此处

POST
/organization/cancel-invitation
await authClient.organization.cancelInvitation({    invitationId: "invitation-id", // required});
PropDescriptionType
invitationId
要取消的邀请 ID。
string

拒绝邀请

如果此用户收到了邀请,但想拒绝它,此方法将允许您通过拒绝它来实现。

POST
/organization/reject-invitation
await authClient.organization.rejectInvitation({    invitationId: "invitation-id", // required});
PropDescriptionType
invitationId
要拒绝的邀请 ID。
string

与接受邀请类似,当 requireEmailVerificationOnInvitation 选项 启用时,拒绝邀请也需要电子邮件 验证。具有未验证电子邮件的用户在尝试拒绝邀请时将收到错误。

获取邀请

要获取邀请,您可以使用客户端提供的 organization.getInvitation 函数。您需要将邀请 ID 作为查询参数提供。

GET
/organization/get-invitation
const { data, error } = await authClient.organization.getInvitation({    id: "invitation-id", // required});
PropDescriptionType
id
要获取的邀请 ID。
string

列出邀请

要列出给定组织的所有邀请,您可以使用客户端提供的 listInvitations 函数。

GET
/organization/list-invitations
const { data, error } = await authClient.organization.listInvitations({    organizationId: "organization-id",});
PropDescriptionType
organizationId?
可选的组织 ID,用于列出邀请。如果未提供,将默认使用用户的活动组织。
string

列出用户邀请

要列出给定用户的所有邀请,您可以使用客户端提供的 listUserInvitations 函数。

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

在服务器上,您可以将用户 ID 作为查询参数传递。

api.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({    organizationId: "organization-id",    limit: 100,    offset: 0,    sortBy: "createdAt",    sortDirection: "desc",    filterField: "createdAt",    filterOperator: "eq",    filterValue: "value",});
PropDescriptionType
organizationId?
可选的组织 ID,用于列出成员。如果未提供,将默认使用用户的活动组织。
string
limit?
要返回的成员限制。
number
offset?
要从其开始的偏移量。
number
sortBy?
要排序的字段。
string
sortDirection?
要排序的方向。
"asc" | "desc"
filterField?
要过滤的字段。
string
filterOperator?
要过滤的运算符。
"eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains"
filterValue?
要过滤的值。
string

移除成员

要移除,您可以使用 organization.removeMember

POST
/organization/remove-member
const { data, error } = await authClient.organization.removeMember({    memberIdOrEmail: "user@example.com", // required    organizationId: "org-id",});
PropDescriptionType
memberIdOrEmail
要移除的成员的 ID 或电子邮件。
string
organizationId?
要移除成员的组织的 ID。如果未提供,将使用活动组织。
string

更新成员角色

要更新组织中成员的角色,您可以使用 organization.updateMemberRole。如果用户有权限更新成员的角色,则角色将被更新。

POST
/organization/update-member-role
await authClient.organization.updateMemberRole({    role: ["admin", "sale"], // required    memberId: "member-id", // required    organizationId: "organization-id",});
PropDescriptionType
role
要应用的新的角色。这可以是字符串或字符串数组,表示角色。
string | string[]
memberId
要应用角色更新的成员 ID。
string
organizationId?
可选的组织 ID,该成员是其中的一部分,以应用角色更新。如果未提供,您必须提供会话标头以获取活动组织。
string

获取活动成员

要获取活动组织的当前成员,您可以使用 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 函数。

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?
代表要添加为成员的用户的用户 ID。如果提供 null,则期望提供会话标头。
string | null
role
要分配给新成员的角色。
string | string[]
organizationId?
可选的组织 ID。如果未提供,将默认使用用户的活动组织。
string
teamId?
可选的要添加成员的团队 ID。
string

离开组织

要离开组织,您可以使用 organization.leave 函数。此函数将从组织中移除当前用户。

POST
/organization/leave
await authClient.organization.leave({    organizationId: "organization-id", // required});
PropDescriptionType
organizationId
要离开的成员的组织 ID。
string

访问控制

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

角色

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

owner: 默认情况下是创建组织的用户。所有者对组织拥有完全控制权,可以执行任何操作。

admin: 具有 admin 角色的用户对组织拥有完全控制权,除了删除组织或更改所有者。

member: 具有 member 角色的用户对组织拥有有限控制权。他们可以创建项目、邀请用户并管理他们创建的项目。

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

权限

默认情况下,有三个资源,这些资源有两个到三个操作。

organization:

update delete

member:

create update delete

invitation:

create cancel

所有者对所有资源和操作拥有完全控制权。管理员对所有资源拥有完全控制权,除了删除组织或更改所有者。成员对这些操作没有任何控制权,除了读取数据。

自定义权限

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

创建访问控制

您首先需要通过调用 createAccessControl 函数并传递语句对象来创建访问控制器。语句对象应将资源名称作为键,将操作数组作为值。

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

创建角色

创建访问控制器后,您可以创建具有您定义的权限的角色。

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

访问控制使用

Has Permission

您可以使用 api 提供的 hasPermission 操作来检查用户的权限。

api.ts
import { auth } from "@/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"],
    },
  });

Check Role Permission

定义角色和权限后,为了避免从服务器检查权限,您可以使用客户端提供的 checkRolePermission 函数。

auth-client.ts
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

确保您已在服务器 auth 插件中预定义了 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 函数。

只有包含具有 create 权限的 ac 资源的角色用户才能创建新角色。 默认情况下,只有 adminowner 角色具有此权限。您也不能添加当前角色在该组织中无法访问的权限。

提示:您可以使用组织插件配置中的 dynamicAccessControl.validateRoleName 选项来验证角色名称。 了解更多 此处

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",});
PropDescriptionType
role
要创建的角色的唯一名称。
string
permission?
要分配给角色的权限。
Record<string, string[]>
organizationId?
将创建角色的组织 ID。默认使用活动组织。
string

现在您可以自由调用 updateMemberRole 来使用您新创建的角色更新成员的角色!

删除角色

要删除角色,您可以使用 deleteRole 函数,然后提供 roleNameroleId 参数 以及 organizationId 参数。

POST
/organization/delete-role
await authClient.organization.deleteRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",});
PropDescriptionType
roleName?
要删除的角色名称。或者,您可以传递 roleId 参数代替。
string
roleId?
要删除的角色 ID。或者,您可以传递 roleName 参数代替。
string
organizationId?
将删除角色的组织 ID。默认使用活动组织。
string

列出角色

要列出角色,您可以使用 listOrgRoles 函数。 这要求成员具有 ac 资源中的 read 权限才能列出角色。

GET
/organization/list-roles
const { data: roles, error } = await authClient.organization.listRoles({    organizationId: "organization-id",});
PropDescriptionType
organizationId?
要列出的角色所属的组织 ID。默认使用用户的活动组织。
string

获取特定角色

要获取特定角色,您可以使用 getOrgRole 函数并传递 roleNameroleId 参数。 这要求成员具有 ac 资源中的 read 权限才能获取角色。

GET
/organization/get-role
const { data: role, error } = await authClient.organization.getRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",});
PropDescriptionType
roleName?
要获取的角色名称。或者,您可以传递 roleId 参数代替。
string
roleId?
要获取的角色 ID。或者,您可以传递 roleName 参数代替。
string
organizationId?
将删除角色的组织 ID。默认使用活动组织。
string

更新角色

要更新角色,您可以使用 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",    },});
PropDescriptionType
roleName?
要更新的角色名称。或者,您可以传递 roleId 参数代替。
string
roleId?
要更新的角色 ID。或者,您可以传递 roleName 参数代替。
string
organizationId?
将更新角色的组织 ID。默认使用活动组织。
string
data
将被更新的数据
Object
data.permission?
可选地更新角色的权限。
Record<string, string[]>
data.roleName?
可选地更新角色的名称。
string

配置选项

以下是可以传递给 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 插件。

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 "./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",});
PropDescriptionType
name
团队的名称。
string
organizationId?
将创建团队的组织 ID。默认使用活动组织。
string

列出团队

获取组织中的所有团队:

GET
/organization/list-teams
const { data, error } = await authClient.organization.listTeams({    query: {        organizationId: "organization-id",    },});
PropDescriptionType
query?
用于过滤或范围限定团队列表的查询参数。
Object
query.organizationId?
要列出的团队所属的组织 ID。默认使用用户的活动组织。
string

更新团队

更新团队的详细信息:

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
要更新的团队的 ID。
string
data
包含您要更新的选项的部分对象。
Object
data.name?
要更新的团队的名称。
string
data.organizationId?
团队所属的组织 ID。
string
data.createdAt?
团队创建的时间戳。
Date
data.updatedAt?
团队最后更新的时间戳。
Date

移除团队

从组织中删除团队:

POST
/organization/remove-team
const { data, error } = await authClient.organization.removeTeam({    teamId: "team-id", // required    organizationId: "organization-id",});
PropDescriptionType
teamId
要移除的团队的团队 ID。
string
organizationId?
团队所属的组织 ID。如果未提供,它将默认使用用户的活动组织。
string

设置活动团队

将给定的团队设置为当前活动团队。如果 teamIdnull,则取消设置当前活动团队。

POST
/organization/set-active-team
const { data, error } = await authClient.organization.setActiveTeam({    teamId: "team-id",});
PropDescriptionType
teamId?
要设置为当前活动团队的团队 ID。
string

列出用户团队

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

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

列出团队成员

列出给定团队的成员。

POST
/organization/list-team-members
const { data, error } = await authClient.organization.listTeamMembers({    teamId: "team-id",});
PropDescriptionType
teamId?
我们应该返回成员的团队。如果未提供,则返回当前活动团队的成员。
string

添加团队成员

将成员添加到团队。

POST
/organization/add-team-member
const { data, error } = await authClient.organization.addTeamMember({    teamId: "team-id", // required    userId: "user-id", // required});
PropDescriptionType
teamId
用户应该成为其成员的团队。
string
userId
代表要添加为成员的用户的用户 ID。
string

移除团队成员

从团队中移除成员。

POST
/organization/remove-team-member
const { data, error } = await authClient.organization.removeTeamMember({    teamId: "team-id", // required    userId: "user-id", // required});
PropDescriptionType
teamId
用户应该从中移除的团队。
string
userId
应该从团队中移除的用户。
string

团队权限

团队遵循组织的权限系统。要管理团队,用户需要以下权限:

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

默认情况下:

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

团队配置选项

团队功能支持多个配置选项:

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

    teams: {
      enabled: true,
      maximumTeams: 10 // 固定数量
      // 或
      maximumTeams: async ({ organizationId, session }, request) => {
        // 基于组织计划的动态限制
        const plan = await getPlan(organizationId)
        return plan === 'pro' ? 20 : 5
      },
      maximumMembersPerTeam: 10 // 固定数量
      // 或
      maximumMembersPerTeam: async ({ teamId, session, organizationId }, request) => {
        // 基于团队计划的动态限制
        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",
});

被邀请的成员在接受邀请后将被添加到指定的团队中。

数据库 Schema

启用团队时,数据库中将添加新的 teamteamMember 表。

表名:team

Field NameTypeKeyDescription
idstring每个团队的唯一标识符
namestring-团队的名称
organizationIdstring组织的 ID
createdAtDate-团队创建的时间戳
updatedAtDate团队创建的时间戳

表名:teamMember

Field NameTypeKeyDescription
idstring每个团队成员的唯一标识符
teamIdstring每个团队的唯一标识符
userIdstring用户的 ID
createdAtDate-团队成员创建的时间戳

Schema

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

组织

表名:organization

Field NameTypeKeyDescription
idstring每个组织的唯一标识符
namestring-组织的名称
slugstring-组织的 slug
logostring组织的标志
metadatastring组织的附加元数据
createdAtDate-组织创建的时间戳

成员

表名:member

Field NameTypeKeyDescription
idstring每个成员的唯一标识符
userIdstring用户的 ID
organizationIdstring组织的 ID
rolestring-用户在组织中的角色
createdAtDate-成员添加到组织的时间戳

邀请

表名:invitation

Field NameTypeKeyDescription
idstring每个邀请的唯一标识符
emailstring-用户的电子邮件地址
inviterIdstring邀请者的 ID
organizationIdstring组织的 ID
rolestring-用户在组织中的角色
statusstring-邀请的状态
expiresAtDate-邀请到期的时间戳

如果启用了团队,您需要向邀请表添加以下字段:

Field NameTypeKeyDescription
teamIdstring团队的 ID

会话

表名:session

您需要向会话表添加两个更多字段,以存储活动组织 ID 和活动团队 ID。

Field NameTypeKeyDescription
activeOrganizationIdstring活动组织的 ID
activeTeamIdstring活动团队的 ID

团队(可选)

表名:team

Field NameTypeKeyDescription
idstring每个团队的唯一标识符
namestring-团队的名称
organizationIdstring组织的 ID
createdAtDate-团队创建的时间戳
updatedAtDate团队创建的时间戳

表名:teamMember

Field NameTypeKeyDescription
idstring每个团队成员的唯一标识符
teamIdstring每个团队的唯一标识符
userIdstring用户的 ID
createdAtDate-团队成员创建的时间戳

表名:invitation

Field NameTypeKeyDescription
teamIdstring团队的 ID

自定义 Schema

要更改 schema 表名或字段,您可以将 schema 选项传递给组织插件。

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

额外字段

Better Auth v1.3 开始,您可以轻松地将自定义字段添加到 organizationinvitationmemberteam 表中。

当您向模型添加额外字段时,相关的 API 端点将自动接受并返回这些新属性。例如,如果您向 organization 表添加自定义字段,则 createOrganization 端点将在其请求和响应负载中包含此字段,如有需要。

auth.ts
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 "@/auth"; // 仅导入 auth 对象类型

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

如果您无法导入 auth 对象类型,您可以不带泛型使用 inferOrgAdditionalFields 函数。此函数将从 schema 对象推断额外字段。

auth-client.ts
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 - 此字段不可用
  unavalibleField: "123", // 这应该不被允许
});

选项

allowUserToCreateOrganization: boolean | ((user: User) => Promise<boolean> | boolean) - 一个确定用户是否可以创建组织的函数。默认情况下,它是 true。您可以将它设置为 false 以限制用户创建组织。

organizationLimit: number | ((user: User) => Promise<boolean> | boolean) - 用户允许的最大组织数量。默认情况下,它是 5。您可以将其设置为任何您想要的数字或返回布尔值的函数。

creatorRole: admin | owner - 创建组织的用户的角色。默认情况下,它是 owner。您可以将它设置为 admin

membershipLimit: 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。启用时,用户必须验证他们的电子邮件地址,然后才能接受或拒绝组织邀请。