organization-best-practices by better-auth/skills
npx skills add https://github.com/better-auth/skills --skill organization-best-practicesorganization() 插件organizationClient() 插件npx @better-auth/cli migrateimport { 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()],
});
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
创建者会自动被分配 owner 角色。
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" },
});
};
根据用户属性限制谁可以创建组织:
organization({
allowUserToCreateOrganization: async (user) => {
return user.emailVerified === true;
},
organizationLimit: async (user) => {
// 高级用户获得更多组织
return user.plan === "premium" ? 20 : 3;
},
});
管理员可以为其他用户创建组织(仅限服务器端):
await auth.api.createOrganization({
body: {
name: "Client Organization",
slug: "client-org",
userId: "user-id-who-will-be-owner", // `userId` 是必需的
},
});
注意:userId 参数不能与会话头一起使用。
存储在会话中,并限定后续 API 调用的范围。在用户选择一个组织后设置。
const setActive = async (organizationId: string) => {
const { data, error } = await authClient.organization.setActive({
organizationId,
});
};
当未提供 organizationId 时,许多端点会使用活动组织(例如 listMembers、listInvitations、inviteMember 等)。
使用 getFullOrganization() 来检索包含所有成员、邀请和团队的活动组织。
await auth.api.addMember({
body: {
userId: "user-id",
role: "member",
organizationId: "org-id",
},
});
对于客户端添加成员,请改用邀请系统。
await auth.api.addMember({
body: {
userId: "user-id",
role: ["admin", "moderator"],
organizationId: "org-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) {
// 用户可以管理成员
}
使用 checkRolePermission({ role, permissions }) 进行客户端 UI 渲染(仅限静态)。对于动态访问控制,请使用 hasPermission 端点。
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
teams: {
enabled: true
}
}),
],
});
const { data } = await authClient.organization.createTeam({
name: "Engineering",
});
使用 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 })。预定义角色(owner、admin、member)不能被删除。分配给成员的角色在重新分配前不能被删除。
在组织生命周期的各个节点执行自定义逻辑:
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: "new-owner-member-id",
role: "owner",
});
// 然后前一个所有者可以被降级或移除
删除组织会移除所有关联数据(成员、邀请、团队)。防止意外删除:
organization({
disableOrganizationDeletion: true, // 通过配置禁用
});
或者通过钩子实现软删除:
organization({
hooks: {
organization: {
beforeDelete: async ({ organization }) => {
// 归档而不是删除
await archiveOrganization(organization.id);
throw new Error("组织已归档,未删除");
},
},
},
});
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} 已创建`);
},
},
},
}),
],
});
每周安装量
3.6K
仓库
GitHub 星标
147
首次出现
2026年2月10日
安全审计
安装于
opencode3.3K
codex3.3K
gemini-cli3.3K
github-copilot3.3K
amp3.2K
kimi-cli3.2K
organization() plugin to server configorganizationClient() plugin to client confignpx @better-auth/cli migrateimport { 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
}),
],
});
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [organizationClient()],
});
The creator is automatically assigned the owner role.
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" },
});
};
Restrict who can create organizations based on user attributes:
organization({
allowUserToCreateOrganization: async (user) => {
return user.emailVerified === true;
},
organizationLimit: async (user) => {
// Premium users get more organizations
return user.plan === "premium" ? 20 : 3;
},
});
Administrators can create organizations for other users (server-side only):
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.
Stored in the session and scopes subsequent API calls. Set after user selects one.
const setActive = async (organizationId: string) => {
const { data, error } = await authClient.organization.setActive({
organizationId,
});
};
Many endpoints use the active organization when organizationId is not provided (listMembers, listInvitations, inviteMember, etc.).
Use getFullOrganization() to retrieve the active org with all members, invitations, and teams.
await auth.api.addMember({
body: {
userId: "user-id",
role: "member",
organizationId: "org-id",
},
});
For client-side member additions, use the invitation system instead.
await auth.api.addMember({
body: {
userId: "user-id",
role: ["admin", "moderator"],
organizationId: "org-id",
},
});
Use removeMember({ memberIdOrEmail }). The last owner cannot be removed — assign ownership to another member first.
Use 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: `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>
`,
});
},
}),
],
});
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",
});
// Share data.url via any channel
This endpoint does not call sendInvitationEmail — handle delivery yourself.
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
});
Default roles: owner (full access), admin (manage members/invitations/settings), member (basic access).
const { data } = await authClient.organization.hasPermission({
permission: "member:write",
});
if (data?.hasPermission) {
// User can manage members
}
Use checkRolePermission({ role, permissions }) for client-side UI rendering (static only). For dynamic access control, use the hasPermission endpoint.
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
teams: {
enabled: true
}
}),
],
});
const { data } = await authClient.organization.createTeam({
name: "Engineering",
});
Use addTeamMember({ teamId, userId }) (member must be in org first) and removeTeamMember({ teamId, userId }) (stays in org).
Set active team with setActiveTeam({ teamId }).
organization({
teams: {
maximumTeams: 20, // Max teams per org
maximumMembersPerTeam: 50, // Max members per team
allowRemovingAllTeams: false, // Prevent removing last team
}
});
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"],
},
});
Use updateRole({ roleId, permission }) and deleteRole({ roleId }). Pre-defined roles (owner, admin, member) cannot be deleted. Roles assigned to members cannot be deleted until reassigned.
Execute custom logic at various points in the organization lifecycle:
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);
},
},
},
});
Customize table names, field names, and add additional fields:
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,
},
},
},
},
});
Always ensure ownership transfer before removing the current owner:
// Transfer ownership first
await authClient.organization.updateMemberRole({
memberId: "new-owner-member-id",
role: "owner",
});
// Then the previous owner can be demoted or removed
Deleting an organization removes all associated data (members, invitations, teams). Prevent accidental deletion:
organization({
disableOrganizationDeletion: true, // Disable via config
});
Or implement soft delete via hooks:
organization({
hooks: {
organization: {
beforeDelete: async ({ organization }) => {
// Archive instead of delete
await archiveOrganization(organization.id);
throw new Error("Organization archived, not deleted");
},
},
},
});
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`);
},
},
},
}),
],
});
Weekly Installs
3.6K
Repository
GitHub Stars
147
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode3.3K
codex3.3K
gemini-cli3.3K
github-copilot3.3K
amp3.2K
kimi-cli3.2K
97,600 周安装