Custom Flows Add phone
Build a custom flow for adding a phone number to a user's account
Users are able to add multiple phone numbers to their account.
Every user has a User
object that represents their account. The User
object has a phoneNumbers
property that contains all the phone numbers associated with the user.
The following example demonstrates how to build a custom user interface that allows users to add a phone number to their account. The example:
Uses the useUser()
hook to get the User
object.
Uses the User.createPhoneNumber()
method to add the phone number to the user's account. A new PhoneNumber
object is created and stored in User.phoneNumbers
.
Uses the prepareVerification()
method on the newly created PhoneNumber
object to send a verification code to the user.
Uses the attemptVerification()
method on the same PhoneNumber
object with the verification code provided by the user to verify the phone number.
app /account /add-phone /page.tsx 'use client'
import * as React from 'react'
import { useUser } from '@clerk/nextjs'
import { PhoneNumberResource } from '@clerk/types'
export default function Page () {
const { isLoaded , user } = useUser ()
const [ phone , setPhone ] = React .useState ( '' )
const [ code , setCode ] = React .useState ( '' )
const [ isVerifying , setIsVerifying ] = React .useState ( false )
const [ successful , setSuccessful ] = React .useState ( false )
const [ phoneObj , setPhoneObj ] = React .useState < PhoneNumberResource | 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 phone number
const handleSubmit = async (e : React . FormEvent ) => {
e .preventDefault ()
try {
// Add unverified phone number to user
const res = await user .createPhoneNumber ({ phoneNumber : phone })
// Reload user to get updated User object
await user .reload ()
// Create a reference to the new phone number to use related methods
const phoneNumber = user . phoneNumbers .find ((a) => a .id === res .id)
setPhoneObj (phoneNumber)
// Send the user an SMS with the verification code
phoneNumber ?.prepareVerification ()
// 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 provided code matches the code sent to the user
const phoneVerifyAttempt = await phoneObj ?.attemptVerification ({ code })
if ( phoneVerifyAttempt ?. 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 (phoneVerifyAttempt , null , 2 ))
}
} catch (err) {
console .error ( JSON .stringify (err , null , 2 ))
}
}
// Display a success message if the phone number was added successfully
if (successful) {
return (
<>
< h1 >Phone added</ h1 >
</>
)
}
// Display the verification form to capture the OTP code
if (isVerifying) {
return (
<>
< h1 >Verify phone</ 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 phone number
return (
<>
< h1 >Add phone</ h1 >
< div >
< form onSubmit = {(e) => handleSubmit (e)}>
< div >
< label htmlFor = "phone" >Enter phone number</ label >
< input
onChange = {(e) => setPhone ( e . target .value)}
id = "phone"
name = "phone"
type = "phone"
value = {phone}
/>
</ div >
< div >
< button type = "submit" >Continue</ button >
</ div >
</ form >
</ div >
</>
)
}
AddPhoneView.swift import SwiftUI
import ClerkSDK
struct AddPhoneView : View {
@State private var phone = ""
@State private var code = ""
@State private var isVerifying = false
// Create a reference to the phone number that we'll be creating
@State private var newPhoneNumber: PhoneNumber ?
var body: some View {
if newPhoneNumber ? .verification ? .status == .verified {
Text ( "Phone added!" )
}
if isVerifying {
TextField ( "Enter code" , text : $code )
Button ( "Verify" ) {
Task { await verifyCode ( code ) }
}
} else {
TextField ( "Enter phone number" , text : $phone )
Button ( "Continue" ) {
Task { await createPhone ( phone ) }
}
}
}
}
extension AddPhoneView {
func createPhone ( _ phone : String ) async {
do {
guard let user = Clerk.shared.user else { return }
// Add an unverified phone number to user
self.newPhoneNumber = try await user. createPhoneNumber ( phone )
guard let newphoneNumber = self.newPhoneNumber else { return }
// Send the user an sms message with the verification code
try await newphoneNumber. prepareVerification ()
// 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 newPhoneNumber else { return }
// Verify that the code entered matches the code sent to the user
self.newPhoneNumber = try await newPhoneNumber. attemptVerification ( code : code )
// If the status is not complete, check why. User may need to
// complete further steps.
dump ( self.newPhoneNumber ? .verification ? .status )
} catch {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
dump ( error )
}
}
}
Last updated on Jan 16, 2025