Supabase Auth does not enforce strict email format validation beyond basic checks. To prevent malformed emails, add client-side validation with a regex pattern before calling signUp, and add server-side validation with a PostgreSQL trigger or Edge Function that checks email format on the auth.users table. This ensures only properly formatted email addresses can register, reducing bounce rates and invalid accounts.
Adding Email Format Validation to Supabase Auth
Supabase Auth accepts any string that has an @ symbol as an email address. This means addresses like 'test@' or 'user@.com' can pass through. For production apps, you need stricter validation to ensure email deliverability. This tutorial covers three layers of defense: client-side validation in your signup form, a database trigger that rejects malformed emails before they are stored, and an optional Edge Function for advanced validation like disposable email detection.
Prerequisites
- A Supabase project with email/password auth enabled
- Access to the SQL Editor in the Supabase Dashboard
- @supabase/supabase-js installed in your frontend project
- Basic understanding of regular expressions
Step-by-step guide
Add client-side email validation before signUp
Add client-side email validation before signUp
The first layer of defense is validating the email format in your frontend before making the API call. Use a regex pattern that checks for a valid email structure: local part, @ symbol, domain, and TLD. Also use the HTML5 type='email' attribute on input fields for basic browser validation. Client-side validation provides instant feedback and prevents unnecessary API calls, but it can be bypassed, so it must be paired with server-side validation.
1// Email validation utility2const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/34function isValidEmail(email: string): boolean {5 if (!email || email.length > 254) return false6 return EMAIL_REGEX.test(email)7}89// Usage in signup handler10async function handleSignup(email: string, password: string) {11 if (!isValidEmail(email)) {12 return { error: 'Please enter a valid email address.' }13 }1415 const { data, error } = await supabase.auth.signUp({16 email: email.toLowerCase().trim(),17 password,18 })1920 return { data, error }21}Expected result: Malformed email addresses are rejected instantly in the UI before making any API call to Supabase.
Create a PostgreSQL function for server-side email validation
Create a PostgreSQL function for server-side email validation
Client-side validation can be bypassed by calling the API directly. Add a database trigger that validates email format on every INSERT into auth.users. Create a PL/pgSQL function that checks the email against a regex pattern and raises an exception if it does not match. This is the strongest form of validation because it runs at the database level regardless of how the request arrives.
1-- Create the validation function2create or replace function public.validate_email_format()3returns trigger4language plpgsql5security definer set search_path = ''6as $$7begin8 -- Check email format with regex9 if new.email !~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' then10 raise exception 'Invalid email format: %', new.email;11 end if;1213 -- Check email length (RFC 5321 limit)14 if length(new.email) > 254 then15 raise exception 'Email address exceeds maximum length of 254 characters';16 end if;1718 return new;19end;20$$;2122-- Create the trigger on auth.users23create trigger validate_email_before_insert24 before insert on auth.users25 for each row26 execute function public.validate_email_format();Expected result: Any attempt to insert a user with a malformed email raises a database exception, which Supabase returns as an error to the client.
Handle validation errors in the frontend
Handle validation errors in the frontend
When the database trigger rejects an email, Supabase returns an error with the exception message. Catch this error in your signup handler and display a user-friendly message. Map specific error patterns to helpful messages so users understand what went wrong and how to fix it.
1async function handleSignup(email: string, password: string) {2 // Client-side validation first3 if (!isValidEmail(email)) {4 return { error: 'Please enter a valid email address (e.g., name@example.com).' }5 }67 const { data, error } = await supabase.auth.signUp({8 email: email.toLowerCase().trim(),9 password,10 })1112 if (error) {13 // Handle server-side validation errors14 if (error.message.includes('Invalid email format')) {15 return { error: 'The email address format is not valid. Please check and try again.' }16 }17 if (error.message.includes('already registered')) {18 return { error: 'An account with this email already exists. Try logging in instead.' }19 }20 return { error: 'Signup failed. Please try again later.' }21 }2223 return { data, error: null }24}Expected result: Users see a clear, friendly error message when their email format is rejected by either the client-side or server-side validation.
Add an Edge Function for advanced validation (optional)
Add an Edge Function for advanced validation (optional)
For advanced validation like blocking disposable email providers, checking MX records, or enforcing domain allowlists, create an Edge Function that acts as a signup proxy. The function validates the email, then calls supabase.auth.admin.createUser() with the service role key if validation passes. This approach gives you full control over the signup flow while keeping the validation logic server-side.
1// supabase/functions/validated-signup/index.ts2import { createClient } from 'npm:@supabase/supabase-js@2'3import { corsHeaders } from '../_shared/cors.ts'45const BLOCKED_DOMAINS = ['tempmail.com', 'throwaway.email', 'mailinator.com']6const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/78Deno.serve(async (req) => {9 if (req.method === 'OPTIONS') {10 return new Response('ok', { headers: corsHeaders })11 }1213 const { email, password } = await req.json()14 const normalizedEmail = email.toLowerCase().trim()1516 // Validate format17 if (!EMAIL_REGEX.test(normalizedEmail)) {18 return new Response(19 JSON.stringify({ error: 'Invalid email format' }),20 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }21 )22 }2324 // Check for disposable email domains25 const domain = normalizedEmail.split('@')[1]26 if (BLOCKED_DOMAINS.includes(domain)) {27 return new Response(28 JSON.stringify({ error: 'Disposable email addresses are not allowed' }),29 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }30 )31 }3233 // Proceed with signup using the regular client34 const supabase = createClient(35 Deno.env.get('SUPABASE_URL')!,36 Deno.env.get('SUPABASE_ANON_KEY')!37 )3839 const { data, error } = await supabase.auth.signUp({40 email: normalizedEmail,41 password,42 })4344 if (error) {45 return new Response(46 JSON.stringify({ error: error.message }),47 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }48 )49 }5051 return new Response(52 JSON.stringify({ data }),53 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }54 )55})Expected result: The Edge Function validates email format and blocks disposable providers before creating the account. Invalid signups are rejected with clear error messages.
Test the validation with edge cases
Test the validation with edge cases
Test your validation layers with emails that should pass and emails that should fail. Include edge cases like emails with special characters in the local part, very long addresses, addresses with subdomains, and common typos. Verify that both the client-side validation and the database trigger produce appropriate error messages.
1// Test cases for email validation2const testEmails = [3 // Should PASS4 { email: 'user@example.com', expected: true },5 { email: 'user.name+tag@domain.co.uk', expected: true },6 { email: 'user@sub.domain.com', expected: true },78 // Should FAIL9 { email: 'user@', expected: false },10 { email: '@domain.com', expected: false },11 { email: 'user@.com', expected: false },12 { email: 'user@domain', expected: false },13 { email: 'user domain@test.com', expected: false },14 { email: '', expected: false },15 { email: 'a'.repeat(255) + '@test.com', expected: false },16]1718for (const { email, expected } of testEmails) {19 const result = isValidEmail(email)20 console.log(`${email}: ${result === expected ? 'PASS' : 'FAIL'}`)21}Expected result: All valid emails pass validation and all malformed emails are rejected at both the client and server level.
Complete working example
1-- ============================================2-- Email Format Validation for Supabase Auth3-- Run in Supabase SQL Editor4-- ============================================56-- Create the email validation function7create or replace function public.validate_email_format()8returns trigger9language plpgsql10security definer set search_path = ''11as $$12declare13 email_pattern text := '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';14begin15 -- Normalize the email16 new.email := lower(trim(new.email));1718 -- Check email is not empty19 if new.email is null or new.email = '' then20 raise exception 'Email address is required';21 end if;2223 -- Check email length (RFC 5321)24 if length(new.email) > 254 then25 raise exception 'Email address exceeds maximum length';26 end if;2728 -- Check format with regex29 if new.email !~ email_pattern then30 raise exception 'Invalid email format';31 end if;3233 -- Check local part length (before @)34 if length(split_part(new.email, '@', 1)) > 64 then35 raise exception 'Email local part exceeds maximum length';36 end if;3738 return new;39end;40$$;4142-- Create trigger on auth.users for new signups43create trigger validate_email_before_insert44 before insert on auth.users45 for each row46 execute function public.validate_email_format();4748-- Optional: Also validate on email updates49create trigger validate_email_before_update50 before update of email on auth.users51 for each row52 when (old.email is distinct from new.email)53 execute function public.validate_email_format();Common mistakes when validating Email Format in Supabase Auth
Why it's a problem: Relying only on client-side validation, which can be bypassed by calling the API directly
How to avoid: Always pair client-side validation with server-side enforcement via a database trigger or Edge Function. Client-side validation is for UX; server-side validation is for security.
Why it's a problem: Using an overly strict regex that rejects valid email addresses with + signs, dots, or subdomains
How to avoid: Test your regex with valid edge cases like user.name+tag@sub.domain.co.uk. The pattern ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ covers most valid formats.
Why it's a problem: Not normalizing email addresses, allowing duplicate accounts with different casing or whitespace
How to avoid: Call .toLowerCase().trim() on the email in your client code and in the database trigger. This prevents 'User@Example.com' and 'user@example.com' from being separate accounts.
Best practices
- Validate email format on the client for instant UX feedback and on the server for security enforcement
- Normalize emails with toLowerCase() and trim() before validation and storage to prevent duplicates
- Use a database trigger on auth.users for the strongest server-side enforcement that cannot be bypassed
- Keep the email regex reasonably permissive — overly strict patterns reject valid addresses
- Check email length limits: 254 characters total, 64 characters for the local part (before @)
- Never expose raw database error messages to users — map them to friendly, helpful messages
- Consider blocking disposable email providers via an Edge Function for apps that require real email addresses
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am using Supabase Auth for my app and want to validate email addresses before signup. Show me how to add client-side validation with a regex, create a PostgreSQL trigger on auth.users to enforce email format at the database level, and handle validation errors in the frontend.
Create a PostgreSQL trigger function that validates email format on INSERT into auth.users. The function should check the email against a regex pattern, enforce the 254-character length limit, normalize to lowercase, and raise an exception for invalid formats.
Frequently asked questions
Does Supabase validate email format by default?
Supabase performs minimal validation — it checks that the string contains an @ symbol but does not enforce a strict format. Addresses like 'test@' or 'user@.com' may pass through. Add your own validation for stricter enforcement.
Can I block disposable email addresses in Supabase?
Yes. Create an Edge Function that checks the email domain against a blocklist of known disposable email providers. Route your signup flow through this function instead of calling supabase.auth.signUp directly.
Will the database trigger affect OAuth signups?
Yes, the trigger runs on every INSERT into auth.users, including OAuth signups. Since OAuth providers like Google and GitHub provide verified email addresses, they should always pass format validation.
What regex should I use for email validation?
The pattern ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ covers the vast majority of valid email addresses. Avoid overly strict patterns that reject valid addresses with + signs or subdomains.
How do I prevent duplicate accounts with different email casing?
Normalize emails to lowercase before passing them to signUp. Supabase stores emails case-sensitively by default. The database trigger can also normalize by setting new.email := lower(trim(new.email)).
Can I add email validation without a database trigger?
Yes, you can validate in an Edge Function that acts as a signup proxy, or rely on client-side validation only. However, a database trigger is the most secure option because it cannot be bypassed regardless of how the API is called.
Can RapidDev help set up robust email validation for my Supabase project?
Yes, RapidDev can implement multi-layer email validation including client-side checks, database triggers, disposable email blocking, and domain allowlists tailored to your application's requirements.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation