šŸ“ Sign Up | šŸ” Log In

← Root | ↑ Up

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ šŸ“„ shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/organizations/manage-roles │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

╔══════════════════════════════════════════════════════════════════════════════════════════════╗
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘

title: Build a custom flow for managing member roles in an organization description: Learn how to use the Clerk API build a custom flow for managing member roles in an organization.

<Include src="_partials/custom-flows-callout" />

Organization members with appropriate permissions can manage a member's role and remove members within an organization.

This guide will demonstrate how to use the Clerk API to build a custom flow for managing member roles in an organization.

<Tabs items={["Next.js", "JavaScript"]}> <Tab> The following example:

1. Uses the [`useOrganization()`](/docs/reference/hooks/use-organization) hook to get `memberships`, which is a list of the [active organization's](!active-organization) memberships.
   - `memberships` is an object with `data` that contains an array of [`OrganizationMembership`](/docs/reference/javascript/types/organization-membership) objects.
   - Each `OrganizationMembership` object has an [`update()`](/docs/reference/javascript/types/organization-membership#update) and [`destroy()`](/docs/reference/javascript/types/organization-membership#destroy) method to update the member's role and remove the member from the organization, respectively.
1. Maps over the `data` array to display the memberships in a table, providing an "Update Role" and "Remove Member" button for each membership that calls the `update()` and `destroy()` methods, respectively.

This example is written for Next.js App Router but can be adapted for any React-based framework.

```tsx {{ filename: 'app/components/ManageRoles.tsx', collapsible: true }}
'use client'

import { useState, useEffect, ChangeEventHandler, useRef } from 'react'
import { useOrganization, useUser } from '@clerk/nextjs'
import type { OrganizationCustomRoleKey } from '@clerk/types'

export const OrgMembersParams = {
  memberships: {
    pageSize: 5,
    keepPreviousData: true,
  },
}

// List of organization memberships. Administrators can
// change member roles or remove members from the organization.
export const ManageRoles = () => {
  const { user } = useUser()
  const { isLoaded, memberships } = useOrganization(OrgMembersParams)

  if (!isLoaded) {
    return <>Loading</>
  }

  return (
    <>
      <h1>Memberships List</h1>
      <table>
        <thead>
          <tr>
            <th>User</th>
            <th>Joined</th>
            <th>Role</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {memberships?.data?.map((mem) => (
            <tr key={mem.id}>
              <td>
                {mem.publicUserData.identifier} {mem.publicUserData.userId === user?.id && '(You)'}
              </td>
              <td>{mem.createdAt.toLocaleDateString()}</td>
              <td>
                <SelectRole
                  defaultRole={mem.role}
                  onChange={async (e) => {
                    await mem.update({
                      role: e.target.value as OrganizationCustomRoleKey,
                    })
                    await memberships?.revalidate()
                  }}
                />
              </td>
              <td>
                <button
                  onClick={async () => {
                    await mem.destroy()
                    await memberships?.revalidate()
                  }}
                >
                  Remove
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      <div>
        <button
          disabled={!memberships?.hasPreviousPage || memberships?.isFetching}
          onClick={() => memberships?.fetchPrevious?.()}
        >
          Previous
        </button>

        <button
          disabled={!memberships?.hasNextPage || memberships?.isFetching}
          onClick={() => memberships?.fetchNext?.()}
        >
          Next
        </button>
      </div>
    </>
  )
}

type SelectRoleProps = {
  fieldName?: string
  isDisabled?: boolean
  onChange?: ChangeEventHandler<HTMLSelectElement>
  defaultRole?: string
}

const SelectRole = (props: SelectRoleProps) => {
  const { fieldName, isDisabled = false, onChange, defaultRole } = props
  const { organization } = useOrganization()
  const [fetchedRoles, setRoles] = useState<OrganizationCustomRoleKey[]>([])
  const isPopulated = useRef(false)

  useEffect(() => {
    if (isPopulated.current) return
    organization
      ?.getRoles({
        pageSize: 20,
        initialPage: 1,
      })
      .then((res) => {
        isPopulated.current = true
        setRoles(res.data.map((roles) => roles.key as OrganizationCustomRoleKey))
      })
  }, [organization?.id])

  if (fetchedRoles.length === 0) return null

  return (
    <select
      name={fieldName}
      disabled={isDisabled}
      aria-disabled={isDisabled}
      onChange={onChange}
      defaultValue={defaultRole}
    >
      {fetchedRoles?.map((roleKey) => (
        <option key={roleKey} value={roleKey}>
          {roleKey}
        </option>
      ))}
    </select>
  )
}
```
</Tab> <Tab> The following example includes a `checkAdminAndRenderMemberships()` function that checks if the user is an admin of the currently active organization and calls `renderMemberships()`. The `renderMemberships()` function lists the organization's memberships and allows administrators to update a member's role and remove a member from the 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>

      <h1>Memberships List</h1>
      <table>
        <thead>
          <tr>
            <th>User ID</th>
            <th>User identifier</th>
            <th>Joined</th>
            <th>Role</th>
            <th id="update-role-head" hidden>Update role</th>
            <th id="remove-member-head" hidden>Remove member</th>
          </tr>
        </thead>
        <tbody id="memberships-table-body"></tbody>
      </table>

      <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) {
      // Render list of organization memberships
      async function renderMemberships(organization, isAdmin) {
        try {
          const { data } = await organization.getMemberships()
          const memberships = data
          console.log(`getMemberships:`, memberships)

          memberships.map((membership) => {
            const membershipTable = document.getElementById('memberships-table-body')
            const row = membershipTable.insertRow()
            row.insertCell().textContent = membership.publicUserData.userId
            row.insertCell().textContent = membership.publicUserData.identifier
            row.insertCell().textContent = membership.createdAt.toLocaleDateString()
            row.insertCell().textContent = membership.role

            // Add administrative actions:
            // Add and remove a member, and update a member's role.
            if (isAdmin) {
              // Show update and remove member buttons
              document.getElementById('update-role-head').removeAttribute('hidden')
              document.getElementById('remove-member-head').removeAttribute('hidden')

              // Get the user ID of the member
              const userId = membership.publicUserData.userId

              // Update a member's role
              const updateBtn = document.createElement('button')
              updateBtn.textContent = 'Change role'
              updateBtn.addEventListener('click', async function (e) {
                e.preventDefault()

                const role = membership.role === 'org:admin' ? 'org:member' : 'org:admin'

                await organization
                  .updateMember({ userId, role })
                  .then((res) => console.log(`updateMember response:`, res))
                  .catch((error) => console.log('An error occurred:', error))
              })
              row.insertCell().appendChild(updateBtn)

              // Remove a member
              const removeBtn = document.createElement('button')
              removeBtn.textContent = 'Remove'
              removeBtn.addEventListener('click', async function (e) {
                e.preventDefault()

                await organization
                  .removeMember(userId)
                  .then((res) => console.log(`removeMember response:`, res))
                  .catch((error) => console.log('An error occurred:', error))
              })
              row.insertCell().appendChild(removeBtn)
            }
          })
        } catch (error) {
          console.log('An error occurred:', error)
        }
      }

      /**
       * Checks if a user is an admin of the
       * currently active organization and
       * renders the organization's memberships.
       */
      async function checkAdminAndRenderMemberships() {
        const organizationId = clerk.organization.id

        const { data } = await clerk.user.getOrganizationMemberships()

        const organizationMemberships = data

        const currentMembership = organizationMemberships.find(
          (membership) => membership.organization.id === organizationId,
        )
        const currentOrganization = currentMembership.organization

        if (!currentOrganization) return

        const isAdmin = currentMembership.role === 'org:admin'

        console.log(`Current organization:`, currentOrganization)

        renderMemberships(currentOrganization, isAdmin)
      }

      checkAdminAndRenderMemberships()
    } else {
      // If there is no active organization,
      // mount Clerk's <OrganizationSwitcher />
      // to allow the user to set an organization as active
      document.getElementById('app').innerHTML = `
        <h2>Select an organization to set it as active</h2>
        <div id="org-switcher"></div>
      `

      const orgSwitcherDiv = document.getElementById('org-switcher')

      clerk.mountOrganizationSwitcher(orgSwitcherDiv)
    }
  } else {
    // If there is no active user, mount Clerk's <SignIn />
    document.getElementById('app').innerHTML = `
      <div id="sign-in"></div>
    `

    const signInDiv = document.getElementById('sign-in')

    clerk.mountSignIn(signInDiv)
  }
  ```
</CodeBlockTabs>
</Tab> </Tabs>
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•

← Root | ↑ Up