🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx skills add https://github.com/better-auth/skills --skill organization-best-practices

💡 提示:需要 Node.js 和 NPM

设置

  1. organization() 插件添加到服务器配置
  2. organizationClient() 插件添加到客户端配置
  3. 运行 npx @better-auth/cli migrate
  4. 验证:检查数据库中是否存在组织、成员、邀请表
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      allowUserToCreateOrganization: true,
      organizationLimit: 5, // 每个用户最多可创建的组织数
      membershipLimit: 100, // 每个组织最多成员数
    }),
  ],
});

客户端设置

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

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

创建组织

创建者会自动被分配 owner(所有者)角色。

const createOrg = async () => {
  const { data, error } = await authClient.organization.create({
    name: "我的公司",
    slug: "my-company",
    logo: "https://example.com/logo.png",
    metadata: { plan: "pro" },
  });
};

控制组织创建权限

根据用户属性限制谁可以创建组织:

organization({
  allowUserToCreateOrganization: async (user) => {
    return user.emailVerified === true;
  },
  organizationLimit: async (user) => {
    // 付费用户可创建更多组织
    return user.plan === "premium" ? 20 : 3;
  },
});

代表用户创建组织

管理员可以代表其他用户创建组织(仅限服务器端):

await auth.api.createOrganization({
  body: {
    name: "客户组织",
    slug: "client-org",
    userId: "将作为所有者的用户ID", // `userId` 是必需的
  },
});

注意userId 参数不能与会话标头一起使用。

活跃组织

存储在会话中,并影响后续 API 调用的范围。在用户选择一个组织后设置。

const setActive = async (organizationId: string) => {
  const { data, error } = await authClient.organization.setActive({
    organizationId,
  });
};

许多端点在没有提供 organizationId 时使用活跃组织(如 listMemberslistInvitationsinviteMember 等)。

使用 getFullOrganization() 获取包含所有成员、邀请和团队的完整活跃组织信息。

成员

添加成员(服务器端)

await auth.api.addMember({
  body: {
    userId: "用户ID",
    role: "member",
    organizationId: "组织ID",
  },
});

对于客户端的成员添加,请改用邀请系统。

分配多个角色

await auth.api.addMember({
  body: {
    userId: "用户ID",
    role: ["admin", "moderator"],
    organizationId: "组织ID",
  },
});

移除成员

使用 removeMember({ memberIdOrEmail })。最后一个所有者不能被移除——需要先将所有权转移给另一个成员。

更新成员角色

使用 updateMemberRole({ memberId, role })

成员数量限制

organization({
  membershipLimit: async (user, organization) => {
    if (organization.metadata?.plan === "enterprise") {
      return 1000;
    }
    return 50;
  },
});

邀请

设置邀请邮件

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

export const auth = betterAuth({
  plugins: [
    organization({
      sendInvitationEmail: async (data) => {
        const { email, organization, inviter, invitation } = data;

        await sendEmail({
          to: email,
          subject: `加入 ${organization.name}`,
          html: `
            <p>${inviter.user.name} 邀请您加入 ${organization.name}</p>
            <a href="https://yourapp.com/accept-invite?id=${invitation.id}">
              接受邀请
            </a>
          `,
        });
      },
    }),
  ],
});

发送邀请

await authClient.organization.inviteMember({
  email: "newuser@example.com",
  role: "member",
});

可分享的邀请链接

const { data } = await authClient.organization.getInvitationURL({
  email: "newuser@example.com",
  role: "member",
  callbackURL: "https://yourapp.com/dashboard",
});

// 通过任何渠道分享 data.url

此端点不会调用 sendInvitationEmail —— 请自行处理邮件发送。

邀请配置

organization({
  invitationExpiresIn: 60 * 60 * 24 * 7, // 7 天(默认:48 小时)
  invitationLimit: 100, // 每个组织最多待处理的邀请数
  cancelPendingInvitationsOnReInvite: true, // 重新邀请时取消旧的待处理邀请
});

角色与权限

默认角色:owner(所有者,完全访问权限)、admin(管理员,管理成员/邀请/设置)、member(成员,基本访问权限)。

检查权限

const { data } = await authClient.organization.hasPermission({
  permission: "member:write",
});

if (data?.hasPermission) {
  // 用户可以管理成员
}

对于客户端的 UI 渲染,使用 checkRolePermission({ role, permissions })(仅静态检查)。对于动态访问控制,请使用 hasPermission 端点。

团队

启用团队功能

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

export const auth = betterAuth({
  plugins: [
    organization({
        teams: {
            enabled: true
        }
    }),
  ],
});

创建团队

const { data } = await authClient.organization.createTeam({
  name: "工程部",
});

管理团队成员

使用 addTeamMember({ teamId, userId })(成员必须先加入组织)和 removeTeamMember({ teamId, userId })(成员仍保留在组织中)。

使用 setActiveTeam({ teamId }) 设置活跃团队。

团队限制

organization({
  teams: {
      maximumTeams: 20, // 每个组织最多团队数
      maximumMembersPerTeam: 50, // 每个团队最多成员数
      allowRemovingAllTeams: false, // 阻止移除最后一个团队
  }
});

动态访问控制

启用动态访问控制

import { organization } from "better-auth/plugins";
import { dynamicAccessControl } from "@better-auth/organization/addons";

export const auth = betterAuth({
  plugins: [
    organization({
        dynamicAccessControl: {
            enabled: true
        }
    }),
  ],
});

创建自定义角色

await authClient.organization.createRole({
  role: "moderator",
  permission: {
    member: ["read"],
    invitation: ["read"],
  },
});

使用 updateRole({ roleId, permission })deleteRole({ roleId })。预定义角色(所有者、管理员、成员)无法删除。已分配给成员的角色在重新分配之前不能删除。

生命周期钩子

在组织生命周期的各个阶段执行自定义逻辑:

organization({
  hooks: {
    organization: {
      beforeCreate: async ({ data, user }) => {
        // 在创建前验证或修改数据
        return {
          data: {
            ...data,
            metadata: { ...data.metadata, createdBy: user.id },
          },
        };
      },
      afterCreate: async ({ organization, member }) => {
        // 创建后逻辑(例如,发送欢迎邮件、创建默认资源)
        await createDefaultResources(organization.id);
      },
      beforeDelete: async ({ organization }) => {
        // 删除前清理
        await archiveOrganizationData(organization.id);
      },
    },
    member: {
      afterCreate: async ({ member, organization }) => {
        await notifyAdmins(organization.id, `新成员加入`);
      },
    },
    invitation: {
      afterCreate: async ({ invitation, organization, inviter }) => {
        await logInvitation(invitation);
      },
    },
  },
});

模式自定义

自定义表名、字段名,并添加额外字段:

organization({
  schema: {
    organization: {
      modelName: "workspace", // 重命名表
      fields: {
        name: "workspaceName", // 重命名字段
      },
      additionalFields: {
        billingId: {
          type: "string",
          required: false,
        },
      },
    },
    member: {
      additionalFields: {
        department: {
          type: "string",
          required: false,
        },
        title: {
          type: "string",
          required: false,
        },
      },
    },
  },
});

安全考虑

所有者保护

  • 最后一个所有者不能被从组织中移除
  • 最后一个所有者不能离开组织
  • 不能从最后一个所有者身上移除所有者角色

在移除当前所有者之前,务必先转移所有权:

// 首先转移所有权
await authClient.organization.updateMemberRole({
  memberId: "新所有者的成员ID",
  role: "owner",
});

// 然后可以降级或移除前任所有者

组织删除

删除组织会移除所有关联数据(成员、邀请、团队)。防止意外删除:

organization({
  disableOrganizationDeletion: true, // 通过配置禁用删除
});

或者通过钩子实现软删除:

organization({
  hooks: {
    organization: {
      beforeDelete: async ({ organization }) => {
        // 存档而非删除
        await archiveOrganization(organization.id);
        throw new Error("组织已存档,未删除");
      },
    },
  },
});

邀请安全

  • 邀请默认在 48 小时后过期
  • 只有被邀请的邮箱地址可以接受邀请
  • 待处理的邀请可以由组织管理员取消

完整配置示例

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

export const auth = betterAuth({
  plugins: [
    organization({
      // 组织限制
      allowUserToCreateOrganization: true,
      organizationLimit: 10,
      membershipLimit: 100,
      creatorRole: "owner",

      // 标识符
      defaultOrganizationIdField: "slug",

      // 邀请
      invitationExpiresIn: 60 * 60 * 24 * 7, // 7 天
      invitationLimit: 50,
      sendInvitationEmail: async (data) => {
        await sendEmail({
          to: data.email,
          subject: `加入 ${data.organization.name}`,
          html: `<a href="https://app.com/invite/${data.invitation.id}">接受</a>`,
        });
      },

      // 钩子
      hooks: {
        organization: {
          afterCreate: async ({ organization }) => {
            console.log(`组织 ${organization.name} 已创建`);
          },
        },
      },
    }),
  ],
});

📄 原始文档

完整文档(英文):

https://skills.sh/better-auth/skills/organization-best-practices

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。