Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Implement Email Verification in Supabase

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.

What you'll learn

  • How to enable email confirmation in the Supabase Dashboard
  • How the email verification flow works from signup to confirmation
  • How to handle the verification redirect callback in your app
  • How to check whether a user has verified their email address
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

2

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.

typescript
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6)
7
8async 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 })
16
17 if (error) {
18 console.error('Signup error:', error.message)
19 return
20 }
21
22 // data.session is null when email confirmation is required
23 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.

3

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.

typescript
1// For Next.js App Router: app/auth/callback/route.ts
2import { createClient } from '@/lib/supabase/server'
3import { NextResponse } from 'next/server'
4
5export 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)
9
10 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 }
17
18 // If there is no code or an error, redirect to an error page
19 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.

4

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.

typescript
1async function isEmailVerified(): Promise<boolean> {
2 const { data: { user }, error } = await supabase.auth.getUser()
3
4 if (error || !user) return false
5
6 return user.email_confirmed_at !== null
7}
8
9// Usage in a component
10const 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.

5

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.

typescript
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 state
5 }
6})

Expected result: The auth state listener detects when email verification is completed and triggers a UI update.

Complete working example

email-verification.ts
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6)
7
8// Sign up with email confirmation
9async 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 })
17
18 if (error) throw new Error(error.message)
19
20 return {
21 user: data.user,
22 needsConfirmation: data.user && !data.session,
23 }
24}
25
26// Check if current user has verified email
27async function isEmailVerified(): Promise<boolean> {
28 const { data: { user } } = await supabase.auth.getUser()
29 return user?.email_confirmed_at !== null && user?.email_confirmed_at !== undefined
30}
31
32// Resend confirmation email
33async 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 })
41
42 if (error) throw new Error(error.message)
43 return true
44}
45
46// Listen for verification completion
47function 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 subscription
56}
57
58// === Usage ===
59
60// 1. Sign up
61const result = await signUpWithVerification('user@example.com', 'password123')
62if (result.needsConfirmation) {
63 console.log('Check your inbox for a confirmation email.')
64}
65
66// 2. Listen for verification
67const sub = onEmailVerified(() => {
68 console.log('Email verified — redirecting to dashboard.')
69})
70
71// 3. Cleanup on unmount
72// 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.

ChatGPT Prompt

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.

Supabase Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.