This error occurs when attempting to sign up a user with an email address already registered in Supabase Auth. The behavior depends on your email confirmation settings.
The "email_exists" error is thrown by Supabase Auth when the email provided during sign-up is already associated with an existing user account in the auth.users table. Supabase enforces email uniqueness to maintain data integrity and prevent duplicate accounts. The error's behavior depends on your project's email confirmation settings. When email confirmation is disabled, Supabase returns a clear "email_exists" error message. However, when email confirmation is enabled, Supabase returns an obfuscated/fake user object instead of a direct error message. This is a deliberate security design decision to prevent account enumeration attacks, where attackers could determine which email addresses are registered by attempting signups. This security-focused approach means you need to detect existing users differently depending on your authentication configuration. The workarounds include checking the identities array, using Admin Auth for server-side checks, or implementing custom email existence verification.
Navigate to your Supabase Dashboard → Authentication → Settings and check the "Confirm email" option.
If disabled: Supabase will return a clear "email_exists" error that you can catch and handle directly.
If enabled: You'll receive an obfuscated/fake user response instead of an error, and need to implement the workarounds below.
When email confirmation is enabled, check the user.identities array in the signup response:
const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'password123'
});
if (error?.message === 'email_exists') {
// Email confirmation is disabled - direct error
console.log('Email already registered - redirect to login');
return;
}
if (data?.user) {
// Email confirmation enabled - check identities array
if (data.user.identities && data.user.identities.length === 0) {
console.log('Email already exists - redirect to login');
return;
}
// New user created successfully
console.log('Signup successful - check email for verification');
}An empty identities array indicates the email already exists.
If you need definitive error messages, use the Admin Auth API with your service role key on the server side:
// Server-side only - never expose service_role key to client
import { createClient } from '@supabase/supabase-js';
const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false
}
}
);
const { data, error } = await supabaseAdmin.auth.admin.createUser({
email: '[email protected]',
password: 'password123',
email_confirm: true
});
if (error?.message?.includes('email already exists')) {
// Email is registered - handle accordingly
console.log('User already has account with this email');
return;
}
// User created successfully
console.log('New user created');Admin Auth returns clear errors even when email confirmation is enabled.
Create a PostgreSQL function in your Supabase SQL Editor to check if an email exists:
CREATE OR REPLACE FUNCTION public.check_email_exists(email_to_check TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM auth.users WHERE email = email_to_check
);
END;
$$;
-- Grant execute permission to authenticated users
GRANT EXECUTE ON FUNCTION public.check_email_exists(TEXT) TO authenticated;Then call it from your client before signup:
const { data: emailExists, error } = await supabase.rpc('check_email_exists', {
email_to_check: '[email protected]'
});
if (emailExists) {
console.log('Email already registered - show login option');
return;
}
// Email is free - proceed with signup
const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'password123'
});Update your signup UI to gracefully handle email existence errors:
async function handleSignup(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password
});
// Handle explicit email_exists error
if (error?.message === 'email_exists') {
showMessage('This email is already registered. Please log in or reset your password.');
redirectToLogin();
return;
}
// Handle other errors
if (error) {
showError(error.message);
return;
}
// Check for obfuscated response (email confirmation enabled)
if (data?.user?.identities?.length === 0) {
showMessage('An account with this email already exists. Please log in.');
redirectToLogin();
return;
}
// Successful signup
showMessage('Account created! Check your email to verify.');
}Always provide clear guidance to users to either log in or reset their password.
Security Implications of Email Confirmation Settings
Supabase's email confirmation behavior is designed to balance usability with security. When enabled, it prevents account enumeration attacks by not revealing whether an email is registered. When disabled, it provides better UX with clear errors but may leak information to attackers.
OAuth Integration Considerations
If your app uses OAuth providers (Google, GitHub, etc.), users who sign up with OAuth using the same email cannot later create a password-protected account with that email. Supabase automatically links OAuth identities to the same email address. Attempting to sign up with password will return an empty identities array (when confirmation is enabled) or email_exists error (when disabled).
Rate Limiting Your Email Checks
If you implement custom RPC functions or server-side checks to detect email existence, add rate limiting to prevent email enumeration attacks. An attacker could hammer your endpoint to discover registered emails.
Migration Path for Existing Users
When switching email confirmation settings, existing unconfirmed signups may behave unexpectedly. Consider reviewing your auth.users table and cleaning up stale unconfirmed accounts before changing settings.
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