Skip to content
2 changes: 2 additions & 0 deletions docs/_partials/custom-flows/future-api-callout.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
> [!IMPORTANT]
> The APIs described here are stable, and will become the default in the next major version of `clerk-js`.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Sign-up with application invitations
description: Learn how to use the Clerk API to build a custom flow for handling application invitations.
sdk: nextjs, react, expo, react-router, tanstack-react-start
---

<Include src="_partials/custom-flows-callout" />
Expand All @@ -19,236 +20,74 @@ Once the user visits the invitation link and is redirected to the specified URL,

For example, if the redirect URL was `https://www.example.com/accept-invitation`, the URL that the user would be redirected to would be `https://www.example.com/accept-invitation?__clerk_ticket=.....`.

To create a sign-up flow using the invitation token, you need to extract the token from the URL and pass it to the [`signUp.create()`](/docs/reference/javascript/sign-up#create) method, as shown in the following example. The following example also demonstrates how to collect additional user information for the sign-up; you can either remove these fields or adjust them to fit your application.
To create a sign-up flow using the invitation token, you need to call the [`signUp.ticket()`](/docs/reference/javascript/sign-up-future#ticket) method, as shown in the following example. The following example also demonstrates how to collect additional user information for the sign-up; you can either remove these fields or adjust them to fit your application.

<Tabs items={["Next.js", "JavaScript"]}>
<Tabs items={["Next.js"]}>
<Tab>
```tsx {{ filename: 'app/accept-invitation/page.tsx', collapsible: true }}
'use client'

import * as React from 'react'
import { useSignUp, useUser } from '@clerk/nextjs'
import { useSearchParams, useRouter } from 'next/navigation'
import { useRouter } from 'next/navigation'

export default function Page() {
const { isSignedIn, user } = useUser()
const { signUp, errors, fetchStatus } = useSignUp()
const router = useRouter()
const { isLoaded, signUp, setActive } = useSignUp()
const [firstName, setFirstName] = React.useState('')
const [lastName, setLastName] = React.useState('')
const [password, setPassword] = React.useState('')

// Handle signed-in users visiting this page
// This will also redirect the user once they finish the sign-up process
React.useEffect(() => {
if (isSignedIn) {
router.push('/')
}
}, [isSignedIn])

// Get the token from the query params
const token = useSearchParams().get('__clerk_ticket')

// If there is no invitation token, restrict access to this page
if (!token) {
return <p>No invitation token found.</p>
}
const handleSubmit = async (formData: FormData) => {
const firstName = formData.get('firstName') as string
const lastName = formData.get('lastName') as string
const password = formData.get('password') as string

// Handle submission of the sign-up form
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()

if (!isLoaded) return

try {
if (!token) return null

// Create a new sign-up with the supplied invitation token.
// Make sure you're also passing the ticket strategy.
// After the below call, the user's email address will be
// automatically verified because of the invitation token.
const signUpAttempt = await signUp.create({
strategy: 'ticket',
ticket: token,
firstName,
lastName,
password,
await signUp.ticket({
firstName,
lastName,
password,
})
if (signUp.status === 'complete') {
await signUp.finalize({
navigate: () => {
router.push('/')
},
})

// If the sign-up was completed, set the session to active
if (signUpAttempt.status === 'complete') {
await setActive({ session: signUpAttempt.createdSessionId })
} else {
// If the sign-up status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signUpAttempt, null, 2))
}
} catch (err) {
console.error(JSON.stringify(err, null, 2))
}
}

if (signUp.status === 'complete' || isSignedIn) {
return null
}

return (
<>
<h1>Sign up</h1>
<form onSubmit={handleSubmit}>
<form action={handleSubmit}>
<div>
<label htmlFor="firstName">Enter first name</label>
<input
id="firstName"
type="text"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input id="firstName" type="text" name="firstName" />
{errors.fields.firstName && <p>{errors.fields.firstName.message}</p>}
</div>
<div>
<label htmlFor="lastName">Enter last name</label>
<input
id="lastName"
type="text"
name="lastName"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input id="lastName" type="text" name="lastName" />
{errors.fields.lastName && <p>{errors.fields.lastName.message}</p>}
</div>
<div>
<label htmlFor="password">Enter password</label>
<input
id="password"
type="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<input id="password" type="password" name="password" />
{errors.fields.password && <p>{errors.fields.password.message}</p>}
</div>
<div id="clerk-captcha" />
<div>
<button type="submit">Next</button>
<button type="submit" disabled={fetchStatus === 'fetching'}>
Next
</button>
</div>
</form>
</>
)
}
```
</Tab>

<Tab>
<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="signed-in"></div>

<div id="task"></div>

<div id="sign-up">
<h2>Sign up</h2>
<form id="sign-up-form">
<label for="firstName">Enter first name</label>
<input name="firstName" id="firstName" />
<label for="lastName">Enter last name</label>
<input name="lastName" id="lastName" />
<label for="password">Enter password</label>
<input name="password" id="password" />
<button type="submit">Continue</button>
</form>
</div>

<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

const clerk = new Clerk(pubKey)
await clerk.load()

if (clerk.isSignedIn) {
// Mount user button component
document.getElementById('signed-in').innerHTML = `
<div id="user-button"></div>
`

const userbuttonDiv = document.getElementById('user-button')

clerk.mountUserButton(userbuttonDiv)
} else if (clerk.session.currentTask) {
// Check for pending tasks and display custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
switch (clerk.session.currentTask.key) {
case 'choose-organization': {
document.getElementById('app').innerHTML = `
<div id="task"></div>
`

const taskDiv = document.getElementById('task')

clerk.mountTaskChooseOrganization(taskDiv)
}
}
} else {
// Get the token from the query parameter
const param = '__clerk_ticket'
const token = new URL(window.location.href).searchParams.get(param)

// Handle the sign-up form
document.getElementById('sign-up-form').addEventListener('submit', async (e) => {
e.preventDefault()

const formData = new FormData(e.target)
const firstName = formData.get('firstName')
const lastName = formData.get('lastName')
const password = formData.get('password')

try {
// Start the sign-up process using the ticket method
const signUpAttempt = await clerk.client.signUp.create({
strategy: 'ticket',
ticket: token,
firstName,
lastName,
password,
})

// If sign-up was successful, set the session to active
if (signUpAttempt.status === 'complete') {
await clerk.setActive({
session: signUpAttempt.createdSessionId,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.log(session?.currentTask)
return
}

await router.push('/')
},
})
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signUpAttempt, null, 2))
}
} catch (err) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
})
}
```
</CodeBlockTabs>
</Tab>
</Tabs>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Add bot protection to your custom sign-up flow
description: Learn how to add Clerk's bot protection to your custom sign-up flow.
sdk: nextjs, react, expo, js-frontend, react-router, tanstack-react-start
---

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