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.
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:
[auth.email]
enable_confirmations = falseRestart the auth container:
supabase stop && supabase startSelf-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:
// 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:
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.userscan 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:
- 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.
- Spam signups. Bots fill your
auth.userstable with garbage, which costs you rows and skews analytics. - 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_atis set on the OAuth callback regardless. - Treating
confirmed_atandemail_confirmed_atas 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 = falseinconfig.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.
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:
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.
Related fixes & guides
- Supabase "Database Error Saving New User" Trigger Fix
- Fix: Multiple GoTrueClient instances detected (Supabase)
- Supabase Edge Function BOOT_ERROR: Fix "Failed to Bootstrap"
- Supabase RLS Policy Design Patterns Beyond the Basics
- Advanced Authentication Patterns with Next.js and Supabase
- Supabase Auth + Middleware: Session Guide for Next.js 15
- Supabase 42501: permission denied for schema auth
- Test Supabase RLS Policies Before You Ship
- 11 Supabase Auth Lessons From a Year in Production
- Fix Supabase RLS Infinite Recursion (Production 2026)

