Clerk logo

Clerk Docs

Ctrl + K
Go to clerk.devGet API keys

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

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 user
2
// will become the organization administrator.
3
import { useOrganizationList } from "@clerk/nextjs";
4
import { FormEventHandler, useState } from "react";
5
6
export default function CreateOrganization() {
7
const { createOrganization } = useOrganizationList();
8
const [organizationName, setOrganizationName] = useState("");
9
10
const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
11
e.preventDefault();
12
createOrganization({ name: organizationName });
13
setOrganizationName("");
14
};
15
16
return (
17
<form onSubmit={handleSubmit}>
18
<input
19
type="text"
20
name="organizationName"
21
value={organizationName}
22
onChange={(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 user
2
// will become the organization administrator.
3
import { useOrganizationList } from "@clerk/clerk-react";
4
import { FormEventHandler, useState } from "react";
5
6
export default function CreateOrganization() {
7
const { createOrganization } = useOrganizationList();
8
const [organizationName, setOrganizationName] = useState("");
9
10
const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
11
e.preventDefault();
12
createOrganization({ name: organizationName });
13
setOrganizationName("");
14
};
15
16
return (
17
<form onSubmit={handleSubmit}>
18
<input
19
type="text"
20
name="organizationName"
21
value={organizationName}
22
onChange={(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>
10
11
<script>
12
const form = document.getElementById("new_organization");
13
form.addEventListener('submit', function(e) {
14
e.preventDefault();
15
const inputEl = form.getElementsByTagName("input")[0];
16
if (!inputEl) {
17
return;
18
}
19
try {
20
await window.Clerk.createOrganization({ name: inputEl.value });
21
} catch (err) {
22
console.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 organization
3
// members.
4
5
// If you create a new Organization, this list will update automatically.
6
import { useOrganizationList } from "@clerk/nextjs";
7
import Link from "next/link";
8
9
const OrganizationList = () => {
10
const { organizationList, isLoaded } = useOrganizationList();
11
12
if (!isLoaded) {
13
// Any loading state
14
return null;
15
}
16
17
return (
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
<Link
27
href={`/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 organization
3
// members.
4
5
// If you create a new Organization, this list will update automatically.
6
import { useOrganizationList } from "@clerk/clerk-react";
7
8
const OrganizationList = () => {
9
const { organizationList, isLoaded } = useOrganizationList();
10
11
if (!isLoaded) {
12
// Any loading state
13
return null;
14
}
15
16
return (
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>
2
3
<script>
4
const list = document.getElementById("organizations_list");
5
try {
6
const organizationMemberships = await window.Clerk.getOrganizationMemberships();
7
organizationMemberships.map((membership) => {
8
const li = document.createElement("li");
9
li.textContent = `${membership.organization.name} - ${membership.role}`;
10
list.appendChild(li);
11
});
12
} catch (err) {
13
console.error(err);
14
}
15
</script>
16

Update the organization name

1
// pages/organizations/[id]/edit.js
2
import { useState, useEffect } from "react";
3
import { useRouter } from "next/router";
4
import { useOrganizations } from "@clerk/nextjs";
5
6
export default function EditOrganization() {
7
const [organization, setOrganization] = useState(null);
8
const [name, setName] = useState("");
9
10
const { query } = useRouter();
11
const organizationId = query.id;
12
13
const { getOrganization } = useOrganizations();
14
15
useEffect(() => {
16
async function fetchOrganization() {
17
try {
18
const org = await getOrganization(organizationId);
19
setOrganization(org);
20
} catch (err) {
21
console.log(err);
22
}
23
}
24
25
fetchOrganization();
26
}, [organizationId, getOrganization]);
27
28
useEffect(() => {
29
if (!organization) {
30
return;
31
}
32
setName(organization.name);
33
}, [organization]);
34
35
async function submit(e) {
36
e.preventDefault();
37
try {
38
await organization.update({ name });
39
router.push(`/organizations/${organization.id}`);
40
} catch (err) {
41
console.error(err);
42
}
43
}
44
45
if (!organization) {
46
return null;
47
}
48
49
return (
50
<div>
51
<h2>Edit organization</h2>
52
<form onSubmit={submit}>
53
<div>
54
<label>Name</label>
55
<br />
56
<input
57
name="name"
58
value={name}
59
onChange={(e) => setName(e.target.value)}
60
/>
61
</div>
62
<button>Save</button>
63
</form>
64
</div>
65
);
66
}
1
import { useState, useEffect } from "react";
2
import { useOrganizations } from "@clerk/react";
3
4
export default function EditOrganization({ organizationId }) {
5
const [organization, setOrganization] = useState(null);
6
const [name, setName] = useState("");
7
8
const { getOrganization } = useOrganizations();
9
10
useEffect(() => {
11
async function fetchOrganization() {
12
try {
13
const org = await getOrganization(organizationId);
14
setOrganization(org);
15
} catch (err) {
16
console.log(err);
17
}
18
}
19
20
fetchOrganization();
21
}, [organizationId, getOrganization]);
22
23
useEffect(() => {
24
if (!organization) {
25
return;
26
}
27
setName(organization.name);
28
}, [organization]);
29
30
async function submit(e) {
31
e.preventDefault();
32
try {
33
await organization.update({ name });
34
router.push(`/organizations/${organization.id}`);
35
} catch (err) {
36
console.error(err);
37
}
38
}
39
40
if (!organization) {
41
return null;
42
}
43
44
return (
45
<div>
46
<h2>Edit organization</h2>
47
<form onSubmit={submit}>
48
<div>
49
<label>Name</label>
50
<br />
51
<input
52
name="name"
53
value={name}
54
onChange={(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>
9
10
<script>
11
async function init() {
12
// This is the current organization ID.
13
const organizationId = "org_XXXXXXX";
14
const organizationMemberships = await window.Clerk.getOrganizationMemberships()
15
const currentMembership = organizationMemberships.find(membership
16
=> membership.organization.id === organizationId);
17
const currentOrganization = currentMembership.organization;
18
19
if (!currentOrganization) {
20
return;
21
}
22
23
editOrganization(currentOrganization);
24
}
25
26
async function editOrganization(organization) {
27
const form = document.getElementById("edit_organization");
28
const inputEl = form.getElementsByTagName("input")[0];
29
if (inputEl) {
30
inputEl.value = organization.name;
31
}
32
33
form.addEventListener('submit', function(e) {
34
e.preventDefault();
35
const inputEl = form.getElementsByTagName("input")[0];
36
if (!inputEl) {
37
return;
38
}
39
try {
40
await organization.update({ name: inputEl.value });
41
} catch (err) {
42
console.error(err);
43
}
44
});
45
}
46
47
init();
48
</script>

Manage organization members

1
// pages/organizations/[id].ts
2
import { useState, useEffect } from "react";
3
import { useOrganization } from "@clerk/nextjs";
4
import type { OrganizationMembershipResource } from "@clerk/types";
5
6
// View and manage active organization members, along with any
7
// pending invitations.
8
// Invite new members.
9
export default function Organization() {
10
const {
11
organization: currentOrganization,
12
membership,
13
isLoaded,
14
} = useOrganization();
15
16
if (!isLoaded || !currentOrganization) {
17
return null;
18
}
19
20
const isAdmin = membership.role === "admin";
21
return (
22
<>
23
<h1>Organization: {currentOrganization.name}</h1>
24
<MemberList />
25
{isAdmin && <InvitationList />}
26
</>
27
);
28
}
29
30
// List of organization memberships. Administrators can
31
// change member roles or remove members from the organization.
32
function MemberList() {
33
const { membershipList, membership } = useOrganization({
34
membershipList: {},
35
});
36
37
if (!membershipList) {
38
return null;
39
}
40
41
const isCurrentUserAdmin = membership.role === "admin";
42
43
return (
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} &lt;
50
{m.publicUserData.identifier}&gt; :: {m.role}
51
{isCurrentUserAdmin && <AdminControls membership={m} />}
52
</li>
53
))}
54
</ul>
55
</div>
56
);
57
}
58
59
function AdminControls({
60
membership,
61
}: {
62
membership: OrganizationMembershipResource;
63
}){
64
const [disabled, setDisabled] = useState(false);
65
const {
66
user: { id: userId },
67
} = useUser();
68
69
if (membership.publicUserData.userId === userId) {
70
return null;
71
}
72
73
const remove = async () => {
74
setDisabled(true);
75
await membership.destroy();
76
};
77
78
const changeRole = async (role: MembershipRole) => {
79
setDisabled(true);
80
await membership.update({ role });
81
setDisabled(false);
82
};
83
84
return (
85
<>
86
::{" "}
87
<button disabled={disabled} onClick={remove}>
88
Remove member
89
</button>{" "}
90
{membership.role === "admin" ? (
91
<button disabled={disabled} onClick={() => changeRole("basic_member")}>
92
Change to member
93
</button>
94
) : (
95
<button disabled={disabled} onClick={() => changeRole("admin")}>
96
Change to admin
97
</button>
98
)}
99
</>
100
);
101
};
102
103
// List of organization pending invitations.
104
// You can invite new organization members and
105
// revoke already sent invitations.
106
function InvitationList() {
107
const { invitationList } = useOrganization();
108
109
if (!invitationList) {
110
return null;
111
}
112
113
const revoke = async (inv) => {
114
await inv.revoke();
115
};
116
117
return (
118
<div>
119
<h2>Invite member</h2>
120
<InviteMember />
121
122
<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
}
133
134
function InviteMember(){
135
const { organization } = useOrganization();
136
const [emailAddress, setEmailAddress] = useState("");
137
const [role, setRole] = useState<"basic_member" | "admin">("basic_member");
138
const [disabled, setDisabled] = useState(false);
139
140
const onSubmit = async (e) => {
141
e.preventDefault();
142
setDisabled(true);
143
await organization.inviteMember({ emailAddress, role });
144
setEmailAddress("");
145
setRole("basic_member");
146
setDisabled(false);
147
};
148
149
return (
150
<form onSubmit={onSubmit}>
151
<input
152
type="text"
153
placeholder="Email address"
154
value={emailAddress}
155
onChange={(e) => setEmailAddress(e.target.value)}
156
/>
157
<label>
158
<input
159
type="radio"
160
checked={role === "admin"}
161
onChange={() => {
162
setRole("admin");
163
}}
164
/>{" "}
165
Admin
166
</label>
167
<label>
168
<input
169
type="radio"
170
checked={role === "basic_member"}
171
onChange={() => {
172
setRole("basic_member");
173
}}
174
/>{" "}
175
Member
176
</label>{" "}
177
<button type="submit" disabled={disabled}>
178
Invite
179
</button>
180
</form>
181
);
182
};
183
1
import { useState, useEffect } from "react";
2
import { useOrganization } from "@clerk/clerk-react";
3
import type { OrganizationMembershipResource } from "@clerk/types";
4
5
// View and manage active organization members, along with any
6
// pending invitations.
7
// Invite new members.
8
export default function Organization() {
9
const {
10
organization: currentOrganization,
11
membership,
12
isLoaded,
13
} = useOrganization();
14
15
if (!isLoaded || !currentOrganization) {
16
return null;
17
}
18
19
const isAdmin = membership.role === "admin";
20
return (
21
<>
22
<h1>Organization: {currentOrganization.name}</h1>
23
<MemberList />
24
{isAdmin && <InvitationList />}
25
</>
26
);
27
}
28
29
// List of organization memberships. Administrators can
30
// change member roles or remove members from the organization.
31
function MemberList() {
32
const { membershipList, membership } = useOrganization({
33
membershipList: {},
34
});
35
36
if (!membershipList) {
37
return null;
38
}
39
40
const isCurrentUserAdmin = membership.role === "admin";
41
42
return (
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} &lt;
49
{m.publicUserData.identifier}&gt; :: {m.role}
50
{isCurrentUserAdmin && <AdminControls membership={m} />}
51
</li>
52
))}
53
</ul>
54
</div>
55
);
56
}
57
58
function AdminControls({
59
membership,
60
}: {
61
membership: OrganizationMembershipResource;
62
}){
63
const [disabled, setDisabled] = useState(false);
64
const {
65
user: { id: userId },
66
} = useUser();
67
68
if (membership.publicUserData.userId === userId) {
69
return null;
70
}
71
72
const remove = async () => {
73
setDisabled(true);
74
await membership.destroy();
75
};
76
77
const changeRole = async (role: MembershipRole) => {
78
setDisabled(true);
79
await membership.update({ role });
80
setDisabled(false);
81
};
82
83
return (
84
<>
85
::{" "}
86
<button disabled={disabled} onClick={remove}>
87
Remove member
88
</button>{" "}
89
{membership.role === "admin" ? (
90
<button disabled={disabled} onClick={() => changeRole("basic_member")}>
91
Change to member
92
</button>
93
) : (
94
<button disabled={disabled} onClick={() => changeRole("admin")}>
95
Change to admin
96
</button>
97
)}
98
</>
99
);
100
};
101
102
// List of organization pending invitations.
103
// You can invite new organization members and
104
// revoke already sent invitations.
105
function InvitationList() {
106
const { invitationList } = useOrganization();
107
108
if (!invitationList) {
109
return null;
110
}
111
112
const revoke = async (inv) => {
113
await inv.revoke();
114
};
115
116
return (
117
<div>
118
<h2>Invite member</h2>
119
<InviteMember />
120
121
<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
}
132
133
function InviteMember(){
134
const { organization } = useOrganization();
135
const [emailAddress, setEmailAddress] = useState("");
136
const [role, setRole] = useState<"basic_member" | "admin">("basic_member");
137
const [disabled, setDisabled] = useState(false);
138
139
const onSubmit = async (e) => {
140
e.preventDefault();
141
setDisabled(true);
142
await organization.inviteMember({ emailAddress, role });
143
setEmailAddress("");
144
setRole("basic_member");
145
setDisabled(false);
146
};
147
148
return (
149
<form onSubmit={onSubmit}>
150
<input
151
type="text"
152
placeholder="Email address"
153
value={emailAddress}
154
onChange={(e) => setEmailAddress(e.target.value)}
155
/>
156
<label>
157
<input
158
type="radio"
159
checked={role === "admin"}
160
onChange={() => {
161
setRole("admin");
162
}}
163
/>{" "}
164
Admin
165
</label>
166
<label>
167
<input
168
type="radio"
169
checked={role === "basic_member"}
170
onChange={() => {
171
setRole("basic_member");
172
}}
173
/>{" "}
174
Member
175
</label>{" "}
176
<button type="submit" disabled={disabled}>
177
Invite
178
</button>
179
</form>
180
);
181
};
182
1
<ul id="memberships_list"></ul>
2
<ul id="invitations_list"></ul>
3
4
<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>
12
13
<script>
14
async function renderMemberships(organization, isAdmin) {
15
const list = document.getElementById("memberships_list");
16
try {
17
const memberships = await organization.getMembers();
18
memberships.map((membership) => {
19
const li = document.createElement("li");
20
li.textContent = `${membership.identifier} - ${membership.role}`;
21
22
// Add administrative actions; update role and remove member.
23
if (isAdmin) {
24
const updateBtn = document.createElement("button");
25
updateBtn.textContent = "Change role";
26
updateBtn.addEventListener("click", async function(e) {
27
e.preventDefault();
28
const role = membership.role === "admin" ?
29
"basic_member" :
30
"admin";
31
await membership.update({ role });
32
});
33
li.appendChild(updateBtn);
34
35
const removeBtn = document.createElement("button");
36
removeBtn.textContent = "Remove";
37
removeBtn.addEventListener("click", async function(e) {
38
e.preventDefault();
39
await currentOrganization.removeMember(membership.userId);
40
});
41
li.appendChild(removeBtn);
42
}
43
44
// Add the entry to the list
45
list.appendChild(li);
46
});
47
} catch (err) {
48
console.error(err);
49
}
50
}
51
52
async function renderInvitations(organization, isAdmin) {
53
const list = document.getElementById("invitations_list");
54
try {
55
const invitations = await organization.getPendingInvitations();
56
invitations.map((invitation) => {
57
const li = document.createElement("li");
58
li.textContent = `${invitation.emailAddress} - ${invitation.role}`;
59
60
// Add administrative actions; revoke invitation
61
if (isAdmin) {
62
const revokeBtn = document.createElement("button");
63
revokeBtn.textContent = "Revoke";
64
revokeBtn.addEventListener("click", async function(e) {
65
e.preventDefault();
66
await invitation.revoke();
67
});
68
li.appendChild(revokeBtn);
69
}
70
// Add the entry to the list
71
list.appendChild(li);
72
});
73
} catch (err) {
74
console.error(err);
75
}
76
}
77
78
async function init() {
79
// This is the current organization ID.
80
const organizationId = "org_XXXXXXX";
81
const organizationMemberships = await window.Clerk.getOrganizationMemberships()
82
const currentMembership = organizationMemberships.find(membership
83
=> membership.organization.id === organizationId);
84
const currentOrganization = currentMembership.organization;
85
86
if (!currentOrganization) {
87
return;
88
}
89
const isAdmin = currentMembership.role === "admin";
90
91
renderMemberships(currentOrganization, isAdmin);
92
renderInvitations(currentOrganization, isAdmin);
93
94
if (isAdmin) {
95
const form = document.getElementById("new_invitation");
96
form.addEventListener("submit", async function(e) {
97
e.preventDefault();
98
const inputEl = form.getElementsByTagName("input");
99
if (!inputEl) {
100
return;
101
}
102
103
try {
104
await currentOrganization.inviteMember({
105
emailAddress: inputEl.value,
106
role: "basic_member",
107
});
108
} catch (err) {
109
console.error(err);
110
}
111
});
112
}
113
}
114
115
init();
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;

Was this helpful?

Clerk © 2022