Organizations, Roles, and Permissions Manage membership requests
Build a custom flow for managing organization membership requests
The following example uses the useOrganization()
hook to get membershipRequests
, which is a list of the active organization's membership requests.
membershipRequests
is an object with data
that contains an array of OrganizationMembershipRequest
objects.
Each OrganizationMembershipRequest
object has an accept()
and reject()
method to accept or reject the membership request, respectively.
This example is written for Next.js App Router but can be adapted for any React meta framework, such as Remix.
app /components /MembershipRequests.tsx '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 >
</>
)
}
The following example:
Calls the getMembershipRequests()
method to retrieve the list of membership requests for the active organization. This method returns data
, which is an array of OrganizationMembershipRequest
objects.
Maps over the data
array to display the membership requests in a table.
Provides an "Accept" and "Reject" button for each request that calls the accept()
and reject()
methods, respectively.
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 >
< 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 >
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 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)
}
Last updated on Jan 21, 2025