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.

Terminalbash
npm install @isdisposable/js
app/actions.tstypescript
'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.

supabase/functions/signup/index.tstypescript
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.

audit.tstypescript
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.

Block disposable emails today

isDisposable is free and open source — add it to your signup flow in one line.

Keep reading