Build a custom flow for switching organizations
This guide will demonstrate how to use the Clerk API to build a custom flow for switching between organizations.
Two examples are provided: one for a paginated list and one for an infinite list.
The following examples:
- Use the
useOrganizationList()
hook to getmemberships
, which is a list of the current user's organization memberships.memberships
returnsdata
, which is an array ofOrganizationMembership
objects. - Map over the
data
array to display the user's organization memberships in a table, providing a button that callssetActive()
to set the selected organization as the active organization.- If there are no organizations, the
<CreateOrganization />
component (custom-flow version, not the Clerk component) is rendered to allow the user to create an organization.
- If there are no organizations, the
The difference between the two examples is the parameters passed to the useOrganizationList()
hook in order to determine how the list is paginated.
- The "Paginated list" example provides a button to load more organizations if there are more available. The
data
array is paginated and will only return the first 5 results, so thefetchNext()
method is used to load more organizations if they are available. - The "Infinite list" example sets the
infinite
option totrue
to enable infinite results.
This example is written for Next.js App Router but can be adapted for any React-based framework.
'use client'
import { useAuth, useOrganizationList } from '@clerk/nextjs'
import CreateOrganization from '../components/create-organization' // See /docs/custom-flows/create-organizations for this component
// List user's organization memberships
export default function JoinedOrganizations() {
const { isLoaded, setActive, userMemberships } = useOrganizationList({
userMemberships: {
// Set pagination parameters
pageSize: 5,
keepPreviousData: true,
},
})
const { orgId } = useAuth()
if (!isLoaded) {
return <p>Loading...</p>
}
return (
<>
<h1>Joined organizations</h1>
{userMemberships?.data?.length > 0 && (
<>
<table>
<thead>
<tr>
<th>Identifier</th>
<th>Organization</th>
<th>Joined</th>
<th>Role</th>
<th>Set as active org</th>
</tr>
</thead>
<tbody>
{userMemberships?.data?.map((mem) => (
<tr key={mem.id}>
<td>{mem.publicUserData.identifier}</td>
<td>{mem.organization.name}</td>
<td>{mem.createdAt.toLocaleDateString()}</td>
<td>{mem.role}</td>
<td>
{orgId === mem.organization.id ? (
<button onClick={() => setActive({ organization: mem.organization.id })}>
Set as active
</button>
) : (
<p>Currently active</p>
)}
</td>
</tr>
))}
</tbody>
</table>
<div>
<button
disabled={!userMemberships?.hasPreviousPage || userMemberships?.isFetching}
onClick={() => userMemberships?.fetchPrevious?.()}
>
Previous
</button>
<button
disabled={!userMemberships?.hasNextPage || userMemberships?.isFetching}
onClick={() => userMemberships?.fetchNext?.()}
>
Next
</button>
</div>
</>
)}
{userMemberships?.data?.length === 0 && (
<div>
<p>No organizations found</p>
<CreateOrganization />
</div>
)}
</>
)
}
'use client'
import { useAuth, useOrganizationList } from '@clerk/nextjs'
import CreateOrganization from '../components/create-organization' // See /docs/custom-flows/create-organizations for this component
// List user's organization memberships
export default function JoinedOrganizations() {
const { isLoaded, setActive, userMemberships } = useOrganizationList({
userMemberships: {
// Set pagination parameters
infinite: true,
},
})
const { orgId } = useAuth()
if (!isLoaded) {
return <p>Loading...</p>
}
return (
<>
<h1>Joined organizations</h1>
{userMemberships?.data?.length > 0 && (
<table>
<thead>
<tr>
<th>Identifier</th>
<th>Organization</th>
<th>Joined</th>
<th>Role</th>
<th>Set as active org</th>
</tr>
</thead>
<tbody>
{userMemberships?.data?.map((mem) => (
<tr key={mem.id}>
<td>{mem.publicUserData.identifier}</td>
<td>{mem.organization.name}</td>
<td>{mem.createdAt.toLocaleDateString()}</td>
<td>{mem.role}</td>
<td>
{orgId === mem.organization.id ? (
<button onClick={() => setActive({ organization: mem.organization.id })}>
Set as active
</button>
) : (
<p>Currently active</p>
)}
</td>
</tr>
))}
</tbody>
</table>
)}
{userMemberships?.data?.length === 0 && (
<div>
<p>No organizations found</p>
<CreateOrganization />
</div>
)}
</>
)
}
The following example:
- Calls the
getOrganizationMemberships()
method to retrieve the list of organizations the current user is a part of. This method returnsdata
, which is an array ofOrganizationMembership
objects. - Maps over the
data
array to display the user's organization memberships in a list, providing a button that callssetActive()
to set the selected organization as the active organization.
Use the tabs to view the code necessary for the index.html
and main.js
files.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<h2>Joined organizations</h2>
<table>
<thead>
<tr>
<th>Identifier</th>
<th>Organization</th>
<th>Joined</th>
<th>Role</th>
<th>Set as active org</th>
</tr>
</thead>
<tbody id="memberships-table-body"></tbody>
</table>
<div id="create-organization-container" hidden>
<h1>Create an organization</h1>
<form id="create-organization">
<label for="name">Name</label>
<input id="name" name="name" />
<button>Create organization</button>
</form>
</div>
<script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
</body>
</html>
import { Clerk } from '@clerk/clerk-js'
const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
if (!pubKey) {
throw new Error('Add your VITE_CLERK_PUBLISHABLE_KEY to .env file')
}
const clerk = new Clerk('YOUR_PUBLISHABLE_KEY')
await clerk.load()
if (clerk.user) {
// Check for an active organization
if (clerk.organization) {
const { data } = await clerk.user.getOrganizationMemberships()
const memberships = data
memberships.map((membership) => {
const membershipTable = document.getElementById('memberships-table-body')
const row = membershipTable.insertRow()
row.insertCell().textContent = membership.publicUserData.identifier
row.insertCell().textContent = membership.organization.name
row.insertCell().textContent = membership.createdAt.toLocaleDateString()
row.insertCell().textContent = membership.role
// Set as active organization
const addBtn = document.createElement('button')
addBtn.textContent = 'Set as active'
addBtn.addEventListener('click', async function (e) {
e.preventDefault()
await clerk
.setActive({ organization: membership.organization.id })
.then((res) => {
console.log('Set as active:', res)
})
.catch((err) => {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
})
})
row.insertCell().appendChild(addBtn)
})
} else {
// If there is no active organization,
// render a form to create an organization
document.getElementById('create-organization-container').removeAttribute('hidden')
const form = document.getElementById('create-organization')
form.addEventListener('submit', function (e) {
e.preventDefault()
const inputEl = document.getElementById('name')
if (!inputEl) {
// ... handle empty input
return
}
clerk
.createOrganization({ name: inputEl.value })
.then((res) => console.log(res))
.catch((error) => console.log('An error occurred:', error))
})
}
} else {
// If there is no active user, mount Clerk's <SignIn />
// or add your sign-in custom flow
document.getElementById('app').innerHTML = `
<div id="sign-in"></div>
`
const signInDiv = document.getElementById('sign-in')
clerk.mountSignIn(signInDiv)
}
Feedback
Last updated on