useSignUp() hook
useSignUp() is the stateful sign-up hook behind <SignUpForm />. It creates the sign-up attempt, keeps the latest in-progress signup attempt in memory, prepares the verification step that comes next, completes OTP verification, and validates deployment invitations before the user submits the form.Usage
The following example shows a basic usage of useSignUp().
export default function EmailSignUp() { const { loading, signUp, signupAttempt } = useSignUp(); async function submit() { if (loading) { return; } await signUp.create({ email: 'jane@example.com', password: 'CorrectHorseBatteryStaple123!', }); } return ( <div> <button onClick={submit}>Create account</button> {signupAttempt ? <p>Current step: {signupAttempt.current_step}</p> : null} </div> );}Return value
The hook returns the following fields and methods.
›loading?: boolean | undefined;
loading?: boolean | undefined;Stays `true` until `useClient()` is ready. While loading is true, the hook does not expose a usable `signUp` object.
›signUp: { create; prepareVerification; completeVerification; validateDeploymentInvitation };
signUp: { create; prepareVerification; completeVerification; validateDeploymentInvitation };The stateful sign-up controller used by the embedded sign-up form.
›create?: (params: SignUpParams) => Promise<ApiResult<unknown>> | undefined;
create?: (params: SignUpParams) => Promise<ApiResult<unknown>> | undefined;Starts a sign-up attempt with the submitted user fields. When the response includes `session.signup_attempts`, the hook stores the latest one in `signupAttempt`.
›prepareVerification?: (params: { strategy: 'email_otp'; redirectUri?: string } | { strategy: 'phone_otp'; lastDigits?: string }) => Promise<ApiResult<{ otp_sent?: boolean; masked_phone?: string; masked_email?: string; verification_method?: string }>> | undefined;
prepareVerification?: (params: { strategy: 'email_otp'; redirectUri?: string } | { strategy: 'phone_otp'; lastDigits?: string }) => Promise<ApiResult<{ otp_sent?: boolean; masked_phone?: string; masked_email?: string; verification_method?: string }>> | undefined;Prepares the verification step for the current sign-up attempt. This is what `SignUpForm` uses after `create()` has moved the flow into email or phone verification.
›completeVerification?: (verificationCode: string) => Promise<ApiResult<Session>> | undefined;
completeVerification?: (verificationCode: string) => Promise<ApiResult<Session>> | undefined;Submits an OTP for the current sign-up attempt and updates `signupAttempt` with the latest attempt state when the server returns one.
›validateDeploymentInvitation?: (token: string) => Promise<{ valid: boolean; first_name?: string; last_name?: string; email?: string; message?: string; error_code?: string }> | undefined;
validateDeploymentInvitation?: (token: string) => Promise<{ valid: boolean; first_name?: string; last_name?: string; email?: string; message?: string; error_code?: string }> | undefined;Validates an invitation token before the user submits the form and returns any prefilled invitation data that should shape the form.
›valid?: boolean | undefined;
valid?: boolean | undefined;Whether the invitation token is valid.
›first_name?: string | undefined | undefined;
first_name?: string | undefined | undefined;Invited first name when the invitation contains one.
›last_name?: string | undefined | undefined;
last_name?: string | undefined | undefined;Invited last name when the invitation contains one.
›email?: string | undefined | undefined;
email?: string | undefined | undefined;Invited email address when the invitation contains one.
›message?: string | undefined | undefined;
message?: string | undefined | undefined;Validation message for invalid or failed invitation checks.
›error_code?: string | undefined | undefined;
error_code?: string | undefined | undefined;Error code describing why invitation validation failed.
›signupAttempt: SignupAttempt | null;
signupAttempt: SignupAttempt | null;The latest in-progress sign-up attempt captured by the hook. Embedded sign-up screens use this to decide which verification step comes next.
›id?: string | undefined;
id?: string | undefined;Stable identifier for the current sign-up attempt.
›first_name?: string | undefined;
first_name?: string | undefined;Collected first name for the attempt.
›last_name?: string | undefined;
last_name?: string | undefined;Collected last name for the attempt.
›email?: string | undefined;
email?: string | undefined;Collected email for the attempt.
›username?: string | undefined;
username?: string | undefined;Collected username for the attempt.
›phone_number?: string | undefined;
phone_number?: string | undefined;Collected phone number for the attempt.
›required_fields?: string[] | undefined;
required_fields?: string[] | undefined;Fields the deployment still requires for the attempt.
›missing_fields?: string[] | undefined;
missing_fields?: string[] | undefined;Fields that are still missing from the attempt.
›current_step?: 'verify_email' | 'verify_phone' | 'verify_authenticator' | undefined;
current_step?: 'verify_email' | 'verify_phone' | 'verify_authenticator' | undefined;Current sign-up verification step.
›remaining_steps?: ('verify_email' | 'verify_phone' | 'verify_authenticator')[] | undefined;
remaining_steps?: ('verify_email' | 'verify_phone' | 'verify_authenticator')[] | undefined;Verification steps that still remain after the current one.
›completed?: boolean | undefined;
completed?: boolean | undefined;Whether the sign-up attempt has fully finished.
›discardSignupAttempt: () => void;
discardSignupAttempt: () => void;Clears the in-memory sign-up attempt and returns the hook to its initial local state.
How it works
The hook keeps
signupAttempt in local React state. The embedded sign-up flow uses that attempt to move from the initial form into OTP verification without rebuilding the whole flow from scratch.Unlike
useSignIn(), there is no strategy builder matrix here. create() takes the form payload directly and the deployment decides which fields and verification steps are required next.The built-in sign-up form uses
validateDeploymentInvitation() before submission when an invite_token query param is present. That validation can prefill the invited user fields and can also block the flow when the token is invalid.The hook itself does not enforce restricted sign-up or waitlist redirects. Those branches are handled by higher-level UI such as <SignUpForm />, which reads deployment restrictions and decides whether to show the form, redirect to the waitlist, or show a restricted message.
After a sign-up attempt is created, higher-level UI decides when to call
prepareVerification(). In the stock flow, that happens automatically when the attempt moves into verify_email or verify_phone.When to use it
Examples
Create a sign-up attempt
export default function BasicSignUp() { const { loading, signUp } = useSignUp(); async function submit() { if (loading) { return; } await signUp.create({ first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com', password: 'CorrectHorseBatteryStaple123!', }); } return <button onClick={submit}>Create account</button>;}Validate an invitation token before rendering the form
export default function InvitationAwareSignUp() { const { loading, signUp } = useSignUp(); async function validate() { if (loading) { return; } const invitation = await signUp.validateDeploymentInvitation('invite-token'); if (invitation.valid) { console.log(invitation.email); } } return <button onClick={validate}>Validate invitation</button>;}Prepare and complete email verification
export default function EmailVerification() { const { loading, signUp, signupAttempt } = useSignUp(); async function start() { if (loading) { return; } await signUp.create({ email: 'jane@example.com', password: 'CorrectHorseBatteryStaple123!', }); await signUp.prepareVerification({ strategy: 'email_otp', redirectUri: window.location.href, }); } async function verify() { await signUp.completeVerification('123456'); } return ( <div> <button onClick={start}>Send OTP</button> <button onClick={verify} disabled={!signupAttempt}> Verify OTP </button> </div> );}Reset the local sign-up flow
export default function RestartSignUp() { const { signupAttempt, discardSignupAttempt } = useSignUp(); if (!signupAttempt) { return null; } return <button onClick={discardSignupAttempt}>Start over</button>;}