Build a custom flow for adding an email to a user's account
Users are able to add multiple email addresses to their account.
Every user has a User
object that represents their account. The User
object has a emailAddresses
property that contains all the email addresses associated with the user.
The following example demonstrates how to build a custom user interface that allows users to add an email address to their account. The example:
- Uses the
useUser()
hook to get theUser
object. - Uses the
User.createEmailAddress()
method to add the email address to the user's account. A newEmailAddress
object is created and stored inUser.emailAddresses
. - Uses the
prepareVerification()
method on the newly createdEmailAddress
object to send a verification code to the user. - Uses the
attemptVerification()
method on the sameEmailAddress
object with the verification code provided by the user to verify the email address.
'use client'
import * as React from 'react'
import { useUser } from '@clerk/nextjs'
import { EmailAddressResource } from '@clerk/types'
export default function Page() {
const { isLoaded, user } = useUser()
const [email, setEmail] = React.useState('')
const [code, setCode] = React.useState('')
const [isVerifying, setIsVerifying] = React.useState(false)
const [successful, setSuccessful] = React.useState(false)
const [emailObj, setEmailObj] = React.useState<EmailAddressResource | undefined>()
if (!isLoaded) return null
if (isLoaded && !user?.id) {
return <p>You must be logged in to access this page</p>
}
// Handle addition of the email address
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
// Add an unverified email address to user
const res = await user.createEmailAddress({ email })
// Reload user to get updated User object
await user.reload()
// Find the email address that was just added
const emailAddress = user.emailAddresses.find((a) => a.id === res.id)
// Create a reference to the email address that was just added
setEmailObj(emailAddress)
// Send the user an email with the verification code
emailAddress?.prepareVerification({ strategy: 'email_code' })
// Set to true to display second form
// and capture the OTP code
setIsVerifying(true)
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
// Handle the submission of the verification form
const verifyCode = async (e: React.FormEvent) => {
e.preventDefault()
try {
// Verify that the code entered matches the code sent to the user
const emailVerifyAttempt = await emailObj?.attemptVerification({ code })
if (emailVerifyAttempt?.verification.status === 'verified') {
setSuccessful(true)
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(emailVerifyAttempt, null, 2))
}
} catch (err) {
console.error(JSON.stringify(err, null, 2))
}
}
// Display a success message if the email was added successfully
if (successful) {
return (
<>
<h1>Email added!</h1>
</>
)
}
// Display the verification form to capture the OTP code
if (isVerifying) {
return (
<>
<h1>Verify email</h1>
<div>
<form onSubmit={(e) => verifyCode(e)}>
<div>
<label htmlFor="code">Enter code</label>
<input
onChange={(e) => setCode(e.target.value)}
id="code"
name="code"
type="text"
value={code}
/>
</div>
<div>
<button type="submit">Verify</button>
</div>
</form>
</div>
</>
)
}
// Display the initial form to capture the email address
return (
<>
<h1>Add Email</h1>
<div>
<form onSubmit={(e) => handleSubmit(e)}>
<div>
<label htmlFor="email">Enter email address</label>
<input
onChange={(e) => setEmail(e.target.value)}
id="email"
name="email"
type="email"
value={email}
/>
</div>
<div>
<button type="submit">Continue</button>
</div>
</form>
</div>
</>
)
}
import SwiftUI
import ClerkSDK
struct AddEmailView: View {
@State private var email = ""
@State private var code = ""
@State private var isVerifying = false
// Create a reference to the email address that we'll be creating
@State private var newEmailAddress: EmailAddress?
var body: some View {
if newEmailAddress?.verification?.status == .verified {
Text("Email added!")
}
if isVerifying {
TextField("Enter code", text: $code)
Button("Verify") {
Task { await verifyCode(code) }
}
} else {
TextField("Enter email address", text: $email)
Button("Continue") {
Task { await createEmail(email) }
}
}
}
}
extension AddEmailView {
func createEmail(_ email: String) async {
do {
guard let user = Clerk.shared.user else { return }
// Add an unverified email address to user
self.newEmailAddress = try await user.createEmailAddress(email)
guard let newEmailAddress = self.newEmailAddress else { return }
// Send the user an email with the verification code
try await newEmailAddress.prepareVerification(strategy: .emailCode)
// Set to true to display second form
// and capture the OTP code
isVerifying = true
} catch {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
dump(error)
}
}
func verifyCode(_ code: String) async {
do {
guard let newEmailAddress else { return }
// Verify that the code entered matches the code sent to the user
self.newEmailAddress = try await newEmailAddress.attemptVerification(strategy: .emailCode(code: code))
// If the status is not complete, check why. User may need to
// complete further steps.
dump(self.newEmailAddress?.verification?.status)
} catch {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
dump(error)
}
}
}
Feedback
Last updated on