---
id: sm-organization-best-practices
name: "organization-best-practices"
url: https://skills.yangsir.net/skill/sm-organization-best-practices
author: better-auth
domain: hr
tags: ["organizational-design", "hr-best-practices", "team-management", "workflow-optimization", "corporate-culture"]
install_count: 16000
rating: 4.60 (20 reviews)
github: https://github.com/better-auth/skills
---

# organization-best-practices

> 此技能提供组织管理最佳实践，指导如何在服务器和客户端配置中添加组织插件，以支持组织和成员管理功能。

**Stats**: 16,000 installs · 4.6/5 (20 reviews)

## Before / After 对比

### 组织管理与用户权限配置

| Metric | Before | After | Change |
|---|---|---|---|
| - | - | - | - |
| - | - | - | - |

## Readme

# organization-best-practices

## Setup

- Add `organization()` plugin to server config

- Add `organizationClient()` plugin to client config

- Run `npx @better-auth/cli migrate`

- Verify: check that organization, member, invitation tables exist in your database

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

```

### Client-Side Setup

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

```

### Controlling Organization Creation

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

```

### Creating Organizations on Behalf of Users

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.

## Active Organizations

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.

## Members

### Adding Members (Server-Side)

```
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: "member",
    organizationId: "org-id",
  },
});

```

For client-side member additions, use the invitation system instead.

### Assigning Multiple Roles

```
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: ["admin", "moderator"],
    organizationId: "org-id",
  },
});

```

### Removing Members

Use `removeMember({ memberIdOrEmail })`. The last owner cannot be removed — assign ownership to another member first.

### Updating Member Roles

Use `updateMemberRole({ memberId, role })`.

### Membership Limits

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

```

## Invitations

### Setting Up Invitation Emails

```
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

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

```

### Shareable Invitation URLs

```
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.

### Invitation Configuration

```
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

Default roles: `owner` (full access), `admin` (manage members/invitations/settings), `member` (basic access).

### Checking Permissions

```
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.

## Teams

### Enabling Teams

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

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

```

### Creating Teams

```
const { data } = await authClient.organization.createTeam({
  name: "Engineering",
});

```

### Managing Team Members

Use `addTeamMember({ teamId, userId })` (member must be in org first) and `removeTeamMember({ teamId, userId })` (stays in org).

Set active team with `setActiveTeam({ teamId })`.

### Team Limits

```
organization({
  teams: {
      maximumTeams: 20, // Max teams per org
      maximumMembersPerTeam: 50, // Max members per team
      allowRemovingAllTeams: false, // Prevent removing last team
  }
});

```

## Dynamic Access Control

### Enabling Dynamic Access Control

```
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

```
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.

## Lifecycle Hooks

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

```

## Schema Customization

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

```

## 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:

```
// 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:

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

```

### 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

## Complete Configuration Example

```
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 Installs4.8KRepository[better-auth/skills](https://github.com/better-auth/skills)GitHub Stars158First SeenFeb 10, 2026Security Audits[Gen Agent Trust HubPass](/better-auth/skills/organization-best-practices/security/agent-trust-hub)[SocketPass](/better-auth/skills/organization-best-practices/security/socket)[SnykPass](/better-auth/skills/organization-best-practices/security/snyk)Installed onopencode4.5Kcodex4.5Kgemini-cli4.5Kgithub-copilot4.5Kamp4.4Kkimi-cli4.4K

---
*Source: https://skills.yangsir.net/skill/sm-organization-best-practices*
*Markdown mirror: https://skills.yangsir.net/api/skill/sm-organization-best-practices/markdown*