Organizations
Learn how to manage organizations and their members
Organizations is a premium feature. Please get in touch if you would like us to enable it for your account. You can contact us at support@clerk.dev.
Demo Repo
A good way to explore Clerk's organizations functionality is to check out this demo repo:
Overview
Organizations are shared accounts, useful for project and team leaders. Organization members can usually collaborate across shared resources. There are members with elevated privileges who can manage member access to the organization's data and resources.
Each member of an organization needs to have a user account in your application. All organization members have access to most of the organization resources, but some members can take advantage of administrative features.
Roles
This distinction in member permissions is possible with organization member roles. Roles determine a user's level of access to the organization. There are currently two roles; administrators and members.
- admin - The "admin" role offers full access to organization resources. Members with the admin role have administrator privileges and can fully manage organizations and organization memberships.
- basic_member - The "basic_member" role is the standard role for a user that is part of the organization. Access to organization resources is limited. Basic members cannot manage organizations and organization memberships, but can view information about the organization and other members in it.
Available actions
You can use Clerk's organizations feature to provide team grouping and sharing functionality for users of your applications.
Your users can create organizations. Organization owners effectively get the "admin" role.
Administrators can then invite other users to join the organization. An invitation email is sent out, and organization invitations support adding existing users of your application, or new ones. They can register once they accept the invitation.
Administrators can also revoke an invitation for a user that hasn't joined yet, as well as remove a user who's already a member from the organization or change their role. When removing organization members or updating their role, there needs to be at least one administrator for the organization at all times.
Administrators can also update an organization, in order to change the organization name for example.
Finally, all members of an organization, regardless of their role can view information about other members in the same organization.
Before you start
- You need to create a Clerk Application in your Clerk Dashboard. For more information, check out our Setup your application guide.
- You need to install Clerk React or ClerkJS to your application.
- The organizations feature needs to be enabled for your account. Contact us at support@clerk.dev to turn the feature on.
Custom flow
Let's follow a simple scenario for setting up an organization and managing its members.
In the following guide, we'll create a new organization, update it and then invite a couple of members. We'll see how to revoke one of the two invitations, and update the other member's role. Finally, we'll remove the other member from the organization.
This tutorial assumes that a signed in user already exists. All snippets below require a session and user to be present.
Create a new organization
1// Form to create a new organization. The current user2// will become the organization administrator.3import { useOrganizationList } from "@clerk/nextjs";4import { FormEventHandler, useState } from "react";56export default function CreateOrganization() {7const { createOrganization } = useOrganizationList();8const [organizationName, setOrganizationName] = useState("");910const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {11e.preventDefault();12createOrganization({ name: organizationName });13setOrganizationName("");14};1516return (17<form onSubmit={handleSubmit}>18<input19type="text"20name="organizationName"21value={organizationName}22onChange={(e) => setOrganizationName(e.currentTarget.value)}23/>24<button type="submit">Create organization</button>25</form>26);27}28
1// Form to create a new organization. The current user2// will become the organization administrator.3import { useOrganizationList } from "@clerk/clerk-react";4import { FormEventHandler, useState } from "react";56export default function CreateOrganization() {7const { createOrganization } = useOrganizationList();8const [organizationName, setOrganizationName] = useState("");910const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {11e.preventDefault();12createOrganization({ name: organizationName });13setOrganizationName("");14};1516return (17<form onSubmit={handleSubmit}>18<input19type="text"20name="organizationName"21value={organizationName}22onChange={(e) => setOrganizationName(e.currentTarget.value)}23/>24<button type="submit">Create organization</button>25</form>26);27}28
1<!-- Form to create an organization -->2<form id="new_organization">3<div>4<label>Name</label>5<br />6<input name="name" />7</div>8<button>Create organization</button>9</form>1011<script>12const form = document.getElementById("new_organization");13form.addEventListener('submit', function(e) {14e.preventDefault();15const inputEl = form.getElementsByTagName("input")[0];16if (!inputEl) {17return;18}19try {20await window.Clerk.createOrganization({ name: inputEl.value });21} catch (err) {22console.error(err);23}24});25</script>26
View all organizations the current user belongs to
1// Lists all organization the user is a member of.2// Each entry can be a link to a page to manage organization3// members.45// If you create a new Organization, this list will update automatically.6import { useOrganizationList } from "@clerk/nextjs";7import Link from "next/link";89const OrganizationList = () => {10const { organizationList, isLoaded } = useOrganizationList();1112if (!isLoaded) {13// Any loading state14return null;15}1617return (18<div>19<h2>Your organizations</h2>20{organizationList.length === 0 ? (21<div>You do not belong to any organizations yet.</div>22) : (23<ul>24{organizationList.map(({ organization }) => (25<li key={organization.id}>26<Link27href={`/organizations/${organization.id}`}28>29<a>{organization.name}</a>30</Link>31</li>32))}33</ul>34)}35</div>36);37};
1// Lists all organization the user is a member of.2// Each entry can be a link to a page to manage organization3// members.45// If you create a new Organization, this list will update automatically.6import { useOrganizationList } from "@clerk/clerk-react";78const OrganizationList = () => {9const { organizationList, isLoaded } = useOrganizationList();1011if (!isLoaded) {12// Any loading state13return null;14}1516return (17<div>18<h2>Your organizations</h2>19{organizationList.length === 0 ? (20<div>You do not belong to any organizations yet.</div>21) : (22<ul>23{organizationList.map(({ organization }) => (24<li key={organization.id}>25<a href={`/organizations/${organization.id}`}>{organization.name}</a>26</li>27))}28</ul>29)}30</div>31);32};
1<ul id="organizations_list"></ul>23<script>4const list = document.getElementById("organizations_list");5try {6const organizationMemberships = await window.Clerk.getOrganizationMemberships();7organizationMemberships.map((membership) => {8const li = document.createElement("li");9li.textContent = `${membership.organization.name} - ${membership.role}`;10list.appendChild(li);11});12} catch (err) {13console.error(err);14}15</script>16
Update the organization name
1// pages/organizations/[id]/edit.js2import { useState, useEffect } from "react";3import { useRouter } from "next/router";4import { useOrganizations } from "@clerk/nextjs";56export default function EditOrganization() {7const [organization, setOrganization] = useState(null);8const [name, setName] = useState("");910const { query } = useRouter();11const organizationId = query.id;1213const { getOrganization } = useOrganizations();1415useEffect(() => {16async function fetchOrganization() {17try {18const org = await getOrganization(organizationId);19setOrganization(org);20} catch (err) {21console.log(err);22}23}2425fetchOrganization();26}, [organizationId, getOrganization]);2728useEffect(() => {29if (!organization) {30return;31}32setName(organization.name);33}, [organization]);3435async function submit(e) {36e.preventDefault();37try {38await organization.update({ name });39router.push(`/organizations/${organization.id}`);40} catch (err) {41console.error(err);42}43}4445if (!organization) {46return null;47}4849return (50<div>51<h2>Edit organization</h2>52<form onSubmit={submit}>53<div>54<label>Name</label>55<br />56<input57name="name"58value={name}59onChange={(e) => setName(e.target.value)}60/>61</div>62<button>Save</button>63</form>64</div>65);66}
1import { useState, useEffect } from "react";2import { useOrganizations } from "@clerk/react";34export default function EditOrganization({ organizationId }) {5const [organization, setOrganization] = useState(null);6const [name, setName] = useState("");78const { getOrganization } = useOrganizations();910useEffect(() => {11async function fetchOrganization() {12try {13const org = await getOrganization(organizationId);14setOrganization(org);15} catch (err) {16console.log(err);17}18}1920fetchOrganization();21}, [organizationId, getOrganization]);2223useEffect(() => {24if (!organization) {25return;26}27setName(organization.name);28}, [organization]);2930async function submit(e) {31e.preventDefault();32try {33await organization.update({ name });34router.push(`/organizations/${organization.id}`);35} catch (err) {36console.error(err);37}38}3940if (!organization) {41return null;42}4344return (45<div>46<h2>Edit organization</h2>47<form onSubmit={submit}>48<div>49<label>Name</label>50<br />51<input52name="name"53value={name}54onChange={(e) => setName(e.target.value)}55/>56</div>57<button>Save</button>58</form>59</div>60);61}
1<form id="edit_organization">2<div>3<label>Name</label>4<br />5<input name="name" />6</div>7<button>Save</button>8</form>910<script>11async function init() {12// This is the current organization ID.13const organizationId = "org_XXXXXXX";14const organizationMemberships = await window.Clerk.getOrganizationMemberships()15const currentMembership = organizationMemberships.find(membership16=> membership.organization.id === organizationId);17const currentOrganization = currentMembership.organization;1819if (!currentOrganization) {20return;21}2223editOrganization(currentOrganization);24}2526async function editOrganization(organization) {27const form = document.getElementById("edit_organization");28const inputEl = form.getElementsByTagName("input")[0];29if (inputEl) {30inputEl.value = organization.name;31}3233form.addEventListener('submit', function(e) {34e.preventDefault();35const inputEl = form.getElementsByTagName("input")[0];36if (!inputEl) {37return;38}39try {40await organization.update({ name: inputEl.value });41} catch (err) {42console.error(err);43}44});45}4647init();48</script>
Manage organization members
1// pages/organizations/[id].ts2import { useState, useEffect } from "react";3import { useOrganization } from "@clerk/nextjs";4import type { OrganizationMembershipResource } from "@clerk/types";56// View and manage active organization members, along with any7// pending invitations.8// Invite new members.9export default function Organization() {10const {11organization: currentOrganization,12membership,13isLoaded,14} = useOrganization();1516if (!isLoaded || !currentOrganization) {17return null;18}1920const isAdmin = membership.role === "admin";21return (22<>23<h1>Organization: {currentOrganization.name}</h1>24<MemberList />25{isAdmin && <InvitationList />}26</>27);28}2930// List of organization memberships. Administrators can31// change member roles or remove members from the organization.32function MemberList() {33const { membershipList, membership } = useOrganization({34membershipList: {},35});3637if (!membershipList) {38return null;39}4041const isCurrentUserAdmin = membership.role === "admin";4243return (44<div>45<h2>Organization members</h2>46<ul>47{membershipList.map((m) => (48<li key={m.id}>49{m.publicUserData.firstName} {m.publicUserData.lastName} <50{m.publicUserData.identifier}> :: {m.role}51{isCurrentUserAdmin && <AdminControls membership={m} />}52</li>53))}54</ul>55</div>56);57}5859function AdminControls({60membership,61}: {62membership: OrganizationMembershipResource;63}){64const [disabled, setDisabled] = useState(false);65const {66user: { id: userId },67} = useUser();6869if (membership.publicUserData.userId === userId) {70return null;71}7273const remove = async () => {74setDisabled(true);75await membership.destroy();76};7778const changeRole = async (role: MembershipRole) => {79setDisabled(true);80await membership.update({ role });81setDisabled(false);82};8384return (85<>86::{" "}87<button disabled={disabled} onClick={remove}>88Remove member89</button>{" "}90{membership.role === "admin" ? (91<button disabled={disabled} onClick={() => changeRole("basic_member")}>92Change to member93</button>94) : (95<button disabled={disabled} onClick={() => changeRole("admin")}>96Change to admin97</button>98)}99</>100);101};102103// List of organization pending invitations.104// You can invite new organization members and105// revoke already sent invitations.106function InvitationList() {107const { invitationList } = useOrganization();108109if (!invitationList) {110return null;111}112113const revoke = async (inv) => {114await inv.revoke();115};116117return (118<div>119<h2>Invite member</h2>120<InviteMember />121122<h2>Pending invitations</h2>123<ul>124{invitationList.map((i) => (125<li key={i.id}>126{i.emailAddress} <button onClick={() => revoke(i)}>Revoke</button>127</li>128))}129</ul>130</div>131);132}133134function InviteMember(){135const { organization } = useOrganization();136const [emailAddress, setEmailAddress] = useState("");137const [role, setRole] = useState<"basic_member" | "admin">("basic_member");138const [disabled, setDisabled] = useState(false);139140const onSubmit = async (e) => {141e.preventDefault();142setDisabled(true);143await organization.inviteMember({ emailAddress, role });144setEmailAddress("");145setRole("basic_member");146setDisabled(false);147};148149return (150<form onSubmit={onSubmit}>151<input152type="text"153placeholder="Email address"154value={emailAddress}155onChange={(e) => setEmailAddress(e.target.value)}156/>157<label>158<input159type="radio"160checked={role === "admin"}161onChange={() => {162setRole("admin");163}}164/>{" "}165Admin166</label>167<label>168<input169type="radio"170checked={role === "basic_member"}171onChange={() => {172setRole("basic_member");173}}174/>{" "}175Member176</label>{" "}177<button type="submit" disabled={disabled}>178Invite179</button>180</form>181);182};183
1import { useState, useEffect } from "react";2import { useOrganization } from "@clerk/clerk-react";3import type { OrganizationMembershipResource } from "@clerk/types";45// View and manage active organization members, along with any6// pending invitations.7// Invite new members.8export default function Organization() {9const {10organization: currentOrganization,11membership,12isLoaded,13} = useOrganization();1415if (!isLoaded || !currentOrganization) {16return null;17}1819const isAdmin = membership.role === "admin";20return (21<>22<h1>Organization: {currentOrganization.name}</h1>23<MemberList />24{isAdmin && <InvitationList />}25</>26);27}2829// List of organization memberships. Administrators can30// change member roles or remove members from the organization.31function MemberList() {32const { membershipList, membership } = useOrganization({33membershipList: {},34});3536if (!membershipList) {37return null;38}3940const isCurrentUserAdmin = membership.role === "admin";4142return (43<div>44<h2>Organization members</h2>45<ul>46{membershipList.map((m) => (47<li key={m.id}>48{m.publicUserData.firstName} {m.publicUserData.lastName} <49{m.publicUserData.identifier}> :: {m.role}50{isCurrentUserAdmin && <AdminControls membership={m} />}51</li>52))}53</ul>54</div>55);56}5758function AdminControls({59membership,60}: {61membership: OrganizationMembershipResource;62}){63const [disabled, setDisabled] = useState(false);64const {65user: { id: userId },66} = useUser();6768if (membership.publicUserData.userId === userId) {69return null;70}7172const remove = async () => {73setDisabled(true);74await membership.destroy();75};7677const changeRole = async (role: MembershipRole) => {78setDisabled(true);79await membership.update({ role });80setDisabled(false);81};8283return (84<>85::{" "}86<button disabled={disabled} onClick={remove}>87Remove member88</button>{" "}89{membership.role === "admin" ? (90<button disabled={disabled} onClick={() => changeRole("basic_member")}>91Change to member92</button>93) : (94<button disabled={disabled} onClick={() => changeRole("admin")}>95Change to admin96</button>97)}98</>99);100};101102// List of organization pending invitations.103// You can invite new organization members and104// revoke already sent invitations.105function InvitationList() {106const { invitationList } = useOrganization();107108if (!invitationList) {109return null;110}111112const revoke = async (inv) => {113await inv.revoke();114};115116return (117<div>118<h2>Invite member</h2>119<InviteMember />120121<h2>Pending invitations</h2>122<ul>123{invitationList.map((i) => (124<li key={i.id}>125{i.emailAddress} <button onClick={() => revoke(i)}>Revoke</button>126</li>127))}128</ul>129</div>130);131}132133function InviteMember(){134const { organization } = useOrganization();135const [emailAddress, setEmailAddress] = useState("");136const [role, setRole] = useState<"basic_member" | "admin">("basic_member");137const [disabled, setDisabled] = useState(false);138139const onSubmit = async (e) => {140e.preventDefault();141setDisabled(true);142await organization.inviteMember({ emailAddress, role });143setEmailAddress("");144setRole("basic_member");145setDisabled(false);146};147148return (149<form onSubmit={onSubmit}>150<input151type="text"152placeholder="Email address"153value={emailAddress}154onChange={(e) => setEmailAddress(e.target.value)}155/>156<label>157<input158type="radio"159checked={role === "admin"}160onChange={() => {161setRole("admin");162}}163/>{" "}164Admin165</label>166<label>167<input168type="radio"169checked={role === "basic_member"}170onChange={() => {171setRole("basic_member");172}}173/>{" "}174Member175</label>{" "}176<button type="submit" disabled={disabled}>177Invite178</button>179</form>180);181};182
1<ul id="memberships_list"></ul>2<ul id="invitations_list"></ul>34<form id="new_invitation">5<div>6<label>Email address</div>7<br />8<input type="email" name="email_address" />9</div>10<button>Invite</button>11</form>1213<script>14async function renderMemberships(organization, isAdmin) {15const list = document.getElementById("memberships_list");16try {17const memberships = await organization.getMembers();18memberships.map((membership) => {19const li = document.createElement("li");20li.textContent = `${membership.identifier} - ${membership.role}`;2122// Add administrative actions; update role and remove member.23if (isAdmin) {24const updateBtn = document.createElement("button");25updateBtn.textContent = "Change role";26updateBtn.addEventListener("click", async function(e) {27e.preventDefault();28const role = membership.role === "admin" ?29"basic_member" :30"admin";31await membership.update({ role });32});33li.appendChild(updateBtn);3435const removeBtn = document.createElement("button");36removeBtn.textContent = "Remove";37removeBtn.addEventListener("click", async function(e) {38e.preventDefault();39await currentOrganization.removeMember(membership.userId);40});41li.appendChild(removeBtn);42}4344// Add the entry to the list45list.appendChild(li);46});47} catch (err) {48console.error(err);49}50}5152async function renderInvitations(organization, isAdmin) {53const list = document.getElementById("invitations_list");54try {55const invitations = await organization.getPendingInvitations();56invitations.map((invitation) => {57const li = document.createElement("li");58li.textContent = `${invitation.emailAddress} - ${invitation.role}`;5960// Add administrative actions; revoke invitation61if (isAdmin) {62const revokeBtn = document.createElement("button");63revokeBtn.textContent = "Revoke";64revokeBtn.addEventListener("click", async function(e) {65e.preventDefault();66await invitation.revoke();67});68li.appendChild(revokeBtn);69}70// Add the entry to the list71list.appendChild(li);72});73} catch (err) {74console.error(err);75}76}7778async function init() {79// This is the current organization ID.80const organizationId = "org_XXXXXXX";81const organizationMemberships = await window.Clerk.getOrganizationMemberships()82const currentMembership = organizationMemberships.find(membership83=> membership.organization.id === organizationId);84const currentOrganization = currentMembership.organization;8586if (!currentOrganization) {87return;88}89const isAdmin = currentMembership.role === "admin";9091renderMemberships(currentOrganization, isAdmin);92renderInvitations(currentOrganization, isAdmin);9394if (isAdmin) {95const form = document.getElementById("new_invitation");96form.addEventListener("submit", async function(e) {97e.preventDefault();98const inputEl = form.getElementsByTagName("input");99if (!inputEl) {100return;101}102103try {104await currentOrganization.inviteMember({105emailAddress: inputEl.value,106role: "basic_member",107});108} catch (err) {109console.error(err);110}111});112}113}114115init();116</script>117
Determining the active organization from the backend
Having enabled the Organizations feature, session tokens generated by Clerk contain the additional claims org_id
, org_role
and orgs
which can be used on the server or edge side of your application.
These claims area accessible through the req.auth.claims
path, where all the Clerk token claims reside.
/** Next.js API route. */import { requireAuth } from "@clerk/nextjs/api";/* Return the user's active organization id and role. */export default requireAuth((req, res) => {const { org_id, org_role } = req.auth.claims;res.json({orgId: org_id,role: org_role,});});
In the same manner you can protect organization pages for administrators securely on the server-side and also fetch any kind of data needed for server-side rendering.
import { withServerSideAuth } from "@clerk/nextjs/ssr";import { organizations } from "@clerk/nextjs/api";const OrganizationAdmin = ({organizationName,}: {organizationName: string;}) => {return <p>Admin page for: {organizationName}</p>;};export const getServerSideProps = withServerSideAuth(async ({ req, resolvedUrl, query }) => {const { sessionId, claims } = req.auth;/** If the user is not signed in, redirect him to the sign in page. */if (!sessionId) {return {redirect: { destination: "/sign-in?redirect_url=" + resolvedUrl },props: {},};}const isCurrentOrganizationAdmin =claims.org_id === query.orgId && claims.org_role === "admin";/** If the user's current organization role is not an admin, redirect them home. */if (!isCurrentOrganizationAdmin) {return {redirect: { destination: "/" },props: {},};}/** Query the Node.js backend API to retrieve any organization information for the SSR step. */const organization = await organizations.getOrganization({organizationId: claims.org_id as string,});return {props: { organizationName: organization.name },};});export default OrganizationAdmin;