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

← Root | ↑ Up

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

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

title: Build a custom flow for managing organization membership requests description: Learn how to use the Clerk API to build a custom flow for managing organization membership requests.

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

This guide will demonstrate how to use the Clerk API to build a custom flow for managing organization membership requests.

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

1. Uses the [`useOrganization()`](/docs/reference/hooks/use-organization) hook to get `membershipRequests`, which is a list of the [active organization's](!active-organization) membership requests.
   - `membershipRequests` is an object with `data` that contains an array of [`OrganizationMembershipRequest`](/docs/reference/javascript/types/organization-membership-request) objects.
   - Each `OrganizationMembershipRequest` object has an [`accept()`](/docs/reference/javascript/types/organization-membership-request#accept) and [`reject()`](/docs/reference/javascript/types/organization-membership-request#reject) method to accept or reject the membership request, respectively.
1. Maps over the `data` array to display the membership requests in a table, providing an "Accept" and "Reject" button for each request that calls the `accept()` and `reject()` methods, respectively.

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

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

import { useOrganization } from '@clerk/nextjs'

export const MembershipRequestsParams = {
  membershipRequests: {
    pageSize: 5,
    keepPreviousData: true,
  },
}

// List of organization membership requests.
export const MembershipRequests = () => {
  const { isLoaded, membershipRequests } = useOrganization(MembershipRequestsParams)

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

  return (
    <>
      <h1>Membership requests</h1>
      <table>
        <thead>
          <tr>
            <th>User</th>
            <th>Date requested</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {membershipRequests?.data?.map((mem) => (
            <tr key={mem.id}>
              <td>{mem.publicUserData.identifier}</td>
              <td>{mem.createdAt.toLocaleDateString()}</td>
              <td>
                <button
                  onClick={async () => {
                    await mem.accept()
                  }}
                >
                  Accept
                </button>
                <button
                  onClick={async () => {
                    await mem.reject()
                  }}
                >
                  Reject
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

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

        <button
          disabled={!membershipRequests?.hasNextPage || membershipRequests?.isFetching}
          onClick={() => membershipRequests?.fetchNext?.()}
        >
          Next
        </button>
      </div>
    </>
  )
}
```
</Tab> <Tab> The following example:
1. Calls the [`getMembershipRequests()`](/docs/reference/javascript/organization#get-membership-requests) method to retrieve the list of membership requests for the active organization. This method returns `data`, which is an array of [`OrganizationMembershipRequest`](/docs/reference/javascript/types/organization-membership-request) objects.
1. Maps over the `data` array to display the membership requests in a table.
1. Provides an "Accept" and "Reject" button for each request that calls the [`accept()`](/docs/reference/javascript/types/organization-membership-request#accept) and [`reject()`](/docs/reference/javascript/types/organization-membership-request#reject) methods, respectively.

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>Membership Requests</h1>
      <table>
        <thead>
          <tr>
            <th>User</th>
            <th>Date requested</th>
            <th>Accept</th>
            <th>Reject</th>
          </tr>
        </thead>
        <tbody id="requests-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) {
      const requestsTable = document.getElementById('requests-table-body')
      const { data } = await clerk.organization
        .getMembershipRequests()
        .then((res) => console.log(`Membership requests:`, data).catch((err) => console.error(err)))
      const requests = data

      requests.map((request) => {
        const row = requestsTable.insertRow()
        row.insertCell().textContent = request.publicUserData.identifier
        row.insertCell().textContent = request.createdAt.toLocaleDateString()

        // Accept request
        const acceptBtn = document.createElement('button')
        acceptBtn.textContent = 'Accept'
        acceptBtn.addEventListener('click', async function (e) {
          e.preventDefault()
          await request.accept()
        })
        row.insertCell().appendChild(acceptBtn)

        // Reject request
        const rejectBtn = document.createElement('button')
        rejectBtn.textContent = 'Reject'
        rejectBtn.addEventListener('click', async function (e) {
          e.preventDefault()
          await request.reject()
        })
        row.insertCell().appendChild(rejectBtn)
      })
    } 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