organization-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Setting Up Organizations

组织设置

When adding organizations to your application, configure the
organization
plugin with appropriate limits and permissions.
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
    }),
  ],
});
Note: After adding the plugin, run
npx @better-auth/cli migrate
to add the required database tables.
在应用中添加组织功能时,请为
organization
插件配置合适的限制与权限。
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
    }),
  ],
});
注意:添加插件后,请运行
npx @better-auth/cli migrate
以添加所需的数据库表。

Client-Side Setup

客户端设置

Add the client plugin to access organization methods:
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

创建组织

Organizations are the top-level entity for grouping users. When created, the creator is automatically assigned the
owner
role.
ts
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" },
  });
};
组织是用于分组用户的顶级实体。创建组织时,创建者会自动被分配
owner
角色。
ts
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) => {
    // Premium users get more organizations
    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
userId
parameter cannot be used alongside session headers.
管理员可代其他用户创建组织(仅服务端支持):
ts
await auth.api.createOrganization({
  body: {
    name: "Client Organization",
    slug: "client-org",
    userId: "user-id-who-will-be-owner", // `userId` is required
  },
});
注意
userId
参数不能与会话头一起使用。

Active Organizations

活跃组织

The active organization is stored in the session and scopes subsequent API calls. Always set an active organization after the user selects one.
ts
const setActive = async (organizationId: string) => {
  const { data, error } = await authClient.organization.setActive({
    organizationId,
  });
};
Many endpoints use the active organization when
organizationId
is not provided:
ts
// These use the active organization automatically
await authClient.organization.listMembers();
await authClient.organization.listInvitations();
await authClient.organization.inviteMember({ email: "user@example.com", role: "member" });
活跃组织会存储在会话中,并限定后续API调用的作用域。用户选择组织后,请务必设置活跃组织。
ts
const setActive = async (organizationId: string) => {
  const { data, error } = await authClient.organization.setActive({
    organizationId,
  });
};
当未提供
organizationId
时,许多端点会自动使用活跃组织:
ts
// These use the active organization automatically
await authClient.organization.listMembers();
await authClient.organization.listInvitations();
await authClient.organization.inviteMember({ email: "user@example.com", role: "member" });

Getting Full Organization Data

获取完整组织数据

Retrieve the active organization with all its members, invitations, and teams:
ts
const { data } = await authClient.organization.getFullOrganization();
// data.organization, data.members, data.invitations, data.teams
获取包含所有成员、邀请及团队信息的活跃组织:
ts
const { data } = await authClient.organization.getFullOrganization();
// data.organization, data.members, data.invitations, data.teams

Members

成员

Members are users who belong to an organization. Each member has a role that determines their permissions.
成员是属于某一组织的用户。每个成员都拥有一个决定其权限的角色。

Adding Members (Server-Side)

添加成员(服务端)

Add members directly without invitations (useful for admin operations):
ts
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: "member",
    organizationId: "org-id",
  },
});
Note: 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

分配多个角色

Members can have multiple roles for fine-grained permissions:
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

移除成员

Remove members by ID or email:
ts
await authClient.organization.removeMember({
  memberIdOrEmail: "user@example.com",
});
Important: The last owner cannot be removed. Assign the owner role to another member first.
通过ID或邮箱移除成员:
ts
await authClient.organization.removeMember({
  memberIdOrEmail: "user@example.com",
});
重要提示:最后一位组织所有者无法被移除。请先将所有者角色分配给其他成员。

Updating Member Roles

更新成员角色

ts
await authClient.organization.updateMemberRole({
  memberId: "member-id",
  role: "admin",
});
ts
await authClient.organization.updateMemberRole({
  memberId: "member-id",
  role: "admin",
});

Membership Limits

成员数量限制

Control the maximum number of members per organization:
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

邀请

The invitation system allows admins to invite users via email. Configure email sending to enable 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}">
              Accept Invitation
            </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",
});

Creating Shareable Invitation URLs

创建可分享的邀请链接

For sharing via Slack, SMS, or in-app notifications:
ts
const { data } = await authClient.organization.getInvitationURL({
  email: "newuser@example.com",
  role: "member",
  callbackURL: "https://yourapp.com/dashboard",
});

// Share data.url via any channel
Note: This endpoint does not call
sendInvitationEmail
. Handle delivery yourself.
用于通过Slack、短信或应用内通知分享:
ts
const { data } = await authClient.organization.getInvitationURL({
  email: "newuser@example.com",
  role: "member",
  callbackURL: "https://yourapp.com/dashboard",
});

// Share data.url via any channel
注意:该端点不会调用
sendInvitationEmail
,请自行处理发送。

Accepting Invitations

接受邀请

ts
await authClient.organization.acceptInvitation({
  invitationId: "invitation-id",
});
ts
await authClient.organization.acceptInvitation({
  invitationId: "invitation-id",
});

Invitation 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 days (default: 48 hours)
  invitationLimit: 100, // Max pending invitations per org
  cancelPendingInvitationsOnReInvite: true, // Cancel old invites when re-inviting
});

Roles & Permissions

角色与权限

The plugin provides role-based access control (RBAC) with three default roles:
RoleDescription
owner
Full access, can delete organization
admin
Can manage members, invitations, settings
member
Basic access to organization resources
该插件提供基于角色的访问控制(RBAC),包含三个默认角色:
角色描述
owner
完全权限,可删除组织
admin
可管理成员、邀请及设置
member
对组织资源拥有基础访问权限

Checking Permissions

检查权限

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

if (data?.hasPermission) {
  // User can manage members
}
ts
const { data } = await authClient.organization.hasPermission({
  permission: "member:write",
});

if (data?.hasPermission) {
  // User can manage members
}

Client-Side Permission Checks

客户端权限检查

For UI rendering without API calls:
ts
const canManageMembers = authClient.organization.checkRolePermission({
  role: "admin",
  permissions: ["member:write"],
});
Note: For dynamic access control, the client side role permission check will not work. Please use the
hasPermission
endpoint.
无需调用API即可用于UI渲染:
ts
const canManageMembers = authClient.organization.checkRolePermission({
  role: "admin",
  permissions: ["member:write"],
});
注意:对于动态访问控制,客户端角色权限检查将无法工作,请使用
hasPermission
端点。

Teams

团队

Teams allow grouping members within an organization.
团队允许在组织内对成员进行分组。

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

管理团队成员

ts
// Add a member to a team (must be org member first)
await authClient.organization.addTeamMember({
  teamId: "team-id",
  userId: "user-id",
});

// Remove from team (stays in org)
await authClient.organization.removeTeamMember({
  teamId: "team-id",
  userId: "user-id",
});
ts
// Add a member to a team (must be org member first)
await authClient.organization.addTeamMember({
  teamId: "team-id",
  userId: "user-id",
});

// Remove from team (stays in org)
await authClient.organization.removeTeamMember({
  teamId: "team-id",
  userId: "user-id",
});

Active Teams

活跃团队

Similar to active organizations, set an active team for the session:
ts
await authClient.organization.setActiveTeam({
  teamId: "team-id",
});
与活跃组织类似,可为会话设置活跃团队:
ts
await authClient.organization.setActiveTeam({
  teamId: "team-id",
});

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, // Max teams per org
      maximumMembersPerTeam: 50, // Max members per team
      allowRemovingAllTeams: false, // Prevent removing last team
  }
});

Dynamic Access Control

动态访问控制

For applications needing custom roles per organization at runtime, enable 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"],
  },
});
ts
await authClient.organization.createRole({
  role: "moderator",
  permission: {
    member: ["read"],
    invitation: ["read"],
  },
});

Updating and Deleting Roles

更新与删除角色

ts
// Update role permissions
await authClient.organization.updateRole({
  roleId: "role-id",
  permission: {
    member: ["read", "write"],
  },
});

// Delete a custom role
await authClient.organization.deleteRole({
  roleId: "role-id",
});
Note: Pre-defined roles (owner, admin, member) cannot be deleted. Roles assigned to members cannot be deleted until members are reassigned.
ts
// Update role permissions
await authClient.organization.updateRole({
  roleId: "role-id",
  permission: {
    member: ["read", "write"],
  },
});

// Delete a custom role
await authClient.organization.deleteRole({
  roleId: "role-id",
});
注意:预定义角色(owner、admin、member)无法删除。已分配给成员的角色,需先重新分配成员角色后才能删除。

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

Schema Customization

Schema自定义

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

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
  • 最后一位组织所有者无法被移除
  • 最后一位组织所有者无法退出组织
  • 无法从最后一位所有者身上移除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

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, // Disable via config
});
或通过钩子实现软删除:
ts
organization({
  hooks: {
    organization: {
      beforeDelete: async ({ organization }) => {
        // Archive instead of delete
        await archiveOrganization(organization.id);
        throw new Error("Organization archived, not deleted");
      },
    },
  },
});

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({
      // 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`);
          },
        },
      },
    }),
  ],
});