Skip to main content
Docs

Build a custom flow for switching organizations

Warning

This guide is for users who want to build a custom user interface using the Clerk API. To use a prebuilt UI, use the Account Portal pages or prebuilt components.

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:

  1. Use the useOrganizationList() hook to get memberships, which is a list of the current user's organization memberships. memberships returns data, which is an array of objects.
  2. 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 .

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.

app/components/CustomOrganizationSwitcher.tsx
'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>
      )}
    </>
  )
}
app/components/CustomOrganizationSwitcher.tsx
'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:

  1. Calls the method to retrieve the list of organizations the current user is a part of. This method returns data, which is an array of objects.
  2. Maps over the data array to display the user's organization memberships in a list, providing a button that calls 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.

index.html
<!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>
main.js
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.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/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

What did you think of this content?

Last updated on