This error occurs when attempting to authenticate or perform actions with a Supabase user whose phone number has not been verified via OTP. Users must complete SMS verification before proceeding.
The "phone_not_confirmed" error in Supabase Auth indicates that a user is trying to perform an action that requires a verified phone number, but they haven't completed the phone verification process yet. This is part of Supabase's security model to ensure that phone numbers are valid and belong to the user. When phone confirmation is enabled in your Supabase project settings, users who sign up or update their phone number receive a 6-digit OTP (one-time password) via SMS. This OTP must be verified using the verifyOtp() method before the phone number is considered confirmed. Until verification is complete, the user's phone number remains unconfirmed in the auth system, blocking certain operations. This error typically appears when you have "Enable phone confirmations" turned on in Authentication > Settings in your Supabase dashboard, but the user hasn't submitted their OTP code or the verification failed.
Check if phone confirmation is enabled in your Supabase dashboard:
1. Go to Authentication > Settings
2. Look for "Enable phone confirmations" toggle
3. If enabled, users MUST verify their phone number via OTP
If you don't need phone confirmation for your use case, you can disable it here. Otherwise, ensure your application implements the full verification flow.
After signing up a user with a phone number, you must collect and verify the OTP:
// Step 1: Sign up user with phone number
const { data, error } = await supabase.auth.signUp({
phone: '+1234567890',
password: 'secure-password-123'
});
if (error) {
console.error('Signup error:', error.message);
return;
}
// User receives SMS with 6-digit OTP
// Step 2: Present form to collect OTP from user
// Step 3: Verify the OTP (must be done within 60 seconds)
const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({
phone: '+1234567890',
token: '123456', // The 6-digit code from SMS
type: 'sms'
});
if (verifyError) {
console.error('Verification error:', verifyError.message);
return;
}
console.log('Phone confirmed!', verifyData);Make sure you're using type: 'sms' for initial phone verification. Use type: 'phone_change' only when updating an existing user's phone number.
OTP codes expire after 60 seconds. Implement a resend mechanism:
async function resendOtp(phone: string) {
const { error } = await supabase.auth.signInWithOtp({
phone: phone
});
if (error) {
console.error('Failed to resend OTP:', error.message);
return false;
}
console.log('New OTP sent to', phone);
return true;
}
// In your UI, provide a "Resend Code" button
// that calls this function after the initial 60 secondsEach new code remains valid for up to 5 minutes, and successive codes stay valid until expiry.
Ensure your SMS provider is properly configured in Supabase:
1. Navigate to Authentication > Settings > Phone Auth
2. Select your SMS provider (Twilio, MessageBird, Vonage, or TextLocal)
3. Enter your provider credentials (API keys, auth tokens, etc.)
4. Configure the sender phone number or ID
5. Save and test with a real phone number
Twilio-specific notes:
- Trial accounts can only send to verified phone numbers
- Add test numbers in Twilio console under Verified Caller IDs
- Check that your Twilio balance is sufficient
- Ensure Twilio Verify service is created if using that provider type
If SMS isn't being delivered, check your Supabase logs under Logs > Auth for delivery errors.
Verify you're calling verifyOtp() correctly:
Common mistake #1: Wrong type parameter
// WRONG - for new phone verification
await supabase.auth.verifyOtp({
phone: '+1234567890',
token: '123456',
type: 'phone_change' // ❌ This looks at phone_change field, not phone
});
// CORRECT - for new phone verification
await supabase.auth.verifyOtp({
phone: '+1234567890',
token: '123456',
type: 'sms' // ✅ Verifies the phone field
});Common mistake #2: Phone format mismatch
// Ensure phone number matches E.164 format
// WRONG: '5551234567'
// CORRECT: '+15551234567' (includes country code)Common mistake #3: Token as number instead of string
// WRONG
token: 123456
// CORRECT
token: '123456'Phone vs Email Confirmation Conflicts
If you have both email and phone confirmation enabled, be aware of a known issue where providing both email and phone during signup with email confirmation enabled but phone confirmation disabled can result in the phone number being stored as null. This is tracked in GitHub issue #30359.
Passwordless Phone Auth
For passwordless authentication flows using only phone OTP (no password):
// Send OTP for passwordless login
const { data } = await supabase.auth.signInWithOtp({
phone: '+1234567890'
});
// Verify OTP
const { data: session } = await supabase.auth.verifyOtp({
phone: '+1234567890',
token: '123456',
type: 'sms'
});Multi-Factor Authentication (MFA)
When using phone-based MFA, phone verification is required as part of the enrollment process. The user must verify their phone number before it can be used as a second factor.
Local Development Issues
SMS/WhatsApp auth methods may throw generic errors when using Twilio Verify in local development environments, even though the same configuration works in Supabase Cloud. This is a known limitation tracked in GitHub issue #19814.
Security Considerations
- Never disable phone confirmation in production unless you have an alternative verification method
- OTP codes should be entered by the user, not auto-filled programmatically (prevents automated attacks)
- Rate-limit OTP resend requests to prevent SMS bombing
- Consider implementing CAPTCHA before OTP requests to prevent abuse
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists