Organization

Organization은 사용자 접근 권한 및 권한 관리를 단순화합니다. 역할과 권한을 할당하여 프로젝트 관리, 팀 협업 및 파트너십을 간소화할 수 있습니다.

설치

auth 설정에 플러그인 추가하기

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [ 
        organization() 
    ] 
})

데이터베이스 마이그레이션

마이그레이션을 실행하거나 스키마를 생성하여 필요한 필드와 테이블을 데이터베이스에 추가합니다.

npx @better-auth/cli migrate
npx @better-auth/cli generate

필드를 수동으로 추가하려면 Schema 섹션을 참조하세요.

클라이언트 플러그인 추가하기

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

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

사용법

플러그인을 설치하면 조직의 멤버와 팀을 관리하는 데 organization 플러그인을 사용할 수 있습니다. 클라이언트 플러그인은 organization 네임스페이스 아래에 메서드를 제공하며, 서버 api는 조직을 관리하는 데 필요한 엔드포인트를 제공하고 자체 백엔드에서 함수를 더 쉽게 호출할 수 있는 방법을 제공합니다.

Organization

조직 생성하기

POST
/organization/create
const metadata = { someKey: "someValue" };const { data, error } = await authClient.organization.create({    name: "My Organization", // required    slug: "my-org", // required    logo: "https://example.com/logo.png",    metadata,    keepCurrentActiveOrganization: false,});
PropDescriptionType
name
조직 이름입니다.
string
slug
조직 슬러그입니다.
string
logo?
조직 로고입니다.
string
metadata?
조직의 메타데이터입니다.
Record<string, any>
keepCurrentActiveOrganization?
새 조직을 생성한 후 현재 활성 조직을 활성 상태로 유지할지 여부입니다.
boolean

조직 생성 권한 제한하기

기본적으로 모든 사용자가 조직을 생성할 수 있습니다. 이를 제한하려면 allowUserToCreateOrganization 옵션을 부울을 반환하는 함수로 설정하거나 직접 true 또는 false로 설정하세요.

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

const auth = betterAuth({
  //...
  plugins: [
    organization({
      allowUserToCreateOrganization: async (user) => {
        const subscription = await getSubscription(user.id); 
        return subscription.plan === "pro"; 
      }, 
    }),
  ],
});

조직 슬러그 사용 여부 확인하기

조직 슬러그가 사용 중인지 확인하려면 클라이언트에서 제공하는 checkSlug 함수를 사용할 수 있습니다. 이 함수는 다음 속성을 가진 객체를 받습니다:

POST
/organization/check-slug
const { data, error } = await authClient.organization.checkSlug({    slug: "my-org", // required});
PropDescriptionType
slug
확인할 조직 슬러그입니다.
string

Organization Hooks

다양한 조직 관련 활동 전후에 실행되는 훅을 사용하여 조직 작업을 커스터마이즈할 수 있습니다. Better Auth는 두 가지 방법으로 훅을 설정할 수 있습니다:

  1. 레거시 organizationCreation hooks (더 이상 사용되지 않으며, organizationHooks를 사용하세요)
  2. 최신 organizationHooks (권장) - 모든 조직 관련 활동에 대한 포괄적인 제어를 제공합니다

조직 생성 및 관리 훅

조직 라이프사이클 작업을 제어합니다:

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

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        // 조직 생성 훅
        beforeCreateOrganization: async ({ organization, user }) => {
          // 조직이 생성되기 전에 사용자 정의 로직 실행
          // 선택적으로 조직 데이터 수정
          return {
            data: {
              ...organization,
              metadata: {
                customField: "value",
              },
            },
          };
        },

        afterCreateOrganization: async ({ organization, member, user }) => {
          // 조직이 생성된 후 사용자 정의 로직 실행
          // 예: 기본 리소스 생성, 알림 전송
          await setupDefaultResources(organization.id);
        },

        // 조직 업데이트 훅
        beforeUpdateOrganization: async ({ organization, user, member }) => {
          // 업데이트 검증, 비즈니스 규칙 적용
          return {
            data: {
              ...organization,
              name: organization.name?.toLowerCase(),
            },
          };
        },

        afterUpdateOrganization: async ({ organization, user, member }) => {
          // 외부 시스템에 변경사항 동기화
          await syncOrganizationToExternalSystems(organization);
        },
      },
    }),
  ],
});

레거시 organizationCreation 훅은 여전히 지원되지만 더 이상 사용되지 않습니다. 새 프로젝트에서는 organizationHooks.beforeCreateOrganizationorganizationHooks.afterCreateOrganization을 사용하세요.

멤버 훅

조직 내 멤버 작업을 제어합니다:

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

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        // 멤버가 조직에 추가되기 전
        beforeAddMember: async ({ member, user, organization }) => {
          // 사용자 정의 검증 또는 수정
          console.log(`${user.email}을(를) ${organization.name}에 추가 중`);

          // 선택적으로 멤버 데이터 수정
          return {
            data: {
              ...member,
              role: "custom-role", // 역할 재정의
            },
          };
        },

        // 멤버가 추가된 후
        afterAddMember: async ({ member, user, organization }) => {
          // 환영 이메일 전송, 기본 리소스 생성 등
          await sendWelcomeEmail(user.email, organization.name);
        },

        // 멤버가 제거되기 전
        beforeRemoveMember: async ({ member, user, organization }) => {
          // 사용자의 리소스 정리, 알림 전송 등
          await cleanupUserResources(user.id, organization.id);
        },

        // 멤버가 제거된 후
        afterRemoveMember: async ({ member, user, organization }) => {
          await logMemberRemoval(user.id, organization.id);
        },

        // 멤버의 역할을 업데이트하기 전
        beforeUpdateMemberRole: async ({
          member,
          newRole,
          user,
          organization,
        }) => {
          // 역할 변경 권한 검증
          if (newRole === "owner" && !hasOwnerUpgradePermission(user)) {
            throw new Error("owner 역할로 업그레이드할 수 없습니다");
          }

          // 선택적으로 역할 수정
          return {
            data: {
              role: newRole,
            },
          };
        },

        // 멤버의 역할을 업데이트한 후
        afterUpdateMemberRole: async ({
          member,
          previousRole,
          user,
          organization,
        }) => {
          await logRoleChange(user.id, previousRole, member.role);
        },
      },
    }),
  ],
});

초대 훅

초대 라이프사이클을 제어합니다:

auth.ts
export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        // 초대를 생성하기 전
        beforeCreateInvitation: async ({
          invitation,
          inviter,
          organization,
        }) => {
          // 사용자 정의 검증 또는 만료 로직
          const customExpiration = new Date(
            Date.now() + 1000 * 60 * 60 * 24 * 7
          ); // 7일

          return {
            data: {
              ...invitation,
              expiresAt: customExpiration,
            },
          };
        },

        // 초대를 생성한 후
        afterCreateInvitation: async ({
          invitation,
          inviter,
          organization,
        }) => {
          // 사용자 정의 초대 이메일 전송, 메트릭 추적 등
          await sendCustomInvitationEmail(invitation, organization);
        },

        // 초대를 수락하기 전
        beforeAcceptInvitation: async ({ invitation, user, organization }) => {
          // 수락 전 추가 검증
          await validateUserEligibility(user, organization);
        },

        // 초대를 수락한 후
        afterAcceptInvitation: async ({
          invitation,
          member,
          user,
          organization,
        }) => {
          // 사용자 계정 설정, 기본 리소스 할당
          await setupNewMemberResources(user, organization);
        },

        // 초대를 거부하기 전/후
        beforeRejectInvitation: async ({ invitation, user, organization }) => {
          // 거부 이유 로깅, 초대자에게 알림 전송
        },

        afterRejectInvitation: async ({ invitation, user, organization }) => {
          await notifyInviterOfRejection(invitation.inviterId, user.email);
        },

        // 초대를 취소하기 전/후
        beforeCancelInvitation: async ({
          invitation,
          cancelledBy,
          organization,
        }) => {
          // 취소 권한 확인
        },

        afterCancelInvitation: async ({
          invitation,
          cancelledBy,
          organization,
        }) => {
          await logInvitationCancellation(invitation.id, cancelledBy.id);
        },
      },
    }),
  ],
});

팀 훅

팀 작업을 제어합니다 (팀이 활성화된 경우):

auth.ts
export const auth = betterAuth({
  plugins: [
    organization({
      teams: { enabled: true },
      organizationHooks: {
        // 팀을 생성하기 전
        beforeCreateTeam: async ({ team, user, organization }) => {
          // 팀 이름 검증, 명명 규칙 적용
          return {
            data: {
              ...team,
              name: team.name.toLowerCase().replace(/\s+/g, "-"),
            },
          };
        },

        // 팀을 생성한 후
        afterCreateTeam: async ({ team, user, organization }) => {
          // 기본 팀 리소스, 채널 등 생성
          await createDefaultTeamResources(team.id);
        },

        // 팀을 업데이트하기 전
        beforeUpdateTeam: async ({ team, updates, user, organization }) => {
          // 업데이트 검증, 비즈니스 규칙 적용
          return {
            data: {
              ...updates,
              name: updates.name?.toLowerCase(),
            },
          };
        },

        // 팀을 업데이트한 후
        afterUpdateTeam: async ({ team, user, organization }) => {
          await syncTeamChangesToExternalSystems(team);
        },

        // 팀을 삭제하기 전
        beforeDeleteTeam: async ({ team, user, organization }) => {
          // 팀 데이터 백업, 멤버에게 알림
          await backupTeamData(team.id);
        },

        // 팀을 삭제한 후
        afterDeleteTeam: async ({ team, user, organization }) => {
          await cleanupTeamResources(team.id);
        },

        // 팀 멤버 작업
        beforeAddTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          // 팀 멤버십 제한, 권한 검증
          const memberCount = await getTeamMemberCount(team.id);
          if (memberCount >= 10) {
            throw new Error("팀이 가득 찼습니다");
          }
        },

        afterAddTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          await grantTeamAccess(user.id, team.id);
        },

        beforeRemoveTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          // 사용자의 팀 관련 데이터 백업
          await backupTeamMemberData(user.id, team.id);
        },

        afterRemoveTeamMember: async ({
          teamMember,
          team,
          user,
          organization,
        }) => {
          await revokeTeamAccess(user.id, team.id);
        },
      },
    }),
  ],
});

훅 오류 처리

모든 훅은 오류 처리를 지원합니다. before 훅에서 오류를 던지면 작업이 진행되지 않습니다:

auth.ts
import { APIError } from "better-auth/api";

export const auth = betterAuth({
  plugins: [
    organization({
      organizationHooks: {
        beforeAddMember: async ({ member, user, organization }) => {
          // 사용자의 위반 사항 확인
          const violations = await checkUserViolations(user.id);
          if (violations.length > 0) {
            throw new APIError("BAD_REQUEST", {
              message:
                "사용자에게 미해결 위반 사항이 있어 조직에 가입할 수 없습니다",
            });
          }
        },

        beforeCreateTeam: async ({ team, user, organization }) => {
          // 팀 이름 고유성 검증
          const existingTeam = await findTeamByName(team.name, organization.id);
          if (existingTeam) {
            throw new APIError("BAD_REQUEST", {
              message: "이 조직에 이미 존재하는 팀 이름입니다",
            });
          }
        },
      },
    }),
  ],
});

사용자의 조직 목록 보기

사용자가 멤버인 조직을 나열하려면 useListOrganizations 훅을 사용할 수 있습니다. 사용자가 멤버인 조직을 반응적으로 가져올 수 있습니다.

client.tsx
import { authClient } from "@/lib/auth-client"

function App(){
const { data: organizations } = authClient.useListOrganizations()
return (
  <div>
    {organizations.map((org) => (
      <p>{org.name}</p>
    ))}
  </div>)
}
page.svelte
<script lang="ts">
  import { authClient } from "$lib/auth-client";
  const organizations = authClient.useListOrganizations();
</script>

<h1>조직</h1>

{#if $organizations.isPending}

  <p>로딩 중...</p>
{:else if !$organizations.data?.length}
  <p>조직이 없습니다.</p>
{:else}
  <ul>
    {#each $organizations.data as organization}
      <li>{organization.name}</li>
    {/each}
  </ul>
{/if}
organization.vue
<script lang="ts">;
export default {
    setup() {
        const organizations = authClient.useListOrganizations()
        return { organizations };
    }
};
</script>

<template>
    <div>
        <h1>조직</h1>
        <div v-if="organizations.isPending">로딩 중...</div>
        <div v-else-if="organizations.data === null">조직이 없습니다.</div>
        <ul v-else>
            <li v-for="organization in organizations.data" :key="organization.id">
                {{ organization.name }}
            </li>
        </ul>
    </div>
</template>

또는 훅을 사용하지 않으려면 organization.list를 호출할 수 있습니다.

GET
/organization/list
const { data, error } = await authClient.organization.list();

활성 조직

활성 조직은 사용자가 현재 작업 중인 작업 공간입니다. 기본적으로 사용자가 로그인하면 활성 조직은 null로 설정됩니다. 활성 조직을 사용자 세션에 설정할 수 있습니다.

항상 활성 조직을 세션에 유지하고 싶지 않을 수 있습니다. 클라이언트 측에서만 활성 조직을 관리할 수 있습니다. 예를 들어, 여러 탭에서 다른 활성 조직을 가질 수 있습니다.

활성 조직 설정하기

organization.setActive 함수를 호출하여 활성 조직을 설정할 수 있습니다. 이렇게 하면 사용자 세션의 활성 조직이 설정됩니다.

일부 애플리케이션에서는 활성 조직을 해제하는 기능이 필요할 수 있습니다. 이 경우 organizationIdnull로 설정하여 이 엔드포인트를 호출할 수 있습니다.

POST
/organization/set-active
const { data, error } = await authClient.organization.setActive({    organizationId: "org-id",    organizationSlug: "org-slug",});
PropDescriptionType
organizationId?
활성으로 설정할 조직 ID입니다. 활성 조직을 해제하려면 null일 수 있습니다.
string | null
organizationSlug?
활성으로 설정할 조직 슬러그입니다. organizationId가 제공되지 않으면 활성 조직을 해제하려면 null일 수 있습니다.
string

세션이 생성될 때 활성 조직을 설정하려면 데이터베이스 훅을 사용할 수 있습니다.

auth.ts
export const auth = betterAuth({
  databaseHooks: {
    session: {
      create: {
        before: async (session) => {
          const organization = await getActiveOrganization(session.userId);
          return {
            data: {
              ...session,
              activeOrganizationId: organization.id,
            },
          };
        },
      },
    },
  },
});

활성 조직 사용하기

사용자의 활성 조직을 가져오려면 useActiveOrganization 훅을 호출할 수 있습니다. 사용자의 활성 조직을 반환합니다. 활성 조직이 변경되면 훅이 재평가되고 새 활성 조직을 반환합니다.

client.tsx
import { authClient } from "@/lib/auth-client"

function App(){
    const { data: activeOrganization } = authClient.useActiveOrganization()
    return (
        <div>
            {activeOrganization ? <p>{activeOrganization.name}</p> : null}
        </div>
    )
}
client.tsx
<script lang="ts">
import { authClient } from "$lib/auth-client";
const activeOrganization = authClient.useActiveOrganization();
</script>

<h2>활성 조직</h2>

{#if $activeOrganization.isPending}
<p>로딩 중...</p>
{:else if $activeOrganization.data === null}
<p>활성 조직이 없습니다.</p>
{:else}
<p>{$activeOrganization.data.name}</p>
{/if}
organization.vue
<script lang="ts">;
export default {
    setup() {
        const activeOrganization = authClient.useActiveOrganization();
        return { activeOrganization };
    }
};
</script>

<template>
    <div>
        <h2>활성 조직</h2>
        <div v-if="activeOrganization.isPending">로딩 중...</div>
        <div v-else-if="activeOrganization.data === null">활성 조직이 없습니다.</div>
        <div v-else>
            {{ activeOrganization.data.name }}
        </div>
    </div>
</template>

전체 조직 정보 가져오기

조직의 전체 세부 정보를 가져오려면 getFullOrganization 함수를 사용할 수 있습니다. 기본적으로 속성을 전달하지 않으면 활성 조직을 사용합니다.

GET
/organization/get-full-organization
const { data, error } = await authClient.organization.getFullOrganization({    organizationId: "org-id",    organizationSlug: "org-slug",    membersLimit: 100,});
PropDescriptionType
organizationId?
가져올 조직 ID입니다. 기본적으로 활성 조직을 사용합니다.
string
organizationSlug?
가져올 조직 슬러그입니다.
string
membersLimit?
가져올 멤버의 제한입니다. 기본적으로 100으로 기본 설정되는 membershipLimit 옵션을 사용합니다.
number

조직 업데이트하기

조직 정보를 업데이트하려면 organization.update를 사용할 수 있습니다.

POST
/organization/update
const { data, error } = await authClient.organization.update({    data: { // required        name: "updated-name",        slug: "updated-slug",        logo: "new-logo.url",        metadata: { customerId: "test" },    },    organizationId: "org-id",});
PropDescriptionType
data
조직을 업데이트할 데이터의 일부 목록입니다.
Object
data.name?
조직의 이름입니다.
string
data.slug?
조직의 슬러그입니다.
string
data.logo?
조직의 로고입니다.
string
data.metadata?
조직의 메타데이터입니다.
Record<string, any> | null
organizationId?
업데이트할 조직 ID입니다.
string

조직 삭제하기

사용자 소유 조직을 제거하려면 organization.delete를 사용할 수 있습니다.

POST
/organization/delete
const { data, error } = await authClient.organization.delete({    organizationId: "org-id", // required});
PropDescriptionType
organizationId
삭제할 조직 ID입니다.
string

사용자가 지정된 조직에서 필요한 권한(기본값: 역할이 owner)을 가지고 있으면 모든 멤버, 초대 및 조직 정보가 제거됩니다.

organizationDeletion 옵션을 통해 조직 삭제 처리 방법을 구성할 수 있습니다:

const auth = betterAuth({
  plugins: [
    organization({
      disableOrganizationDeletion: true, //완전히 비활성화하려면
      organizationHooks: {
        beforeDeleteOrganization: async (data, request) => {
          // 조직을 삭제하기 전에 실행할 콜백
        },
        afterDeleteOrganization: async (data, request) => {
          // 조직을 삭제한 후 실행할 콜백
        },
      },
    }),
  ],
});

초대

조직에 멤버를 추가하려면 먼저 사용자에게 초대를 보내야 합니다. 사용자는 초대 링크가 포함된 이메일/SMS를 받게 됩니다. 사용자가 초대를 수락하면 조직에 추가됩니다.

초대 이메일 설정하기

멤버 초대가 작동하려면 먼저 better-auth 인스턴스에 sendInvitationEmail을 제공해야 합니다. 이 함수는 사용자에게 초대 이메일을 보내는 역할을 합니다.

초대 링크를 구성하여 사용자에게 보내야 합니다. 링크에는 초대 ID가 포함되어야 하며, 사용자가 클릭하면 acceptInvitation 함수와 함께 사용됩니다.

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendOrganizationInvitation } from "./email";
export const auth = betterAuth({
  plugins: [
    organization({
      async sendInvitationEmail(data) {
        const inviteLink = `https://example.com/accept-invitation/${data.id}`;
        sendOrganizationInvitation({
          email: data.email,
          invitedByUsername: data.inviter.user.name,
          invitedByEmail: data.inviter.user.email,
          teamName: data.organization.name,
          inviteLink,
        });
      },
    }),
  ],
});

초대 보내기

조직에 사용자를 초대하려면 클라이언트에서 제공하는 invite 함수를 사용할 수 있습니다. invite 함수는 다음 속성을 가진 객체를 받습니다:

POST
/organization/invite-member
const { data, error } = await authClient.organization.inviteMember({    email: "example@gmail.com", // required    role: "member", // required    organizationId: "org-id",    resend: true,    teamId: "team-id",});
PropDescriptionType
email
초대할 사용자의 이메일 주소입니다.
string
role
사용자에게 할당할 역할입니다. admin, member, 또는 guest일 수 있습니다.
string | string[]
organizationId?
사용자를 초대할 조직 ID입니다. 기본값은 활성 조직입니다.
string
resend?
사용자가 이미 초대된 경우 초대 이메일을 다시 보냅니다.
boolean
teamId?
사용자를 초대할 팀 ID입니다.
string
  • 사용자가 이미 조직의 멤버인 경우 초대가 취소됩니다. - 사용자가 이미 조직에 초대된 경우 resendtrue로 설정되지 않으면 초대가 다시 전송되지 않습니다. - cancelPendingInvitationsOnReInvitetrue로 설정된 경우 사용자가 이미 조직에 초대되어 있고 새 초대가 전송되면 초대가 취소됩니다.

초대 수락하기

사용자가 초대 이메일을 받으면 초대 링크를 클릭하여 초대를 수락할 수 있습니다. 초대 링크에는 초대를 수락하는 데 사용될 초대 ID가 포함되어야 합니다.

사용자가 로그인한 후 acceptInvitation 함수를 호출해야 합니다.

POST
/organization/accept-invitation
const { data, error } = await authClient.organization.acceptInvitation({    invitationId: "invitation-id", // required});
PropDescriptionType
invitationId
수락할 초대 ID입니다.
string

이메일 확인 요구 사항

조직 구성에서 requireEmailVerificationOnInvitation 옵션이 활성화된 경우 사용자는 초대를 수락하기 전에 이메일 주소를 확인해야 합니다. 이는 확인된 사용자만 조직에 가입할 수 있도록 추가 보안 계층을 추가합니다.

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

export const auth = betterAuth({
  plugins: [
    organization({
      requireEmailVerificationOnInvitation: true, 
      async sendInvitationEmail(data) {
        // ... 이메일 전송 로직
      },
    }),
  ],
});

초대 수락 콜백

초대가 수락될 때 콜백 함수를 실행하도록 Better Auth를 구성할 수 있습니다. 이는 이벤트 로깅, 분석 업데이트, 알림 전송 또는 누군가가 조직에 가입할 때 실행해야 하는 기타 사용자 정의 로직에 유용합니다.

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

export const auth = betterAuth({
  plugins: [
    organization({
      async sendInvitationEmail(data) {
        // ... 초대 이메일 로직
      },
      async onInvitationAccepted(data) {
        // 이 콜백은 초대가 수락될 때 트리거됩니다
      },
    }),
  ],
});

콜백은 다음 데이터를 받습니다:

  • id: 초대 ID
  • role: 사용자에게 할당된 역할
  • organization: 사용자가 가입한 조직
  • invitation: 초대 객체
  • inviter: 초대를 보낸 멤버 (사용자 세부 정보 포함)
  • acceptedUser: 초대를 수락한 사용자

초대 취소하기

사용자가 초대를 보낸 경우 이 메서드를 사용하여 취소할 수 있습니다.

사용자가 초대를 거부하는 방법을 찾고 있다면 여기에서 찾을 수 있습니다.

POST
/organization/cancel-invitation
await authClient.organization.cancelInvitation({    invitationId: "invitation-id", // required});
PropDescriptionType
invitationId
취소할 초대 ID입니다.
string

초대 거부하기

이 사용자가 초대를 받았지만 거절하려는 경우 이 메서드를 사용하여 거부할 수 있습니다.

POST
/organization/reject-invitation
await authClient.organization.rejectInvitation({    invitationId: "invitation-id", // required});
PropDescriptionType
invitationId
거부할 초대 ID입니다.
string

초대 수락과 마찬가지로 초대 거부도 requireEmailVerificationOnInvitation 옵션이 활성화된 경우 이메일 확인이 필요합니다. 확인되지 않은 이메일을 가진 사용자는 초대를 거부하려고 할 때 오류를 받게 됩니다.

초대 가져오기

초대를 가져오려면 클라이언트에서 제공하는 organization.getInvitation 함수를 사용할 수 있습니다. 초대 ID를 쿼리 매개변수로 제공해야 합니다.

GET
/organization/get-invitation
const { data, error } = await authClient.organization.getInvitation({    id: "invitation-id", // required});
PropDescriptionType
id
가져올 초대 ID입니다.
string

초대 목록 보기

지정된 조직의 모든 초대를 나열하려면 클라이언트에서 제공하는 listInvitations 함수를 사용할 수 있습니다.

GET
/organization/list-invitations
const { data, error } = await authClient.organization.listInvitations({    organizationId: "organization-id",});
PropDescriptionType
organizationId?
초대를 나열할 조직의 선택적 ID입니다. 제공되지 않으면 사용자의 활성 조직으로 기본 설정됩니다.
string

사용자 초대 목록 보기

지정된 사용자의 모든 초대를 나열하려면 클라이언트에서 제공하는 listUserInvitations 함수를 사용할 수 있습니다.

auth-client.ts
const invitations = await authClient.organization.listUserInvitations();

서버에서는 사용자 ID를 쿼리 매개변수로 전달할 수 있습니다.

api.ts
const invitations = await auth.api.listUserInvitations({
  query: {
    email: "user@example.com",
  },
});

email 쿼리 매개변수는 특정 사용자에 대한 초대를 쿼리하기 위해 서버에서만 사용할 수 있습니다.

멤버

멤버 목록 보기

조직의 모든 멤버를 나열하려면 listMembers 함수를 사용할 수 있습니다.

GET
/organization/list-members
const { data, error } = await authClient.organization.listMembers({    organizationId: "organization-id",    limit: 100,    offset: 0,    sortBy: "createdAt",    sortDirection: "desc",    filterField: "createdAt",    filterOperator: "eq",    filterValue: "value",});
PropDescriptionType
organizationId?
멤버를 나열할 선택적 조직 ID입니다. 제공되지 않으면 사용자의 활성 조직으로 기본 설정됩니다.
string
limit?
반환할 멤버의 제한입니다.
number
offset?
시작할 오프셋입니다.
number
sortBy?
정렬할 필드입니다.
string
sortDirection?
정렬 방향입니다.
"asc" | "desc"
filterField?
필터링할 필드입니다.
string
filterOperator?
필터링할 연산자입니다.
"eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains"
filterValue?
필터링할 값입니다.
string

멤버 제거하기

멤버를 제거하려면 organization.removeMember를 사용할 수 있습니다.

POST
/organization/remove-member
const { data, error } = await authClient.organization.removeMember({    memberIdOrEmail: "user@example.com", // required    organizationId: "org-id",});
PropDescriptionType
memberIdOrEmail
제거할 멤버의 ID 또는 이메일입니다.
string
organizationId?
멤버를 제거할 조직의 ID입니다. 제공되지 않으면 활성 조직이 사용됩니다.
string

멤버 역할 업데이트하기

조직에서 멤버의 역할을 업데이트하려면 organization.updateMemberRole을 사용할 수 있습니다. 사용자가 멤버의 역할을 업데이트할 권한이 있으면 역할이 업데이트됩니다.

POST
/organization/update-member-role
await authClient.organization.updateMemberRole({    role: ["admin", "sale"], // required    memberId: "member-id", // required    organizationId: "organization-id",});
PropDescriptionType
role
적용할 새 역할입니다. 역할을 나타내는 문자열 또는 문자열 배열일 수 있습니다.
string | string[]
memberId
역할 업데이트를 적용할 멤버 ID입니다.
string
organizationId?
멤버가 속한 선택적 조직 ID입니다. 제공되지 않으면 활성 조직을 가져오기 위해 세션 헤더를 제공해야 합니다.
string

활성 멤버 가져오기

활성 조직의 현재 멤버를 가져오려면 organization.getActiveMember 함수를 사용할 수 있습니다. 이 함수는 활성 조직에서 사용자의 멤버 세부 정보를 반환합니다.

GET
/organization/get-active-member
const { data: member, error } = await authClient.organization.getActiveMember();

활성 멤버 역할 가져오기

활성 조직의 현재 역할 멤버를 가져오려면 organization.getActiveMemberRole 함수를 사용할 수 있습니다. 이 함수는 활성 조직에서 사용자의 멤버 역할을 반환합니다.

GET
/organization/get-active-member-role
const { data: { role }, error } = await authClient.organization.getActiveMemberRole();

멤버 추가하기

초대를 보내지 않고 조직에 멤버를 직접 추가하려면 서버에서만 호출할 수 있는 addMember 함수를 사용할 수 있습니다.

POST
/organization/add-member
const data = await auth.api.addMember({    body: {        userId: "user-id",        role: ["admin", "sale"], // required        organizationId: "org-id",        teamId: "team-id",    },});
PropDescriptionType
userId?
멤버로 추가될 사용자를 나타내는 사용자 ID입니다. null이 제공되면 세션 헤더를 제공해야 합니다.
string | null
role
새 멤버에게 할당할 역할입니다.
string | string[]
organizationId?
전달할 선택적 조직 ID입니다. 제공되지 않으면 사용자의 활성 조직으로 기본 설정됩니다.
string
teamId?
멤버를 추가할 선택적 팀 ID입니다.
string

조직 탈퇴하기

조직을 탈퇴하려면 organization.leave 함수를 사용할 수 있습니다. 이 함수는 조직에서 현재 사용자를 제거합니다.

POST
/organization/leave
await authClient.organization.leave({    organizationId: "organization-id", // required});
PropDescriptionType
organizationId
멤버가 탈퇴할 조직 ID입니다.
string

접근 제어

organization 플러그인은 매우 유연한 접근 제어 시스템을 제공합니다. 조직에서 사용자가 가진 역할에 따라 사용자의 접근 권한을 제어할 수 있습니다. 사용자의 역할에 따라 자체 권한 세트를 정의할 수 있습니다.

역할

기본적으로 조직에는 세 가지 역할이 있습니다:

owner: 기본적으로 조직을 만든 사용자입니다. 소유자는 조직에 대한 전체 제어 권한을 가지며 모든 작업을 수행할 수 있습니다.

admin: 관리자 역할을 가진 사용자는 조직 삭제 또는 소유자 변경을 제외한 조직에 대한 전체 제어 권한을 갖습니다.

member: 멤버 역할을 가진 사용자는 조직에 대한 제한된 제어 권한을 갖습니다. 프로젝트를 만들고, 사용자를 초대하고, 자신이 만든 프로젝트를 관리할 수 있습니다.

사용자는 여러 역할을 가질 수 있습니다. 여러 역할은 쉼표(",")로 구분된 문자열로 저장됩니다.

권한

기본적으로 세 가지 리소스가 있으며, 각각 2~3개의 작업이 있습니다.

organization:

update delete

member:

create update delete

invitation:

create cancel

소유자는 모든 리소스와 작업에 대한 전체 제어 권한을 갖습니다. 관리자는 조직 삭제 또는 소유자 변경을 제외한 모든 리소스에 대한 전체 제어 권한을 갖습니다. 멤버는 데이터 읽기를 제외하고는 이러한 작업에 대한 제어 권한이 없습니다.

사용자 정의 권한

플러그인은 각 역할에 대한 자체 권한 세트를 정의하는 쉬운 방법을 제공합니다.

접근 제어 생성하기

먼저 createAccessControl 함수를 호출하고 statement 객체를 전달하여 접근 제어기를 만들어야 합니다. statement 객체는 리소스 이름을 키로, 작업 배열을 값으로 가져야 합니다.

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";

/**
 * typescript가 타입을 올바르게 추론할 수 있도록 `as const`를 사용하세요
 */
const statement = { 
    project: ["create", "share", "update", "delete"], 
} as const; 

const ac = createAccessControl(statement); 

역할 생성하기

접근 제어기를 만든 후 정의한 권한을 가진 역할을 만들 수 있습니다.

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";

const statement = {
    project: ["create", "share", "update", "delete"],
} as const;

const ac = createAccessControl(statement);

const member = ac.newRole({ 
    project: ["create"], 
}); 

const admin = ac.newRole({ 
    project: ["create", "update"], 
}); 

const owner = ac.newRole({ 
    project: ["create", "update", "delete"], 
}); 

const myCustomRole = ac.newRole({ 
    project: ["create", "update", "delete"], 
    organization: ["update"], 
}); 

기존 역할에 대한 사용자 정의 역할을 만들면 해당 역할에 대해 미리 정의된 권한이 재정의됩니다. 사용자 정의 역할에 기존 권한을 추가하려면 defaultStatements를 가져와 새 statement와 병합하고 역할의 권한 세트를 기본 역할과 병합해야 합니다.

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements, adminAc } from 'better-auth/plugins/organization/access'

const statement = {
    ...defaultStatements, 
    project: ["create", "share", "update", "delete"],
} as const;

const ac = createAccessControl(statement);

const admin = ac.newRole({
    project: ["create", "update"],
    ...adminAc.statements, 
});

플러그인에 역할 전달하기

역할을 만든 후 클라이언트와 서버 모두에서 organization 플러그인에 전달할 수 있습니다.

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { ac, owner, admin, member } from "@/auth/permissions"

export const auth = betterAuth({
    plugins: [
        organization({
            ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        }),
    ],
});

클라이언트 플러그인에도 접근 제어기와 역할을 전달해야 합니다.

auth-client
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
import { ac, owner, admin, member, myCustomRole } from "@/auth/permissions"

export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        })
  ]
})

접근 제어 사용법

권한 확인하기:

api에서 제공하는 hasPermission 작업을 사용하여 사용자의 권한을 확인할 수 있습니다.

api.ts
import { auth } from "@/auth";

await auth.api.hasPermission({
  headers: await headers(),
  body: {
    permissions: {
      project: ["create"], // 접근 제어의 구조와 일치해야 합니다
    },
  },
});

// 여러 리소스 권한을 동시에 확인할 수도 있습니다
await auth.api.hasPermission({
  headers: await headers(),
  body: {
    permissions: {
      project: ["create"], // 접근 제어의 구조와 일치해야 합니다
      sale: ["create"],
    },
  },
});

서버에서 클라이언트의 사용자 권한을 확인하려면 클라이언트에서 제공하는 hasPermission 함수를 사용할 수 있습니다.

auth-client.ts
const canCreateProject = await authClient.organization.hasPermission({
  permissions: {
    project: ["create"],
  },
});

// 여러 리소스 권한을 동시에 확인할 수도 있습니다
const canCreateProjectAndCreateSale =
  await authClient.organization.hasPermission({
    permissions: {
      project: ["create"],
      sale: ["create"],
    },
  });

역할 권한 확인하기:

역할과 권한을 정의한 후 서버에서 권한을 확인하지 않고도 클라이언트에서 제공하는 checkRolePermission 함수를 사용할 수 있습니다.

auth-client.ts
const canCreateProject = authClient.organization.checkRolePermission({
  permissions: {
    organization: ["delete"],
  },
  role: "admin",
});

// 여러 리소스 권한을 동시에 확인할 수도 있습니다
const canCreateProjectAndCreateSale =
  authClient.organization.checkRolePermission({
    permissions: {
      organization: ["delete"],
      member: ["delete"],
    },
    role: "admin",
  });

모든 것이 클라이언트 측에서 동기적으로 실행되므로 동적 역할은 포함되지 않습니다. 동적 역할 및 권한 확인을 포함하려면 hasPermission API를 사용하세요.


동적 접근 제어

동적 접근 제어를 사용하면 조직에 대한 역할을 런타임에 생성할 수 있습니다. 이는 조직과 관련된 생성된 역할과 권한을 데이터베이스 테이블에 저장하여 달성됩니다.

동적 접근 제어 활성화하기

동적 접근 제어를 활성화하려면 서버 및 클라이언트 플러그인 모두에 enabledtrue로 설정된 dynamicAccessControl 구성 옵션을 전달하세요.

서버 auth 플러그인에 ac 인스턴스를 미리 정의해야 합니다. 이는 사용 가능한 권한을 추론할 수 있는 방법이기 때문에 중요합니다.

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { ac } from "@/auth/permissions";

export const auth = betterAuth({
    plugins: [ 
        organization({ 
            ac, // 동적 접근 제어가 작동하려면 정의해야 합니다
            dynamicAccessControl: { 
              enabled: true, 
            }, 
        }) 
    ] 
})
auth-client.ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
    plugins: [ 
        organizationClient({ 
            dynamicAccessControl: { 
              enabled: true, 
            }, 
        }) 
    ] 
})

데이터베이스에 새 organizationRole 테이블을 추가하려면 마이그레이션을 실행해야 합니다.

authClient.organization.checkRolePermission 함수는 모든 것이 클라이언트 측에서 동기적으로 실행되므로 동적 역할을 포함하지 않습니다. 동적 역할 확인을 포함하려면 hasPermission API를 사용하세요.

역할 생성하기

런타임에 조직에 대한 새 역할을 만들려면 createRole 함수를 사용할 수 있습니다.

ac 리소스에 create 권한이 포함된 역할을 가진 사용자만 새 역할을 만들 수 있습니다. 기본적으로 adminowner 역할만 이 권한을 갖습니다. 또한 해당 조직에서 현재 역할이 이미 액세스할 수 없는 권한은 추가할 수 없습니다.

팁: organization 플러그인 구성에서 dynamicAccessControl.validateRoleName 옵션을 사용하여 역할 이름을 검증할 수 있습니다. 여기에서 자세히 알아보세요.

POST
/organization/create-role
// 사용자 정의 리소스 또는 권한을 사용하려면,// organization 구성의 `ac` 인스턴스에 정의되어 있는지 확인하세요.const permission = {  project: ["create", "update", "delete"]}await authClient.organization.createRole({    role: "my-unique-role", // required    permission: permission,    organizationId: "organization-id",});
PropDescriptionType
role
생성할 역할의 고유한 이름입니다.
string
permission?
역할에 할당할 권한입니다.
Record<string, string[]>
organizationId?
역할이 생성될 조직 ID입니다. 기본값은 활성 조직입니다.
string

이제 자유롭게 updateMemberRole을 호출하여 새로 생성한 역할로 멤버의 역할을 업데이트할 수 있습니다!

역할 삭제하기

역할을 삭제하려면 deleteRole 함수를 사용하고 roleName 또는 roleId 매개변수와 함께 organizationId 매개변수를 제공할 수 있습니다.

POST
/organization/delete-role
await authClient.organization.deleteRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",});
PropDescriptionType
roleName?
삭제할 역할의 이름입니다. 또는 대신 roleId 매개변수를 전달할 수 있습니다.
string
roleId?
삭제할 역할의 ID입니다. 또는 대신 roleName 매개변수를 전달할 수 있습니다.
string
organizationId?
역할이 삭제될 조직 ID입니다. 기본값은 활성 조직입니다.
string

역할 목록 보기

역할을 나열하려면 listOrgRoles 함수를 사용할 수 있습니다. 멤버가 역할을 나열하려면 ac 리소스에 read 권한이 필요합니다.

GET
/organization/list-roles
const { data: roles, error } = await authClient.organization.listRoles({    organizationId: "organization-id",});
PropDescriptionType
organizationId?
나열할 역할이 속한 조직 ID입니다. 기본값은 사용자의 활성 조직입니다.
string

특정 역할 가져오기

특정 역할을 가져오려면 getOrgRole 함수를 사용하고 roleName 또는 roleId 매개변수를 전달할 수 있습니다. 멤버가 역할을 가져오려면 ac 리소스에 read 권한이 필요합니다.

GET
/organization/get-role
const { data: role, error } = await authClient.organization.getRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",});
PropDescriptionType
roleName?
가져올 역할의 이름입니다. 또는 대신 roleId 매개변수를 전달할 수 있습니다.
string
roleId?
가져올 역할의 ID입니다. 또는 대신 roleName 매개변수를 전달할 수 있습니다.
string
organizationId?
역할이 삭제될 조직 ID입니다. 기본값은 활성 조직입니다.
string

역할 업데이트하기

역할을 업데이트하려면 updateOrgRole 함수를 사용하고 roleName 또는 roleId 매개변수를 전달할 수 있습니다.

POST
/organization/update-role
const { data: updatedRole, error } = await authClient.organization.updateRole({    roleName: "my-role",    roleId: "role-id",    organizationId: "organization-id",    data: { // required        permission: { project: ["create", "update", "delete"] },        roleName: "my-new-role",    },});
PropDescriptionType
roleName?
업데이트할 역할의 이름입니다. 또는 대신 roleId 매개변수를 전달할 수 있습니다.
string
roleId?
업데이트할 역할의 ID입니다. 또는 대신 roleName 매개변수를 전달할 수 있습니다.
string
organizationId?
역할이 업데이트될 조직 ID입니다. 기본값은 활성 조직입니다.
string
data
업데이트될 데이터입니다
Object
data.permission?
선택적으로 역할의 권한을 업데이트합니다.
Record<string, string[]>
data.roleName?
선택적으로 역할의 이름을 업데이트합니다.
string

구성 옵션

다음은 dynamicAccessControl 객체에 전달할 수 있는 옵션 목록입니다.

enabled

이 옵션은 동적 접근 제어를 활성화하거나 비활성화하는 데 사용됩니다. 기본적으로 비활성화되어 있습니다.

organization({
  dynamicAccessControl: {
    enabled: true
  }
})

maximumRolesPerOrganization

이 옵션은 조직에 대해 생성할 수 있는 역할 수를 제한하는 데 사용됩니다.

기본적으로 조직에 대해 생성할 수 있는 최대 역할 수는 무제한입니다.

organization({
  dynamicAccessControl: {
    maximumRolesPerOrganization: 10
  }
})

숫자를 반환하는 함수를 전달할 수도 있습니다.

organization({
  dynamicAccessControl: {
    maximumRolesPerOrganization: async (organizationId) => { 
      const organization = await getOrganization(organizationId); 
      return organization.plan === "pro" ? 100 : 10; 
    } 
  }
})

추가 필드

organizationRole 테이블에 추가 필드를 추가하려면 organization 플러그인에 additionalFields 구성 옵션을 전달할 수 있습니다.

organization({
  schema: {
    organizationRole: {
      additionalFields: {
        // 역할 색상!
        color: {
          type: "string",
          defaultValue: "#ffffff",
        },
        //... 기타 필드
      },
    },
  },
})

그런 다음 추가 필드를 추론하기 위해 inferOrgAdditionalFields를 아직 사용하지 않는 경우 이를 사용하여 추가 필드를 추론할 수 있습니다.

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient, inferOrgAdditionalFields } from "better-auth/client/plugins"
import type { auth } from "./auth"

export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            schema: inferOrgAdditionalFields<typeof auth>()
        })
    ]
})

그렇지 않으면 서버의 org 플러그인에서와 동일한 방식으로 스키마 값을 직접 전달할 수 있습니다.

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

export const authClient = createAuthClient({
    plugins: [
        organizationClient({
            schema: {
                organizationRole: {
                    additionalFields: {
                        color: {
                            type: "string",
                            defaultValue: "#ffffff",
                        }
                    }
                }
            }
        })
    ]
})

팀을 사용하면 조직 내에서 멤버를 그룹화할 수 있습니다. 팀 기능은 추가적인 조직 구조를 제공하며 보다 세밀한 수준에서 권한을 관리하는 데 사용할 수 있습니다.

팀 활성화하기

팀을 활성화하려면 서버 및 클라이언트 플러그인 모두에 teams 구성 옵션을 전달하세요:

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

export const auth = betterAuth({
  plugins: [
    organization({
      teams: {
        enabled: true,
        maximumTeams: 10, // 선택 사항: 조직당 팀 제한
        allowRemovingAllTeams: false, // 선택 사항: 마지막 팀 제거 방지
      },
    }),
  ],
});
auth-client.ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";

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

팀 관리하기

팀 생성하기

조직 내에서 새 팀을 만듭니다:

POST
/organization/create-team
const { data, error } = await authClient.organization.createTeam({    name: "my-team", // required    organizationId: "organization-id",});
PropDescriptionType
name
팀의 이름입니다.
string
organizationId?
팀이 생성될 조직 ID입니다. 기본값은 활성 조직입니다.
string

팀 목록 보기

조직의 모든 팀을 가져옵니다:

GET
/organization/list-teams
const { data, error } = await authClient.organization.listTeams({    query: {        organizationId: "organization-id",    },});
PropDescriptionType
query?
팀 목록을 필터링하거나 범위를 지정하기 위한 쿼리 매개변수입니다.
Object
query.organizationId?
나열할 팀이 속한 조직 ID입니다. 기본값은 사용자의 활성 조직입니다.
string

팀 업데이트하기

팀의 세부 정보를 업데이트합니다:

POST
/organization/update-team
const { data, error } = await authClient.organization.updateTeam({    teamId: "team-id", // required    data: { // required        name: "My new team name",        organizationId: "My new organization ID for this team",        createdAt: new Date(),        updatedAt: new Date(),    },});
PropDescriptionType
teamId
업데이트할 팀의 ID입니다.
string
data
업데이트할 옵션이 포함된 부분 객체입니다.
Object
data.name?
업데이트할 팀의 이름입니다.
string
data.organizationId?
팀이 속한 조직 ID입니다.
string
data.createdAt?
팀이 생성된 타임스탬프입니다.
Date
data.updatedAt?
팀이 마지막으로 업데이트된 타임스탬프입니다.
Date

팀 제거하기

조직에서 팀을 삭제합니다:

POST
/organization/remove-team
const { data, error } = await authClient.organization.removeTeam({    teamId: "team-id", // required    organizationId: "organization-id",});
PropDescriptionType
teamId
제거할 팀의 팀 ID입니다.
string
organizationId?
팀이 속한 조직 ID입니다. 제공되지 않으면 사용자의 활성 조직으로 기본 설정됩니다.
string

활성 팀 설정하기

주어진 팀을 현재 활성 팀으로 설정합니다. teamIdnull이면 현재 활성 팀이 해제됩니다.

POST
/organization/set-active-team
const { data, error } = await authClient.organization.setActiveTeam({    teamId: "team-id",});
PropDescriptionType
teamId?
현재 활성 팀으로 설정할 팀 ID입니다.
string

사용자 팀 목록 보기

현재 사용자가 속한 모든 팀을 나열합니다.

GET
/organization/list-user-teams
const { data, error } = await authClient.organization.listUserTeams();

팀 멤버 목록 보기

주어진 팀의 멤버를 나열합니다.

POST
/organization/list-team-members
const { data, error } = await authClient.organization.listTeamMembers({    teamId: "team-id",});
PropDescriptionType
teamId?
멤버를 반환할 팀입니다. 제공되지 않으면 현재 활성 팀의 멤버가 반환됩니다.
string

팀 멤버 추가하기

팀에 멤버를 추가합니다.

POST
/organization/add-team-member
const { data, error } = await authClient.organization.addTeamMember({    teamId: "team-id", // required    userId: "user-id", // required});
PropDescriptionType
teamId
사용자가 멤버가 되어야 하는 팀입니다.
string
userId
멤버로 추가될 사용자를 나타내는 사용자 ID입니다.
string

팀 멤버 제거하기

팀에서 멤버를 제거합니다.

POST
/organization/remove-team-member
const { data, error } = await authClient.organization.removeTeamMember({    teamId: "team-id", // required    userId: "user-id", // required});
PropDescriptionType
teamId
사용자가 제거되어야 하는 팀입니다.
string
userId
팀에서 제거되어야 하는 사용자입니다.
string

팀 권한

팀은 조직의 권한 시스템을 따릅니다. 팀을 관리하려면 사용자에게 다음 권한이 필요합니다:

  • team:create - 새 팀 생성
  • team:update - 팀 세부 정보 업데이트
  • team:delete - 팀 제거

기본적으로:

  • 조직 소유자와 관리자는 팀을 관리할 수 있습니다
  • 일반 멤버는 팀을 생성, 업데이트 또는 삭제할 수 없습니다

팀 구성 옵션

팀 기능은 여러 구성 옵션을 지원합니다:

  • maximumTeams: 조직당 팀 수 제한

    teams: {
      enabled: true,
      maximumTeams: 10 // 고정 숫자
      // 또는
      maximumTeams: async ({ organizationId, session }, request) => {
        // 조직 플랜에 따른 동적 제한
        const plan = await getPlan(organizationId)
        return plan === 'pro' ? 20 : 5
      },
      maximumMembersPerTeam: 10 // 고정 숫자
      // 또는
      maximumMembersPerTeam: async ({ teamId, session, organizationId }, request) => {
        // 팀 플랜에 따른 동적 제한
        const plan = await getPlan(organizationId, teamId)
        return plan === 'pro' ? 50 : 10
      },
    }
  • allowRemovingAllTeams: 마지막 팀을 제거할 수 있는지 제어

    teams: {
      enabled: true,
      allowRemovingAllTeams: false // 마지막 팀 제거 방지
    }

팀 멤버

조직에 멤버를 초대할 때 팀을 지정할 수 있습니다:

await authClient.organization.inviteMember({
  email: "user@example.com",
  role: "member",
  teamId: "team-id",
});

초대된 멤버는 초대를 수락하면 지정된 팀에 추가됩니다.

데이터베이스 스키마

팀이 활성화되면 새로운 teamteamMember 테이블이 데이터베이스에 추가됩니다.

테이블 이름: team

Field NameTypeKeyDescription
idstring각 팀의 고유 식별자
namestring-팀의 이름
organizationIdstring조직의 ID
createdAtDate-팀이 생성된 타임스탬프
updatedAtDate팀이 생성된 타임스탬프

테이블 이름: teamMember

Field NameTypeKeyDescription
idstring각 팀 멤버의 고유 식별자
teamIdstring각 팀의 고유 식별자
userIdstring사용자의 ID
createdAtDate-팀 멤버가 생성된 타임스탬프

스키마

organization 플러그인은 데이터베이스에 다음 테이블을 추가합니다:

Organization

테이블 이름: organization

Field NameTypeKeyDescription
idstring각 조직의 고유 식별자
namestring-조직의 이름
slugstring-조직의 슬러그
logostring조직의 로고
metadatastring조직의 추가 메타데이터
createdAtDate-조직이 생성된 타임스탬프

Member

테이블 이름: member

Field NameTypeKeyDescription
idstring각 멤버의 고유 식별자
userIdstring사용자의 ID
organizationIdstring조직의 ID
rolestring-조직에서 사용자의 역할
createdAtDate-멤버가 조직에 추가된 타임스탬프

Invitation

테이블 이름: invitation

Field NameTypeKeyDescription
idstring각 초대의 고유 식별자
emailstring-사용자의 이메일 주소
inviterIdstring초대자의 ID
organizationIdstring조직의 ID
rolestring-조직에서 사용자의 역할
statusstring-초대의 상태
createdAtDate-초대가 생성된 타임스탬프
expiresAtDate-초대가 만료되는 타임스탬프

팀이 활성화된 경우 invitation 테이블에 다음 필드를 추가해야 합니다:

Field NameTypeKeyDescription
teamIdstring팀의 ID

Session

테이블 이름: session

활성 조직 ID와 활성 팀 ID를 저장하기 위해 세션 테이블에 두 개의 필드를 더 추가해야 합니다.

Field NameTypeKeyDescription
activeOrganizationIdstring활성 조직의 ID
activeTeamIdstring활성 팀의 ID

Teams (선택 사항)

테이블 이름: team

Field NameTypeKeyDescription
idstring각 팀의 고유 식별자
namestring-팀의 이름
organizationIdstring조직의 ID
createdAtDate-팀이 생성된 타임스탬프
updatedAtDate팀이 생성된 타임스탬프

테이블 이름: teamMember

Field NameTypeKeyDescription
idstring각 팀 멤버의 고유 식별자
teamIdstring각 팀의 고유 식별자
userIdstring사용자의 ID
createdAtDate-팀 멤버가 생성된 타임스탬프

테이블 이름: invitation

Field NameTypeKeyDescription
teamIdstring팀의 ID

스키마 커스터마이징하기

스키마 테이블 이름이나 필드를 변경하려면 organization 플러그인에 schema 옵션을 전달할 수 있습니다.

auth.ts
const auth = betterAuth({
  plugins: [
    organization({
      schema: {
        organization: {
          modelName: "organizations", //organization 테이블을 organizations로 매핑
          fields: {
            name: "title", //name 필드를 title로 매핑
          },
          additionalFields: {
            // organization 테이블에 새 필드 추가
            myCustomField: {
              type: "string",
              input: true,
              required: false,
            },
          },
        },
      },
    }),
  ],
});

추가 필드

Better Auth v1.3부터 organization, invitation, member, team 테이블에 사용자 정의 필드를 쉽게 추가할 수 있습니다.

모델에 추가 필드를 추가하면 관련 API 엔드포인트가 자동으로 이러한 새 속성을 수락하고 반환합니다. 예를 들어 organization 테이블에 사용자 정의 필드를 추가하면 createOrganization 엔드포인트는 필요에 따라 요청 및 응답 페이로드에 이 필드를 포함합니다.

auth.ts
const auth = betterAuth({
  plugins: [
    organization({
      schema: {
        organization: {
          additionalFields: {
            myCustomField: {
              type: "string", 
              input: true, 
              required: false, 
            }, 
          },
        },
      },
    }),
  ],
});

추가 필드를 추론하려면 inferOrgAdditionalFields 함수를 사용할 수 있습니다. 이 함수는 auth 객체 타입에서 추가 필드를 추론합니다.

auth-client.ts
import { createAuthClient } from "better-auth/client";
import {
  inferOrgAdditionalFields,
  organizationClient,
} from "better-auth/client/plugins";
import type { auth } from "@/auth"; // auth 객체 타입만 가져오기

const client = createAuthClient({
  plugins: [
    organizationClient({
      schema: inferOrgAdditionalFields<typeof auth>(),
    }),
  ],
});

auth 객체 타입을 가져올 수 없는 경우 제네릭 없이 inferOrgAdditionalFields 함수를 사용할 수 있습니다. 이 함수는 스키마 객체에서 추가 필드를 추론합니다.

auth-client.ts
const client = createAuthClient({
  plugins: [
    organizationClient({
      schema: inferOrgAdditionalFields({
        organization: {
          additionalFields: {
            newField: {
              type: "string", 
            }, 
          },
        },
      }),
    }),
  ],
});

//사용 예제
await client.organization.create({
  name: "Test",
  slug: "test",
  newField: "123", //허용되어야 합니다
  //@ts-expect-error - 이 필드는 사용할 수 없습니다
  unavalibleField: "123", //허용되지 않아야 합니다
});

옵션

allowUserToCreateOrganization: boolean | ((user: User) => Promise<boolean> | boolean) - 사용자가 조직을 생성할 수 있는지 여부를 결정하는 함수입니다. 기본값은 true입니다. 사용자가 조직을 생성하지 못하도록 제한하려면 false로 설정할 수 있습니다.

organizationLimit: number | ((user: User) => Promise<boolean> | boolean) - 사용자에게 허용되는 최대 조직 수입니다. 기본값은 5입니다. 원하는 숫자나 부울을 반환하는 함수로 설정할 수 있습니다.

creatorRole: admin | owner - 조직을 만든 사용자의 역할입니다. 기본값은 owner입니다. admin으로 설정할 수 있습니다.

membershipLimit: number - 조직에 허용되는 최대 멤버 수입니다. 기본값은 100입니다. 원하는 숫자로 설정할 수 있습니다.

sendInvitationEmail: async (data) => Promise<void> - 사용자에게 초대 이메일을 보내는 함수입니다.

invitationExpiresIn : number - 초대 링크가 유효한 시간(초)입니다. 기본값은 48시간(2일)입니다.

cancelPendingInvitationsOnReInvite: boolean - 사용자가 이미 조직에 초대된 경우 대기 중인 초대를 취소할지 여부입니다. 기본값은 false입니다.

invitationLimit: number | ((user: User) => Promise<boolean> | boolean) - 사용자에게 허용되는 최대 초대 수입니다. 기본값은 100입니다. 원하는 숫자나 부울을 반환하는 함수로 설정할 수 있습니다.

requireEmailVerificationOnInvitation: boolean - 초대를 수락하거나 거부하기 전에 이메일 확인을 요구할지 여부입니다. 기본값은 false입니다. 활성화하면 사용자가 조직 초대를 수락하거나 거부하기 전에 이메일 주소를 확인해야 합니다.

On this page

설치
auth 설정에 플러그인 추가하기
데이터베이스 마이그레이션
클라이언트 플러그인 추가하기
사용법
Organization
조직 생성하기
조직 생성 권한 제한하기
조직 슬러그 사용 여부 확인하기
Organization Hooks
조직 생성 및 관리 훅
멤버 훅
초대 훅
팀 훅
훅 오류 처리
사용자의 조직 목록 보기
활성 조직
활성 조직 설정하기
활성 조직 사용하기
전체 조직 정보 가져오기
조직 업데이트하기
조직 삭제하기
초대
초대 이메일 설정하기
초대 보내기
초대 수락하기
이메일 확인 요구 사항
초대 수락 콜백
초대 취소하기
초대 거부하기
초대 가져오기
초대 목록 보기
사용자 초대 목록 보기
멤버
멤버 목록 보기
멤버 제거하기
멤버 역할 업데이트하기
활성 멤버 가져오기
활성 멤버 역할 가져오기
멤버 추가하기
조직 탈퇴하기
접근 제어
역할
권한
사용자 정의 권한
접근 제어 생성하기
역할 생성하기
플러그인에 역할 전달하기
접근 제어 사용법
동적 접근 제어
동적 접근 제어 활성화하기
역할 생성하기
역할 삭제하기
역할 목록 보기
특정 역할 가져오기
역할 업데이트하기
구성 옵션
enabled
maximumRolesPerOrganization
추가 필드
팀 활성화하기
팀 관리하기
팀 생성하기
팀 목록 보기
팀 업데이트하기
팀 제거하기
활성 팀 설정하기
사용자 팀 목록 보기
팀 멤버 목록 보기
팀 멤버 추가하기
팀 멤버 제거하기
팀 권한
팀 구성 옵션
팀 멤버
데이터베이스 스키마
스키마
Organization
Member
Invitation
Session
Teams (선택 사항)
스키마 커스터마이징하기
추가 필드
옵션