Invite users to your application
Inviting users to your Clerk application allows you to onboard new users seamlessly by sending them a unique invitation link.
Once you create an invitation, Clerk sends an email to the invited user with a unique invitation link. When the user visits the invitation link, they will be redirected to the Account Portal sign-up page and their email address will be automatically verified. If you want to redirect the user to a specific page in your application, you can specify a redirect URL when creating the invitation.
Invitations expire after a month. If the user clicks on an expired invitation, they will get redirected to the application's sign-up page and will have to go through the normal sign-up flow. Their email address will not be auto-verified.
Create an invitation
You can create an invitation either in the Clerk Dashboard or programmatically. When making this decision, keep in mind that if you create an invitation through the Clerk Dashboard, you can only set an invitation expiration date. If you create an invitation programatically, you are able to set more options, such as the URL you want the user to be redirected to after they accept the invitation, metadata to add to the invitation, or whether an invitation should be created if there is already an existing invitation for the given email address.
In the Clerk Dashboard
To create an invitation in the Clerk Dashboard, navigate to the Invitations page.
Programmatically
To create an invitation programmatically, you can either make a request directly to Clerk's Backend API or use the createInvitation() method as shown in the following example.
import { clerkClient } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
export async function GET() {
  try {
    const client = await clerkClient()
    const invitation = await client.invitations.createInvitation({
      emailAddress: 'invite@example.com',
      redirectUrl: 'https://www.example.com/my-sign-up',
      publicMetadata: {
        example: 'metadata',
        example_nested: {
          nested: 'metadata',
        },
      },
    })
    return NextResponse.json({ message: 'Invitation created', invitation })
  } catch (error) {
    console.log(error)
    return NextResponse.json({ error: 'Error creating invitation' })
  }
}import type { APIRoute } from 'astro'
import { clerkClient } from '@clerk/astro/server'
export const GET: APIRoute = async (context) => {
  await clerkClient(context).invitations.createInvitation({
    emailAddress: 'invite@example.com',
    redirectUrl: 'https://www.example.com/my-sign-up',
    publicMetadata: {
      example: 'metadata',
      example_nested: {
        nested: 'metadata',
      },
    },
  })
  return new Response(JSON.stringify({ success: true }), { status: 200 })
}import { getAuth, clerkClient } from '@clerk/express'
app.post('/createUser', async (req, res) => {
  await clerkClient.invitations.createInvitation({
    emailAddress: 'invite@example.com',
    redirectUrl: 'https://www.example.com/my-sign-up',
    publicMetadata: {
      example: 'metadata',
      example_nested: {
        nested: 'metadata',
      },
    },
    password: 'password',
  })
  res.status(200).json({ success: true })
})import { clerkClient } from '@clerk/react-router/server'
import type { Route } from './+types/example'
export async function loader(args: Route.LoaderArgs) {
  await clerkClient.invitations.createInvitation({
    emailAddress: 'invite@example.com',
    redirectUrl: 'https://www.example.com/my-sign-up',
    publicMetadata: {
      example: 'metadata',
      example_nested: {
        nested: 'metadata',
      },
    },
  })
  return { success: true }
}import { json } from '@tanstack/react-start'
import { createFileRoute } from '@tanstack/react-router'
import { clerkClient } from '@clerk/tanstack-react-start/server'
export const ServerRoute = createFileRoute('/api/example')({
  server: {
    handlers: {
      GET: async () => {
        await clerkClient().invitations.createInvitation({
          emailAddress: 'invite@example.com',
          redirectUrl: 'https://www.example.com/my-sign-up',
          publicMetadata: {
            example: 'metadata',
            example_nested: {
              nested: 'metadata',
            },
          },
        })
        return json({ success: true })
      },
    },
  },
})See the Backend API reference for an example of the response.
With a redirect URL
When you create an invitation programmatically, you can specify a redirectUrl parameter. This parameter tells Clerk where to redirect the user when they visit the invitation link.
Once the user visits the invitation link, they will be redirected to the page you specified, which means you must handle the sign-up flow in your code for that page. You can either embed the <SignUp /> component on that page, or if the prebuilt component doesn't meet your specific needs or if you require more control over the logic, you can build a custom flow.
With invitation metadata
When you create an invitation programmatically, you can specify a publicMetadata parameter to add metadata to an invitation. Once the invited user signs up using the invitation link, the invitation metadata will end up in the user's public metadata. Learn more about user metadata.
Revoke an invitation
You can revoke an invitation at any time. Revoking an invitation prevents the user from using the invitation link that was sent to them. You can revoke an invitation in the Clerk Dashboard or programmatically.
In the Clerk Dashboard
To revoke an invitation in the Clerk Dashboard, navigate to the Invitations page.
Programmatically
To revoke an invitation programmatically, you can either make a request directly to Clerk's Backend API or use the revokeInvitation() method as shown in the following example.
import { clerkClient } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
export async function GET() {
  try {
    const client = await clerkClient()
    const invitation = await client.invitations.revokeInvitation({
      invitationId: 'invitation_123',
    })
    return NextResponse.json({ message: 'Invitation revoked' })
  } catch (error) {
    console.log(error)
    return NextResponse.json({ error: 'Error revoking invitation' })
  }
}import type { APIRoute } from 'astro'
import { clerkClient } from '@clerk/astro/server'
export const GET: APIRoute = async (context) => {
  await clerkClient(context).invitations.revokeInvitation({
    invitationId: 'invitation_123',
  })
  return new Response(JSON.stringify({ success: true }), { status: 200 })
}import { getAuth, clerkClient } from '@clerk/express'
app.post('/revokeInvitation', async (req, res) => {
  await clerkClient.invitations.revokeInvitation({
    invitationId: 'invitation_123',
  })
  res.status(200).json({ success: true })
})import { clerkClient } from '@clerk/react-router/server'
import type { Route } from './+types/example'
export async function loader(args: Route.LoaderArgs) {
  await clerkClient.invitations.revokeInvitation({
    invitationId: 'invitation_123',
  })
  return { success: true }
}import { json } from '@tanstack/react-start'
import { createFileRoute } from '@tanstack/react-router'
import { clerkClient } from '@clerk/tanstack-react-start/server'
export const ServerRoute = createFileRoute('/api/example')({
  server: {
    handlers: {
      GET: async () => {
        await clerkClient().invitations.revokeInvitation({
          invitationId: 'invitation_123',
        })
        return json({ success: true })
      },
    },
  },
})See the Backend API reference for an example of the response.
Custom flow
Clerk's prebuilt components and Account Portal pages handle the sign-up flow for you, including the invitation flow. If Clerk's prebuilt components don't meet your specific needs or if you require more control over the logic, you can rebuild the existing Clerk flows using the Clerk API. For more information, see the custom flow for application invitations.
Feedback
Last updated on