Supabase Register with Email OTP Verification

Good day, I have an Expo React Native mobile app that uses Supabase for the Auth and Database. I wish to make my register to have two steps, first fills the registration form and then redirect to the verify OTP page where I can fill the OTP numbers. However, I can't find any guideline for Supabase to register with Email OTP verification, all it uses was login with email OTP only or magic link the passwordless auth. All I found is that the Supabase email OTP only works to sign in not for sign up. I'm aware that supabase has email confirmation link but I was hoping I can use the verify OTP instead. Here's the progress I have encounter so far: - I have asked A.I how to implement Register with email OTP verification, some A.I did mentions I required to turn on the enabled phone provider for the OTP registration. Is this true tho? Why would I need phone provider for the auth, while I'm using the email provider. - Whenever I try to test the register, just before verifying the OTP the user registration already created at Supabase Auth. Even when I'm already using the await, it just won't work or I still need to modify my code. But this problem make me thinks it just not possible to register with OTP. I was hoping the professional here has my answer, thank you everyone for having the time to read my question!
9 Replies
OakRatos
OakRatos6d ago
@dev_edbert supabase doesn’t support email OTP for sign-up it only works for login. For registration, you must use the email confirmation link or build a custom OTP flow yourself.
silentworks
silentworks6d ago
The OTP is used for both sign in and sign ups, you just use signInWithOtp and it will send an email for signing up. The default template however doesn't contain the 6 digit OTP code, you have to add it using the {{ .Token }} variable in the email template.
dev_edbert
dev_edbertOP6d ago
But how do I prevent it didnt register the user to my supabase auth before the user verify the OTP correctly Like just after redirect to the verify OTP, I refresh the auth and the user already registered before I enter the OTP
silentworks
silentworks6d ago
How would it know who to verify if they aren’t registered? Likely your email link is verifying the account. If you want to do it manually then you should setup the url you want the user to go to inside of your email template
dev_edbert
dev_edbertOP6d ago
Hmm.. gor the OTP I actually already turn off the confirm email sign up,, Thats the question, I already set it await.. hm maybe I need to work on the logic Wait let me share my repo Here's how I handle the register:
const handleRegister = async () => {
if (!email || !password || !username) {
Alert.alert('Error', 'Please fill in all required fields');
return;
}

if (password !== confirmPassword) {
Alert.alert('Error', 'Passwords do not match');
return;
}

if (password.length < 6) {
Alert.alert('Error', 'Password must be at least 6 characters long');
return;
}

const passwordValidationErrors = validatePassword(password);
if (passwordValidationErrors.length > 0) {
Alert.alert(
'Invalid Password',
`Password must contain: ${passwordValidationErrors.join(', ')}`
);
return;
}

setIsLoading(true);

try {
// First, check if invitation code exists (if provided)
if (invitationCode) {
const { data: inviteData, error: inviteError } = await supabase
.from('invitation_codes')
.select('user_id')
.eq('code', invitationCode)
.eq('is_active', true)
.single();

if (inviteError) {
throw new Error('Invalid invitation code');
}
}

// Sign up the user (this will send OTP)
const { error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
username: username,
full_name: username,
}
},
});

if (error) throw error;

// Navigate to OTP verification screen
router.push({
pathname: '/verify-otp',
params: {
email,
password,
username,
invitationCode: invitationCode || '',
},
});

} catch (error: any) {
Alert.alert('Registration Failed', error.message);
} finally {
setIsLoading(false);
}
};
const handleRegister = async () => {
if (!email || !password || !username) {
Alert.alert('Error', 'Please fill in all required fields');
return;
}

if (password !== confirmPassword) {
Alert.alert('Error', 'Passwords do not match');
return;
}

if (password.length < 6) {
Alert.alert('Error', 'Password must be at least 6 characters long');
return;
}

const passwordValidationErrors = validatePassword(password);
if (passwordValidationErrors.length > 0) {
Alert.alert(
'Invalid Password',
`Password must contain: ${passwordValidationErrors.join(', ')}`
);
return;
}

setIsLoading(true);

try {
// First, check if invitation code exists (if provided)
if (invitationCode) {
const { data: inviteData, error: inviteError } = await supabase
.from('invitation_codes')
.select('user_id')
.eq('code', invitationCode)
.eq('is_active', true)
.single();

if (inviteError) {
throw new Error('Invalid invitation code');
}
}

// Sign up the user (this will send OTP)
const { error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
username: username,
full_name: username,
}
},
});

if (error) throw error;

// Navigate to OTP verification screen
router.push({
pathname: '/verify-otp',
params: {
email,
password,
username,
invitationCode: invitationCode || '',
},
});

} catch (error: any) {
Alert.alert('Registration Failed', error.message);
} finally {
setIsLoading(false);
}
};
And here's how I verify the OTP:
const verifyOTP = async (otpCode: string) => {
if (otpCode.length !== 6) {
Alert.alert('Invalid OTP', 'Please enter all 6 digits');
return;
}

setIsVerifying(true);

try {
// Verify the OTP
const { data, error } = await supabase.auth.verifyOtp({
email: email!,
token: otpCode,
type: 'signup',
});

if (error) throw error;

if (data.user) {
// Now create the user profile with additional data
const profileUpdates: any = {
username: username,
full_name: username,
};

// If invitation code was provided, find the inviter
if (invitationCode) {
const { data: inviteData, error: inviteError } = await supabase
.from('invitation_codes')
.select('user_id')
.eq('code', invitationCode)
.eq('is_active', true)
.single();

if (!inviteError && inviteData) {
profileUpdates.inviter_id = inviteData.user_id;
}
}

// Update the profile
await supabase
.from('profiles')
.update(profileUpdates)
.eq('id', data.user.id);

Alert.alert(
'Account Created Successfully! 🎉',
'Your email has been verified and your account is ready to use.',
[
{ text: 'Continue', onPress: () => router.replace('/(tabs)') }
]
);
}
} catch (error: any) {
Alert.alert('Verification Failed', error.message);
// Clear OTP on error
setOtp(['', '', '', '', '', '']);
inputRefs.current[0]?.focus();
} finally {
setIsVerifying(false);
}
};
const verifyOTP = async (otpCode: string) => {
if (otpCode.length !== 6) {
Alert.alert('Invalid OTP', 'Please enter all 6 digits');
return;
}

setIsVerifying(true);

try {
// Verify the OTP
const { data, error } = await supabase.auth.verifyOtp({
email: email!,
token: otpCode,
type: 'signup',
});

if (error) throw error;

if (data.user) {
// Now create the user profile with additional data
const profileUpdates: any = {
username: username,
full_name: username,
};

// If invitation code was provided, find the inviter
if (invitationCode) {
const { data: inviteData, error: inviteError } = await supabase
.from('invitation_codes')
.select('user_id')
.eq('code', invitationCode)
.eq('is_active', true)
.single();

if (!inviteError && inviteData) {
profileUpdates.inviter_id = inviteData.user_id;
}
}

// Update the profile
await supabase
.from('profiles')
.update(profileUpdates)
.eq('id', data.user.id);

Alert.alert(
'Account Created Successfully! 🎉',
'Your email has been verified and your account is ready to use.',
[
{ text: 'Continue', onPress: () => router.replace('/(tabs)') }
]
);
}
} catch (error: any) {
Alert.alert('Verification Failed', error.message);
// Clear OTP on error
setOtp(['', '', '', '', '', '']);
inputRefs.current[0]?.focus();
} finally {
setIsVerifying(false);
}
};
But the official docs did show that there is type 'signup' for the verifyOtp method with email
dev_edbert
dev_edbertOP6d ago
JavaScript: Send a password reset request | Supabase Docs
Supabase API reference for JavaScript: Send a password reset request
No description
silentworks
silentworks5d ago
But what does the invite email content look like?
dev_edbert
dev_edbertOP4d ago
oh this one is for my affiliate system
silentworks
silentworks4d ago
I’m talking about the content of the email template in the Supabase dashboard. Please show what that looks like.

Did you find this page helpful?