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

How to Validate Email Format in Supabase Auth

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.

What you'll learn

  • Why Supabase Auth's default email validation may not be strict enough
  • How to add client-side email validation before calling signUp
  • How to create a PostgreSQL trigger for server-side email format enforcement
  • How to use an Edge Function for advanced email validation logic
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+, PostgreSQL 14+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1// Email validation utility
2const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
3
4function isValidEmail(email: string): boolean {
5 if (!email || email.length > 254) return false
6 return EMAIL_REGEX.test(email)
7}
8
9// Usage in signup handler
10async function handleSignup(email: string, password: string) {
11 if (!isValidEmail(email)) {
12 return { error: 'Please enter a valid email address.' }
13 }
14
15 const { data, error } = await supabase.auth.signUp({
16 email: email.toLowerCase().trim(),
17 password,
18 })
19
20 return { data, error }
21}

Expected result: Malformed email addresses are rejected instantly in the UI before making any API call to Supabase.

2

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.

typescript
1-- Create the validation function
2create or replace function public.validate_email_format()
3returns trigger
4language plpgsql
5security definer set search_path = ''
6as $$
7begin
8 -- Check email format with regex
9 if new.email !~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' then
10 raise exception 'Invalid email format: %', new.email;
11 end if;
12
13 -- Check email length (RFC 5321 limit)
14 if length(new.email) > 254 then
15 raise exception 'Email address exceeds maximum length of 254 characters';
16 end if;
17
18 return new;
19end;
20$$;
21
22-- Create the trigger on auth.users
23create trigger validate_email_before_insert
24 before insert on auth.users
25 for each row
26 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.

3

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.

typescript
1async function handleSignup(email: string, password: string) {
2 // Client-side validation first
3 if (!isValidEmail(email)) {
4 return { error: 'Please enter a valid email address (e.g., name@example.com).' }
5 }
6
7 const { data, error } = await supabase.auth.signUp({
8 email: email.toLowerCase().trim(),
9 password,
10 })
11
12 if (error) {
13 // Handle server-side validation errors
14 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 }
22
23 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.

4

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.

typescript
1// supabase/functions/validated-signup/index.ts
2import { createClient } from 'npm:@supabase/supabase-js@2'
3import { corsHeaders } from '../_shared/cors.ts'
4
5const BLOCKED_DOMAINS = ['tempmail.com', 'throwaway.email', 'mailinator.com']
6const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
7
8Deno.serve(async (req) => {
9 if (req.method === 'OPTIONS') {
10 return new Response('ok', { headers: corsHeaders })
11 }
12
13 const { email, password } = await req.json()
14 const normalizedEmail = email.toLowerCase().trim()
15
16 // Validate format
17 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 }
23
24 // Check for disposable email domains
25 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 }
32
33 // Proceed with signup using the regular client
34 const supabase = createClient(
35 Deno.env.get('SUPABASE_URL')!,
36 Deno.env.get('SUPABASE_ANON_KEY')!
37 )
38
39 const { data, error } = await supabase.auth.signUp({
40 email: normalizedEmail,
41 password,
42 })
43
44 if (error) {
45 return new Response(
46 JSON.stringify({ error: error.message }),
47 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
48 )
49 }
50
51 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.

5

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.

typescript
1// Test cases for email validation
2const testEmails = [
3 // Should PASS
4 { email: 'user@example.com', expected: true },
5 { email: 'user.name+tag@domain.co.uk', expected: true },
6 { email: 'user@sub.domain.com', expected: true },
7
8 // Should FAIL
9 { 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]
17
18for (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

validate-email.sql
1-- ============================================
2-- Email Format Validation for Supabase Auth
3-- Run in Supabase SQL Editor
4-- ============================================
5
6-- Create the email validation function
7create or replace function public.validate_email_format()
8returns trigger
9language plpgsql
10security definer set search_path = ''
11as $$
12declare
13 email_pattern text := '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
14begin
15 -- Normalize the email
16 new.email := lower(trim(new.email));
17
18 -- Check email is not empty
19 if new.email is null or new.email = '' then
20 raise exception 'Email address is required';
21 end if;
22
23 -- Check email length (RFC 5321)
24 if length(new.email) > 254 then
25 raise exception 'Email address exceeds maximum length';
26 end if;
27
28 -- Check format with regex
29 if new.email !~ email_pattern then
30 raise exception 'Invalid email format';
31 end if;
32
33 -- Check local part length (before @)
34 if length(split_part(new.email, '@', 1)) > 64 then
35 raise exception 'Email local part exceeds maximum length';
36 end if;
37
38 return new;
39end;
40$$;
41
42-- Create trigger on auth.users for new signups
43create trigger validate_email_before_insert
44 before insert on auth.users
45 for each row
46 execute function public.validate_email_format();
47
48-- Optional: Also validate on email updates
49create trigger validate_email_before_update
50 before update of email on auth.users
51 for each row
52 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.

ChatGPT Prompt

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.

Supabase Prompt

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.

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.