To implement email verification in Supabase, enable the Confirm email setting in Authentication > Providers > Email in the Dashboard. When a user signs up, Supabase sends a confirmation email with a verification link. Handle the redirect callback in your app to complete verification, then check the user's email_confirmed_at field to determine if they have verified. Configure a custom SMTP provider to ensure reliable delivery beyond the default 2 emails per hour limit.
Implementing Email Verification in Supabase Auth
Email verification ensures that users own the email address they sign up with. Supabase Auth has built-in support for email confirmation — when enabled, signUp returns a user object but no session until the user clicks the confirmation link. This tutorial covers the full flow from enabling the setting to handling the callback redirect.
Prerequisites
- A Supabase project with Authentication enabled
- The Supabase JS client installed (@supabase/supabase-js v2+)
- A frontend application with routing (React Router, Next.js, etc.)
- A custom SMTP provider configured for production email delivery
Step-by-step guide
Enable email confirmation in the Dashboard
Enable email confirmation in the Dashboard
Go to the Supabase Dashboard, navigate to Authentication > Providers, and click on the Email provider. Toggle on the Confirm email setting. When this is enabled, new signups will receive a confirmation email and their session will be null until they click the link. Also check the URL Configuration section under Authentication — set your Site URL to your production domain and add your app's callback URL to the Redirect URLs allowlist. The callback URL is where Supabase redirects users after they click the confirmation link.
Expected result: The Confirm email setting is enabled and your Site URL and Redirect URLs are configured.
Implement the signup flow with email confirmation
Implement the signup flow with email confirmation
When you call signUp with Confirm email enabled, the user receives a confirmation email but the session will be null. Your UI should display a message telling the user to check their inbox. The emailRedirectTo option in the signUp call specifies where the user is sent after clicking the confirmation link. This URL must be in the Redirect URLs allowlist in the Dashboard.
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient(4 process.env.NEXT_PUBLIC_SUPABASE_URL!,5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!6)78async function signUp(email: string, password: string) {9 const { data, error } = await supabase.auth.signUp({10 email,11 password,12 options: {13 emailRedirectTo: `${window.location.origin}/auth/callback`,14 },15 })1617 if (error) {18 console.error('Signup error:', error.message)19 return20 }2122 // data.session is null when email confirmation is required23 if (data.user && !data.session) {24 console.log('Confirmation email sent. Check your inbox.')25 }26}Expected result: The signup function sends a confirmation email and returns a user with a null session, prompting the user to check their inbox.
Handle the email confirmation callback
Handle the email confirmation callback
When the user clicks the confirmation link in their email, Supabase redirects them to your emailRedirectTo URL with authentication parameters in the URL hash. You need a callback route in your app that extracts these parameters and exchanges them for a session. For Next.js App Router, create a route handler. For React with React Router, create a callback page component. The key is to call supabase.auth.exchangeCodeForSession() if using PKCE flow, or let the client auto-detect the hash parameters.
1// For Next.js App Router: app/auth/callback/route.ts2import { createClient } from '@/lib/supabase/server'3import { NextResponse } from 'next/server'45export async function GET(request: Request) {6 const { searchParams } = new URL(request.url)7 const code = searchParams.get('code')8 const redirectTo = new URL('/', request.url)910 if (code) {11 const supabase = await createClient()12 const { error } = await supabase.auth.exchangeCodeForSession(code)13 if (!error) {14 return NextResponse.redirect(redirectTo)15 }16 }1718 // If there is no code or an error, redirect to an error page19 return NextResponse.redirect(new URL('/auth/error', request.url))20}Expected result: The callback route exchanges the confirmation code for a session and redirects the verified user to the main app.
Check if a user has verified their email
Check if a user has verified their email
After a user is authenticated, check the email_confirmed_at field on the user object to determine if they have verified their email. If this field is null, the user has not yet confirmed their email. Use this to conditionally show verification reminders, restrict access to certain features, or block certain actions until the email is confirmed.
1async function isEmailVerified(): Promise<boolean> {2 const { data: { user }, error } = await supabase.auth.getUser()34 if (error || !user) return false56 return user.email_confirmed_at !== null7}89// Usage in a component10const verified = await isEmailVerified()11if (!verified) {12 console.log('Please verify your email to continue.')13}Expected result: Your app can check whether the current user has a verified email and display appropriate UI or restrictions.
Listen for the email verification event
Listen for the email verification event
Set up an onAuthStateChange listener to detect when a user confirms their email. The SIGNED_IN event fires when the user completes verification (either by clicking the link or entering an OTP). Use this to update your UI in real-time without requiring a page refresh. The USER_UPDATED event may also fire when email confirmation status changes.
1supabase.auth.onAuthStateChange((event, session) => {2 if (event === 'SIGNED_IN' && session?.user.email_confirmed_at) {3 console.log('Email verified! User is now fully authenticated.')4 // Redirect to the main app or update UI state5 }6})Expected result: The auth state listener detects when email verification is completed and triggers a UI update.
Complete working example
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient(4 process.env.NEXT_PUBLIC_SUPABASE_URL!,5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!6)78// Sign up with email confirmation9async function signUpWithVerification(email: string, password: string) {10 const { data, error } = await supabase.auth.signUp({11 email,12 password,13 options: {14 emailRedirectTo: `${window.location.origin}/auth/callback`,15 },16 })1718 if (error) throw new Error(error.message)1920 return {21 user: data.user,22 needsConfirmation: data.user && !data.session,23 }24}2526// Check if current user has verified email27async function isEmailVerified(): Promise<boolean> {28 const { data: { user } } = await supabase.auth.getUser()29 return user?.email_confirmed_at !== null && user?.email_confirmed_at !== undefined30}3132// Resend confirmation email33async function resendConfirmation(email: string) {34 const { error } = await supabase.auth.resend({35 type: 'signup',36 email,37 options: {38 emailRedirectTo: `${window.location.origin}/auth/callback`,39 },40 })4142 if (error) throw new Error(error.message)43 return true44}4546// Listen for verification completion47function onEmailVerified(callback: () => void) {48 const { data: { subscription } } = supabase.auth.onAuthStateChange(49 (event, session) => {50 if (event === 'SIGNED_IN' && session?.user.email_confirmed_at) {51 callback()52 }53 }54 )55 return subscription56}5758// === Usage ===5960// 1. Sign up61const result = await signUpWithVerification('user@example.com', 'password123')62if (result.needsConfirmation) {63 console.log('Check your inbox for a confirmation email.')64}6566// 2. Listen for verification67const sub = onEmailVerified(() => {68 console.log('Email verified — redirecting to dashboard.')69})7071// 3. Cleanup on unmount72// sub.unsubscribe()Common mistakes when implementing Email Verification in Supabase
Why it's a problem: Not adding the callback URL to the Redirect URLs allowlist in the Dashboard
How to avoid: Go to Authentication > URL Configuration and add your callback URL (e.g., https://yourapp.com/auth/callback) to the Redirect URLs list. Supabase blocks redirects to URLs not in the allowlist.
Why it's a problem: Assuming data.session is always available after signUp when Confirm email is enabled
How to avoid: When Confirm email is enabled, signUp returns a user but no session. Check for data.session === null and show a 'check your inbox' message instead of trying to access the session.
Why it's a problem: Using the default SMTP in production and hitting the 2 emails per hour rate limit
How to avoid: Configure a custom SMTP provider (Resend, SendGrid, Postmark) in Project Settings > Authentication > SMTP Settings before launching. The default 2 per hour limit is for development only.
Why it's a problem: Not handling the case where the confirmation link has expired
How to avoid: Confirmation links expire after 24 hours by default. If the user clicks an expired link, show an error message with a button to resend the confirmation email using supabase.auth.resend().
Best practices
- Always configure a custom SMTP provider before going to production
- Set a clear emailRedirectTo URL that handles the authentication callback
- Show a helpful 'check your inbox' message after signup instead of a blank screen
- Provide a 'resend confirmation' button for users who do not receive the initial email
- Use getUser() on the server side to verify email_confirmed_at — never trust client-side session data for authorization
- Set up onAuthStateChange early in your app to catch verification events from redirects
- Add the confirmation callback URL to both development and production Redirect URLs lists
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to implement email verification in my Supabase app. Walk me through enabling confirm email in the Dashboard, handling the signup flow where session is null until confirmation, creating the callback route, and checking if a user's email is verified. Include a resend confirmation feature.
Implement a complete email verification flow for a Supabase Auth project: signUp with emailRedirectTo, a callback route handler that exchanges the code for a session, a function to check email_confirmed_at, and a resend confirmation function. Include an onAuthStateChange listener for real-time verification detection.
Frequently asked questions
How long is the email confirmation link valid?
By default, Supabase email confirmation links expire after 24 hours. If the link expires, the user needs to request a new confirmation email using supabase.auth.resend().
What happens if a user signs up but never confirms their email?
The user record exists in auth.users but their email_confirmed_at field is null. They cannot sign in until they confirm their email. You can clean up unconfirmed users periodically using an admin script.
Can I disable email confirmation for development?
Yes, go to Authentication > Providers > Email and toggle off Confirm email. This makes signUp return an active session immediately without sending a confirmation email. Re-enable it before deploying to production.
Why is my confirmation email not being received?
Check three things: (1) the default SMTP has a 2 emails per hour limit — configure a custom SMTP provider; (2) the email may be in the spam folder; (3) verify the user's email address is correct in the auth.users table.
Can I use a 6-digit OTP instead of a confirmation link?
Yes, Supabase supports OTP-based confirmation. Use the {{ .Token }} template variable in your email template to include the 6-digit code, and verify it client-side with supabase.auth.verifyOtp({ email, token, type: 'signup' }).
Does email verification work with OAuth providers?
OAuth providers like Google and GitHub verify email addresses as part of their own flow. When a user signs in with OAuth, Supabase trusts the provider's email verification and sets email_confirmed_at automatically.
Can RapidDev help implement a custom email verification flow for my Supabase project?
Yes, RapidDev can implement the full email verification flow including custom email templates, SMTP configuration, callback handling, and user experience optimizations like resend functionality and verification status indicators.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation