āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/organizations/organization-switcher ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
This guide will demonstrate how to use the Clerk API to build a custom flow for switching between organizations.
<Tabs items={["Next.js", "JavaScript"]}> <Tab> Two examples are provided: one for a paginated list and one for an infinite list.
The following examples:
1. Use the [`useOrganizationList()`](/docs/reference/hooks/use-organization-list) hook to get `memberships`, which is a list of the current user's organization memberships. `memberships` returns `data`, which is an array of [`OrganizationMembership`](/docs/reference/javascript/types/organization-membership) objects.
1. Map over the `data` array to display the user's organization memberships in a table, providing a button that calls `setActive()` to set the selected organization as the [active organization](!active-organization).
- If there are no organizations, the [`<CreateOrganization />` component (custom-flow version, not the Clerk component)](/docs/guides/development/custom-flows/organizations/create-organizations) is rendered to allow the user to create an organization.
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 the `fetchNext()` method is used to load more organizations if they are available.
- The "Infinite list" example sets the `infinite` option to `true` to enable infinite results.
This example is written for Next.js App Router but can be adapted for any React-based framework.
<CodeBlockTabs options={["Paginated list", "Infinite list"]}>
```jsx {{ filename: 'app/components/CustomOrganizationSwitcher.tsx', collapsible: true }}
'use client'
import { useAuth, useOrganizationList } from '@clerk/nextjs'
import CreateOrganization from '../components/create-organization' // See /docs/guides/development/custom-flows/organizations/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>
)}
</>
)
}
```
```jsx {{ filename: 'app/components/CustomOrganizationSwitcher.tsx', collapsible: true }}
'use client'
import { useAuth, useOrganizationList } from '@clerk/nextjs'
import CreateOrganization from '../components/create-organization' // See /docs/guides/development/custom-flows/organizations/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>
)}
</>
)
}
```
</CodeBlockTabs>
</Tab>
<Tab>
The following example:
1. Calls the [`getOrganizationMemberships()`](/docs/reference/javascript/user#get-organization-memberships) method to retrieve the list of organizations the current user is a part of. This method returns `data`, which is an array of [`OrganizationMembership`](/docs/reference/javascript/types/organization-membership) objects.
1. Maps over the `data` array to display the user's organization memberships in a list, providing a button that calls [`setActive()`](/docs/reference/javascript/clerk#set-active) 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.
<CodeBlockTabs options={["index.html", "main.js"]}>
```html {{ filename: 'index.html', collapsible: true }}
<!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>
```
```js {{ filename: 'main.js', collapsible: true }}
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('{{pub_key}}')
await clerk.load()
if (clerk.isSignedIn) {
// 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/guides/development/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)
}
```
</CodeBlockTabs>
</Tab>
</Tabs>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā