better-auth-organization-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSetup
安装配置
- Add plugin to server config
organization() - Add plugin to client config
organizationClient() - Run
npx @better-auth/cli migrate - Verify: check that organization, member, invitation tables exist in your database
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
allowUserToCreateOrganization: true,
organizationLimit: 5, // Max orgs per user
membershipLimit: 100, // Max members per org
}),
],
});- 服务端配置中添加插件
organization() - 客户端配置中添加插件
organizationClient() - 执行
npx @better-auth/cli migrate - 验证:检查数据库中是否存在organization、member、invitation表
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
allowUserToCreateOrganization: true,
organizationLimit: 5, // 单用户最多可创建的组织数量
membershipLimit: 100, // 单个组织最多可容纳的成员数量
}),
],
});Client-Side Setup
客户端安装配置
ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [organizationClient()],
});ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [organizationClient()],
});Creating Organizations
创建组织
The creator is automatically assigned the role.
ownerts
const createOrg = async () => {
const { data, error } = await authClient.organization.create({
name: "My Company",
slug: "my-company",
logo: "https://example.com/logo.png",
metadata: { plan: "pro" },
});
};创建者会自动被分配角色。
ownerts
const createOrg = async () => {
const { data, error } = await authClient.organization.create({
name: "My Company",
slug: "my-company",
logo: "https://example.com/logo.png",
metadata: { plan: "pro" },
});
};Controlling Organization Creation
组织创建权限控制
Restrict who can create organizations based on user attributes:
ts
organization({
allowUserToCreateOrganization: async (user) => {
return user.emailVerified === true;
},
organizationLimit: async (user) => {
// Premium users get more organizations
return user.plan === "premium" ? 20 : 3;
},
});可基于用户属性限制谁可以创建组织:
ts
organization({
allowUserToCreateOrganization: async (user) => {
return user.emailVerified === true;
},
organizationLimit: async (user) => {
// 高级版用户可创建更多组织
return user.plan === "premium" ? 20 : 3;
},
});Creating Organizations on Behalf of Users
代用户创建组织
Administrators can create organizations for other users (server-side only):
ts
await auth.api.createOrganization({
body: {
name: "Client Organization",
slug: "client-org",
userId: "user-id-who-will-be-owner", // `userId` is required
},
});Note: The parameter cannot be used alongside session headers.
userId管理员可为其他用户创建组织(仅支持服务端调用):
ts
await auth.api.createOrganization({
body: {
name: "Client Organization",
slug: "client-org",
userId: "user-id-who-will-be-owner", // `userId`为必填参数
},
});注意:参数不可与会话请求头同时使用。
userIdActive Organizations
当前活跃组织
Stored in the session and scopes subsequent API calls. Set after user selects one.
ts
const setActive = async (organizationId: string) => {
const { data, error } = await authClient.organization.setActive({
organizationId,
});
};Many endpoints use the active organization when is not provided (, , , etc.).
organizationIdlistMemberslistInvitationsinviteMemberUse to retrieve the active org with all members, invitations, and teams.
getFullOrganization()存储在会话中,后续API调用将默认作用于该组织,用户选择组织后进行设置。
ts
const setActive = async (organizationId: string) => {
const { data, error } = await authClient.organization.setActive({
organizationId,
});
};当未提供时,多数接口都会使用当前活跃组织(如、、等)。
organizationIdlistMemberslistInvitationsinviteMember可使用获取当前活跃组织的完整信息,包含所有成员、邀请和团队。
getFullOrganization()Members
成员管理
Adding Members (Server-Side)
添加成员(服务端)
ts
await auth.api.addMember({
body: {
userId: "user-id",
role: "member",
organizationId: "org-id",
},
});For client-side member additions, use the invitation system instead.
ts
await auth.api.addMember({
body: {
userId: "user-id",
role: "member",
organizationId: "org-id",
},
});如需在客户端添加成员,请使用邀请系统。
Assigning Multiple Roles
分配多角色
ts
await auth.api.addMember({
body: {
userId: "user-id",
role: ["admin", "moderator"],
organizationId: "org-id",
},
});ts
await auth.api.addMember({
body: {
userId: "user-id",
role: ["admin", "moderator"],
organizationId: "org-id",
},
});Removing Members
移除成员
Use . The last owner cannot be removed — assign ownership to another member first.
removeMember({ memberIdOrEmail })使用方法。无法移除最后一位所有者——请先将所有权转让给其他成员。
removeMember({ memberIdOrEmail })Updating Member Roles
更新成员角色
Use .
updateMemberRole({ memberId, role })使用方法。
updateMemberRole({ memberId, role })Membership Limits
成员数量限制
ts
organization({
membershipLimit: async (user, organization) => {
if (organization.metadata?.plan === "enterprise") {
return 1000;
}
return 50;
},
});ts
organization({
membershipLimit: async (user, organization) => {
if (organization.metadata?.plan === "enterprise") {
return 1000;
}
return 50;
},
});Invitations
邀请管理
Setting Up Invitation Emails
配置邀请邮件
ts
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: `Join ${organization.name}`,
html: `
<p>${inviter.user.name} invited you to join ${organization.name}</p>
<a href="https://yourapp.com/accept-invite?id=${invitation.id}">
Accept Invitation
</a>
`,
});
},
}),
],
});ts
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: `Join ${organization.name}`,
html: `
<p>${inviter.user.name} invited you to join ${organization.name}</p>
<a href="https://yourapp.com/accept-invite?id=${invitation.id}">
接受邀请
</a>
`,
});
},
}),
],
});Sending Invitations
发送邀请
ts
await authClient.organization.inviteMember({
email: "newuser@example.com",
role: "member",
});ts
await authClient.organization.inviteMember({
email: "newuser@example.com",
role: "member",
});Shareable Invitation URLs
可分享邀请链接
ts
const { data } = await authClient.organization.getInvitationURL({
email: "newuser@example.com",
role: "member",
callbackURL: "https://yourapp.com/dashboard",
});
// Share data.url via any channelThis endpoint does not call — handle delivery yourself.
sendInvitationEmailts
const { data } = await authClient.organization.getInvitationURL({
email: "newuser@example.com",
role: "member",
callbackURL: "https://yourapp.com/dashboard",
});
// 可通过任意渠道分享data.url该接口不会触发——请自行处理链接投递。
sendInvitationEmailInvitation Configuration
邀请配置
ts
organization({
invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days (default: 48 hours)
invitationLimit: 100, // Max pending invitations per org
cancelPendingInvitationsOnReInvite: true, // Cancel old invites when re-inviting
});ts
organization({
invitationExpiresIn: 60 * 60 * 24 * 7, // 7天,默认48小时
invitationLimit: 100, // 单个组织最多可存在的待处理邀请数量
cancelPendingInvitationsOnReInvite: true, // 重新发送邀请时取消旧的待处理邀请
});Roles & Permissions
角色与权限
Default roles: (full access), (manage members/invitations/settings), (basic access).
owneradminmember默认角色:(完全访问权限)、(可管理成员/邀请/设置)、(基础访问权限)。
owneradminmemberChecking Permissions
权限校验
ts
const { data } = await authClient.organization.hasPermission({
permission: "member:write",
});
if (data?.hasPermission) {
// User can manage members
}Use for client-side UI rendering (static only). For dynamic access control, use the endpoint.
checkRolePermission({ role, permissions })hasPermissionts
const { data } = await authClient.organization.hasPermission({
permission: "member:write",
});
if (data?.hasPermission) {
// 用户拥有成员管理权限
}客户端UI渲染时可使用(仅适用于静态校验)。动态访问控制请使用接口。
checkRolePermission({ role, permissions })hasPermissionTeams
团队管理
Enabling Teams
开启团队功能
ts
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
teams: {
enabled: true,
},
}),
],
});ts
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
teams: {
enabled: true,
},
}),
],
});Creating Teams
创建团队
ts
const { data } = await authClient.organization.createTeam({
name: "Engineering",
});ts
const { data } = await authClient.organization.createTeam({
name: "Engineering",
});Managing Team Members
团队成员管理
Use (member must be in org first) and (stays in org).
addTeamMember({ teamId, userId })removeTeamMember({ teamId, userId })Set active team with .
setActiveTeam({ teamId })使用(成员需先加入组织)和(成员仍保留组织身份)。
addTeamMember({ teamId, userId })removeTeamMember({ teamId, userId })使用设置当前活跃团队。
setActiveTeam({ teamId })Team Limits
团队数量限制
ts
organization({
teams: {
maximumTeams: 20, // Max teams per org
maximumMembersPerTeam: 50, // Max members per team
allowRemovingAllTeams: false, // Prevent removing last team
},
});ts
organization({
teams: {
maximumTeams: 20, // 单个组织最多可创建的团队数量
maximumMembersPerTeam: 50, // 单个团队最多可容纳的成员数量
allowRemovingAllTeams: false, // 禁止移除最后一个团队
},
});Dynamic Access Control
动态访问控制
Enabling Dynamic Access Control
开启动态访问控制
ts
import { organization } from "better-auth/plugins";
import { dynamicAccessControl } from "@better-auth/organization/addons";
export const auth = betterAuth({
plugins: [
organization({
dynamicAccessControl: {
enabled: true,
},
}),
],
});ts
import { organization } from "better-auth/plugins";
import { dynamicAccessControl } from "@better-auth/organization/addons";
export const auth = betterAuth({
plugins: [
organization({
dynamicAccessControl: {
enabled: true,
},
}),
],
});Creating Custom Roles
创建自定义角色
ts
await authClient.organization.createRole({
role: "moderator",
permission: {
member: ["read"],
invitation: ["read"],
},
});Use and . Pre-defined roles (owner, admin, member) cannot be deleted. Roles assigned to members cannot be deleted until reassigned.
updateRole({ roleId, permission })deleteRole({ roleId })ts
await authClient.organization.createRole({
role: "moderator",
permission: {
member: ["read"],
invitation: ["read"],
},
});可使用和方法。预定义角色(owner、admin、member)不可删除。已分配给成员的角色需先重新分配后才可删除。
updateRole({ roleId, permission })deleteRole({ roleId })Lifecycle Hooks
生命周期钩子
Execute custom logic at various points in the organization lifecycle:
ts
organization({
hooks: {
organization: {
beforeCreate: async ({ data, user }) => {
// Validate or modify data before creation
return {
data: {
...data,
metadata: { ...data.metadata, createdBy: user.id },
},
};
},
afterCreate: async ({ organization, member }) => {
// Post-creation logic (e.g., send welcome email, create default resources)
await createDefaultResources(organization.id);
},
beforeDelete: async ({ organization }) => {
// Cleanup before deletion
await archiveOrganizationData(organization.id);
},
},
member: {
afterCreate: async ({ member, organization }) => {
await notifyAdmins(organization.id, `New member joined`);
},
},
invitation: {
afterCreate: async ({ invitation, organization, inviter }) => {
await logInvitation(invitation);
},
},
},
});可在组织生命周期的各个节点执行自定义逻辑:
ts
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);
},
},
},
});Schema Customization
表结构自定义
Customize table names, field names, and add additional fields:
ts
organization({
schema: {
organization: {
modelName: "workspace", // Rename table
fields: {
name: "workspaceName", // Rename fields
},
additionalFields: {
billingId: {
type: "string",
required: false,
},
},
},
member: {
additionalFields: {
department: {
type: "string",
required: false,
},
title: {
type: "string",
required: false,
},
},
},
},
});可自定义表名、字段名,也可新增额外字段:
ts
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,
},
},
},
},
});Security Considerations
安全注意事项
Owner Protection
所有者保护
- The last owner cannot be removed from an organization
- The last owner cannot leave the organization
- The owner role cannot be removed from the last owner
Always ensure ownership transfer before removing the current owner:
ts
// Transfer ownership first
await authClient.organization.updateMemberRole({
memberId: "new-owner-member-id",
role: "owner",
});
// Then the previous owner can be demoted or removed- 不可移除组织的最后一位所有者
- 最后一位所有者不可退出组织
- 不可移除最后一位所有者的所有者角色
在移除当前所有者前请先完成所有权转让:
ts
// 先转让所有权
await authClient.organization.updateMemberRole({
memberId: "new-owner-member-id",
role: "owner",
});
// 之后原所有者可被降级或移除Organization Deletion
组织删除
Deleting an organization removes all associated data (members, invitations, teams). Prevent accidental deletion:
ts
organization({
disableOrganizationDeletion: true, // Disable via config
});Or implement soft delete via hooks:
ts
organization({
hooks: {
organization: {
beforeDelete: async ({ organization }) => {
// Archive instead of delete
await archiveOrganization(organization.id);
throw new Error("Organization archived, not deleted");
},
},
},
});删除组织会移除所有关联数据(成员、邀请、团队)。可通过以下方式防止误删:
ts
organization({
disableOrganizationDeletion: true, // 通过配置禁用组织删除功能
});也可通过钩子实现软删除:
ts
organization({
hooks: {
organization: {
beforeDelete: async ({ organization }) => {
// 归档而非删除
await archiveOrganization(organization.id);
throw new Error("组织已归档,未删除");
},
},
},
});Invitation Security
邀请安全
- Invitations expire after 48 hours by default
- Only the invited email address can accept an invitation
- Pending invitations can be cancelled by organization admins
- 邀请默认48小时后过期
- 仅被邀请的邮箱地址可接受邀请
- 组织管理员可取消待处理的邀请
Complete Configuration Example
完整配置示例
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";
export const auth = betterAuth({
plugins: [
organization({
// Organization limits
allowUserToCreateOrganization: true,
organizationLimit: 10,
membershipLimit: 100,
creatorRole: "owner",
// Slugs
defaultOrganizationIdField: "slug",
// Invitations
invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
invitationLimit: 50,
sendInvitationEmail: async (data) => {
await sendEmail({
to: data.email,
subject: `Join ${data.organization.name}`,
html: `<a href="https://app.com/invite/${data.invitation.id}">Accept</a>`,
});
},
// Hooks
hooks: {
organization: {
afterCreate: async ({ organization }) => {
console.log(`Organization ${organization.name} created`);
},
},
},
}),
],
});ts
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",
// Slug配置
defaultOrganizationIdField: "slug",
// 邀请配置
invitationExpiresIn: 60 * 60 * 24 * 7, // 7天
invitationLimit: 50,
sendInvitationEmail: async (data) => {
await sendEmail({
to: data.email,
subject: `Join ${data.organization.name}`,
html: `<a href="https://app.com/invite/${data.invitation.id}">接受邀请</a>`,
});
},
// 钩子配置
hooks: {
organization: {
afterCreate: async ({ organization }) => {
console.log(`组织 ${organization.name} 已创建`);
},
},
},
}),
],
});