Sign In to Supabase Without Email Verification (2026)

Disable Supabase email confirmation safely: dashboard toggle, admin API auto-confirm, and the RLS half-verified pattern — with the trade-offs.

Mahdi Ben RhoumaMahdi Ben Rhouma · Updated

The friction#

You are building locally. You sign up a test user, Supabase replies "Check your email to confirm your account", and now you have to open the inbox, click the link, and only then can you log in. Every. Single. Iteration. A related redirect issue occurs when the magic link or email auth ignores the intended destination, as covered in the redirectTo fix guide.

On hosted Supabase, email confirmation is on by default. On self-hosted and local (supabase start) it is off by default. So the friction is worst when you are developing against a hosted project — which is most people.

The original ask for this is supabase/supabase#5113: a flag to let users sign in without email confirmation. The flag exists in three forms, depending on whether you want to disable it globally or just confirm one user.

Option 1 — Turn it off in the dashboard (hosted dev)#

Dashboard → Authentication → Sign In / Providers → Email → Confirm email: OFF

URL: https://supabase.com/dashboard/project/_/auth/providers

New signups can now sign in immediately. No confirmation email is sent.

This is project-wide. It affects every signup on that project, so use it only on a dev project, not production.

Option 2 — Turn it off in config (self-hosted / local)#

In supabase/config.toml:

toml
[auth.email]
enable_confirmations = false

Restart the auth container:

bash
supabase stop && supabase start

Self-hosted and local projects ship with enable_confirmations = false, so you only need this if you previously turned it on.

Option 3 — Confirm one user with the admin API (production-safe)#

When you want to keep confirmation on globally but pre-confirm one user — a team member, an invited customer, a test account — use the admin API with the service role key:

ts
// server-only — NEVER expose the service role key to the browser
import { createClient } from '@supabase/supabase-js'
 
const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!, // service role, not anon
)
 
const { data, error } = await supabaseAdmin.auth.admin.createUser({
  email: 'teammate@example.com',
  password: 'a-strong-temporary-password',
  email_confirm: true, // ✅ user is already confirmed, no email sent
})

email_confirm: true writes email_confirmed_at for you. The user can sign in immediately. This is the right tool for production invites.

Option 4 — Confirm an existing user in SQL#

For a user who already exists but is stuck unconfirmed:

sql
update auth.users
set email_confirmed_at = now(),
    confirmed_at = now()
where email = 'teammate@example.com';

Run this in the SQL editor with the service role. It is the surgical option — no setting changes, no API call, just flip the column. Useful when a confirmation email bounced or a user lost access to their inbox.

⚠️ Do not expose this as a client-callable function. Anyone who can run it against auth.users can confirm any email — which is account takeover. It is a server-side escape hatch, not a feature.

When you must keep confirmation on#

In production, keep email confirmation on. Three reasons:

  1. Account takeover. Without confirmation, anyone can sign up with an email they do not own and lock the real owner out of that address on your app.
  2. Spam signups. Bots fill your auth.users table with garbage, which costs you rows and skews analytics.
  3. Abuse of free tier. Unverified signups are the main vector for Supabase free-tier quota abuse; a project with confirmation off is a magnet for it.

The production pattern: confirmation on, plus the admin API for pre- confirmed invites. Disable confirmation only on dev and staging projects.

The redirect after sign-in#

Once confirmation is off (or the user is confirmed), the next friction is where they land after signInWithPassword. If the post-login redirect is broken, the user is authenticated but staring at the wrong page — see fix Supabase auth redirects that land on the wrong URL. And if the session itself does not persist across refreshes, it is the Supabase auth session persistence issue, not email confirmation.

Common mistakes#

  • Disabling confirmation in production. It is a security control, not a convenience. Use the admin API to pre-confirm individuals instead.
  • Using the service role key in the browser. The service role bypasses RLS. If it ships in client code, your database is public. The getSession() security warning covers the same boundary from the read side.
  • Forgetting the OAuth path still verifies. Turning off email confirmation does not affect OAuth providers — Google, GitHub already verify the email upstream, so email_confirmed_at is set on the OAuth callback regardless.
  • Treating confirmed_at and email_confirmed_at as different. Set both in SQL; Supabase reads both depending on the flow.

TL;DR#

  • Hosted dev: dashboard → Auth Providers → Email → Confirm email OFF.
  • Local/self-hosted: auth.email.enable_confirmations = false in config.toml.
  • Production: keep it ON. Pre-confirm individuals with the admin API (email_confirm: true) or SQL (update auth.users set email_confirmed_at = now()).

Handling the sign-in error instead of turning confirmation off#

Every option above is a way to avoid the confirmation gate entirely — disable it globally, or pre-confirm a user before they ever try to sign in. There's a fourth approach that keeps confirmation on in production and just handles the failure properly: catching the specific error signInWithPassword returns for an unconfirmed account, instead of showing a generic "invalid credentials" message that leaves the user unsure whether they mistyped their password or never confirmed at all.

ts
const { data, error } = await supabase.auth.signInWithPassword({ email, password })
 
if (error) {
  if (error.code === 'email_not_confirmed') {
    // show a "check your inbox" state with a resend button, not a login error
  } else {
    // wrong password, unknown email, etc. — a genuine credentials error
  }
}

The resend button behind that state calls a separate method — it does not retry signInWithPassword:

ts
await supabase.auth.resend({
  type: 'signup',
  email,
})

This distinction matters because the two errors need different UI: "your password is wrong" should prompt a retry or a password reset, while "your email isn't confirmed yet" should prompt a resend, and conflating them into one generic error message is what sends users to your support inbox instead of just clicking a link in a second email. The full set of error codes supabase-js returns from auth calls, and how to type them so a switch on error.code is exhaustive instead of guesswork, is covered in Supabase Auth Error Codes in TypeScript.

The default email service is rate-limited — production needs custom SMTP#

Whichever option above you use, if it results in Supabase actually sending a confirmation or resend email, there's a ceiling on how many of those the built-in email service will send. Supabase's own docs are explicit that the default sender is meant for local development and low-volume testing, not for a production app's real signup traffic — it exists so supabase start and a fresh hosted project work out of the box without asking you to configure an SMTP provider on day one.

The practical effect: a launch, a marketing push, or even a slow trickle of real signups can hit that ceiling, and from that point every further confirmation or resend email silently fails to send — the API call still returns success, the user just never receives anything, and support tickets about "I never got the email" start arriving with no error anywhere in your own logs to explain why.

The fix is configuring a custom SMTP provider under Dashboard → Project Settings → Authentication → SMTP Settings before a project goes to real users, not after the first support ticket. This is a one-time setup that applies to every email Supabase Auth sends — confirmations, magic links, password resets — so it's worth doing at the same time you decide how confirmation itself should behave in production, which is exactly the decision covered in the "when you must keep confirmation on" section above. Related delivery problems that show up even after SMTP is configured correctly — a confirmation link that redirects to the wrong URL rather than failing to send at all — are covered in Supabase Auth Redirect Fix.

Further reading: Supabase OAuth access_token hash Stuck in URL.

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Related fixes & guides