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 OrganizationMembership 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 active 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.

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

What did you think of this content?

Last updated on