How to block disposable emails in Supabase Auth
May 14, 2026 · 7 min read
Supabase Auth accepts any syntactically valid email by default, including disposable addresses from services like guerrillamail.com. Those users confirm a throwaway inbox once and are gone — but they still occupy a row in auth.users. This guide blocks them before the account is created.
The reliable pattern: check before signUp()
The cleanest place to reject a disposable email is your own signup handler, right before you call supabase.auth.signUp(). Run the check server-side so it can't be bypassed.
npm install @isdisposable/js'use server';
import { isDisposable } from '@isdisposable/js';
import { createClient } from '@/lib/supabase/server';
export async function signup(formData: FormData) {
const email = String(formData.get('email'));
const password = String(formData.get('password'));
if (isDisposable(email)) {
return { error: 'Please use a permanent email address.' };
}
const supabase = await createClient();
const { error } = await supabase.auth.signUp({ email, password });
if (error) return { error: error.message };
return { ok: true };
}The check is synchronous and offline — no extra latency, no API key. A disposable address never reaches Supabase.
Defense in depth: an Edge Function gate
If signups can come from multiple clients (web, mobile, third-party), centralize the rule in a Supabase Edge Function so every path is covered. The function validates, then performs the signup with the admin client.
import { isDisposable } from 'npm:@isdisposable/js';
import { createClient } from 'jsr:@supabase/supabase-js';
Deno.serve(async (req) => {
const { email, password } = await req.json();
if (isDisposable(email)) {
return new Response(
JSON.stringify({ error: 'Disposable email addresses are not allowed.' }),
{ status: 422, headers: { 'Content-Type': 'application/json' } },
);
}
const admin = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
);
const { error } = await admin.auth.admin.createUser({
email,
password,
email_confirm: false,
});
return new Response(
JSON.stringify(error ? { error: error.message } : { ok: true }),
{ status: error ? 400 : 200, headers: { 'Content-Type': 'application/json' } },
);
});Point every client at this function instead of calling signUp() directly. The disposable-email rule then lives in exactly one place.
Cleaning up users who already slipped through
Already have signups? Pull the existing list and flag disposable accounts with a bulk check.
import { isDisposableBulk } from '@isdisposable/js';
const { data } = await admin.auth.admin.listUsers();
const emails = data.users.map((u) => u.email ?? '');
const flags = isDisposableBulk(emails);
const disposable = data.users.filter((_, i) => flags[i]);
console.log(`${disposable.length} disposable accounts`);Summary
Supabase won't filter disposable emails for you. Add an isDisposable(email) check before signUp(), and for multi-client apps centralize it in an Edge Function. It's free, offline, and one line. For brand-new domains and MX checks, layer the isDisposable API on top.