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

How to Enable Magic Link Login in Supabase

To enable magic link login in Supabase, go to Authentication > Providers > Email in the Dashboard and ensure the email provider is enabled. Then call supabase.auth.signInWithOtp({ email }) in your client code. Supabase sends a magic link to the user's email, and clicking it signs them in without a password. Handle the redirect by setting emailRedirectTo in the options and processing the auth callback in your application.

What you'll learn

  • How to enable the email provider for magic link login in the Dashboard
  • How to implement signInWithOtp in your client code
  • How to handle the magic link redirect callback in your application
  • How to configure custom SMTP for reliable email delivery
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read10-15 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

To enable magic link login in Supabase, go to Authentication > Providers > Email in the Dashboard and ensure the email provider is enabled. Then call supabase.auth.signInWithOtp({ email }) in your client code. Supabase sends a magic link to the user's email, and clicking it signs them in without a password. Handle the redirect by setting emailRedirectTo in the options and processing the auth callback in your application.

Enabling Passwordless Magic Link Login in Supabase

Magic link login lets users sign in by clicking a link sent to their email — no password required. It reduces friction for users and eliminates password-related support issues. Supabase supports magic links out of the box through the signInWithOtp method. This tutorial walks you through enabling the email provider, implementing the client-side code, handling the redirect after the user clicks the link, and configuring SMTP for production use.

Prerequisites

  • A Supabase project with Auth enabled
  • @supabase/supabase-js v2+ installed in your frontend project
  • A frontend framework (React, Next.js, Vue, etc.) with routing
  • Custom SMTP configured (recommended for production to avoid 2 emails/hour limit)

Step-by-step guide

1

Enable the email provider in the Dashboard

Go to the Supabase Dashboard, navigate to Authentication > Providers > Email. Make sure the email provider is enabled. Magic links work through the email provider — there is no separate toggle for magic links. By default, the email provider is enabled when you create a new Supabase project. Optionally, if you want users to sign in exclusively via magic link without passwords, you can disable 'Allow new users to sign up' and only use signInWithOtp, which creates the user and signs them in with a single call.

Expected result: The email provider is enabled in the Supabase Dashboard under Authentication > Providers.

2

Implement magic link login in the client

Use supabase.auth.signInWithOtp() with the user's email address. This sends a magic link email to the user. If the user does not have an account yet, Supabase automatically creates one (unless signups are disabled). Set the emailRedirectTo option to tell Supabase where to send the user after they click the magic link. This should be a page in your application that handles the auth callback.

typescript
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6)
7
8async function sendMagicLink(email: string) {
9 const { data, error } = await supabase.auth.signInWithOtp({
10 email,
11 options: {
12 emailRedirectTo: 'https://your-app.com/auth/callback',
13 // Set to false to prevent creating new accounts
14 // shouldCreateUser: false,
15 },
16 })
17
18 if (error) {
19 console.error('Error sending magic link:', error.message)
20 return { success: false, error: error.message }
21 }
22
23 return { success: true, message: 'Check your email for the magic link!' }
24}

Expected result: Calling sendMagicLink with an email address sends a magic link email to the user.

3

Handle the magic link redirect callback

When the user clicks the magic link in their email, Supabase redirects them to your emailRedirectTo URL with auth parameters in the URL hash. Your application needs to handle this redirect by extracting the session from the URL. The Supabase client does this automatically when the page loads if you have an onAuthStateChange listener set up. For Next.js App Router, create a route handler that exchanges the code for a session.

typescript
1// For Next.js App Router: src/app/auth/callback/route.ts
2import { createServerClient } from '@supabase/ssr'
3import { cookies } from 'next/headers'
4import { NextResponse } from 'next/server'
5
6export async function GET(request: Request) {
7 const { searchParams, origin } = new URL(request.url)
8 const code = searchParams.get('code')
9 const next = searchParams.get('next') ?? '/dashboard'
10
11 if (code) {
12 const cookieStore = await cookies()
13 const supabase = createServerClient(
14 process.env.NEXT_PUBLIC_SUPABASE_URL!,
15 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
16 {
17 cookies: {
18 getAll() { return cookieStore.getAll() },
19 setAll(cookiesToSet) {
20 cookiesToSet.forEach(({ name, value, options }) =>
21 cookieStore.set(name, value, options)
22 )
23 },
24 },
25 }
26 )
27
28 const { error } = await supabase.auth.exchangeCodeForSession(code)
29 if (!error) {
30 return NextResponse.redirect(`${origin}${next}`)
31 }
32 }
33
34 return NextResponse.redirect(`${origin}/login?error=auth_failed`)
35}

Expected result: Users are redirected to your app and signed in after clicking the magic link in their email.

4

Build the magic link login form

Create a simple form that collects the user's email address and shows feedback after the magic link is sent. The form should show a success message telling the user to check their email, and handle error states like rate limiting or invalid email addresses. No password field is needed since magic links are passwordless.

typescript
1// React component: MagicLinkLogin.tsx
2import { useState } from 'react'
3import { createClient } from '@supabase/supabase-js'
4
5const supabase = createClient(
6 process.env.NEXT_PUBLIC_SUPABASE_URL!,
7 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
8)
9
10export function MagicLinkLogin() {
11 const [email, setEmail] = useState('')
12 const [status, setStatus] = useState<'idle' | 'loading' | 'sent' | 'error'>('idle')
13 const [message, setMessage] = useState('')
14
15 async function handleSubmit(e: React.FormEvent) {
16 e.preventDefault()
17 setStatus('loading')
18
19 const { error } = await supabase.auth.signInWithOtp({
20 email,
21 options: {
22 emailRedirectTo: `${window.location.origin}/auth/callback`,
23 },
24 })
25
26 if (error) {
27 setStatus('error')
28 setMessage(error.message)
29 } else {
30 setStatus('sent')
31 setMessage('Check your email for a login link!')
32 }
33 }
34
35 if (status === 'sent') {
36 return <p>{message}</p>
37 }
38
39 return (
40 <form onSubmit={handleSubmit}>
41 <input
42 type="email"
43 value={email}
44 onChange={(e) => setEmail(e.target.value)}
45 placeholder="Enter your email"
46 required
47 />
48 <button type="submit" disabled={status === 'loading'}>
49 {status === 'loading' ? 'Sending...' : 'Send magic link'}
50 </button>
51 {status === 'error' && <p style={{ color: 'red' }}>{message}</p>}
52 </form>
53 )
54}

Expected result: A login form that collects an email address, sends a magic link, and shows appropriate feedback.

5

Configure custom SMTP for reliable delivery

The default Supabase SMTP has a limit of 2 emails per hour, which is not suitable for production. Configure a custom SMTP provider to remove this limit and improve email deliverability. Go to Dashboard > Authentication > SMTP Settings and enter your SMTP credentials. Popular options include Resend (recommended for developers), SendGrid, Mailgun, and Amazon SES. After configuring, test by sending a magic link to ensure emails arrive quickly.

typescript
1# SMTP Settings in Dashboard > Authentication > SMTP Settings:
2#
3# Sender email: noreply@your-domain.com
4# Sender name: Your App Name
5# Host: smtp.resend.com (example for Resend)
6# Port: 465
7# Username: resend
8# Password: re_your_api_key
9#
10# Other popular SMTP providers:
11# SendGrid: smtp.sendgrid.net, port 587
12# Mailgun: smtp.mailgun.org, port 587
13# Amazon SES: email-smtp.us-east-1.amazonaws.com, port 587

Expected result: Custom SMTP is configured and magic link emails are delivered reliably without the 2 emails/hour limit.

Complete working example

magic-link-auth.tsx
1// Complete Magic Link Authentication Implementation
2import { useState, useEffect } from 'react'
3import { createClient, Session } from '@supabase/supabase-js'
4
5const supabase = createClient(
6 process.env.NEXT_PUBLIC_SUPABASE_URL!,
7 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
8)
9
10export function MagicLinkAuth() {
11 const [email, setEmail] = useState('')
12 const [status, setStatus] = useState<'idle' | 'loading' | 'sent' | 'error'>('idle')
13 const [message, setMessage] = useState('')
14 const [session, setSession] = useState<Session | null>(null)
15
16 useEffect(() => {
17 // Check for existing session
18 supabase.auth.getSession().then(({ data: { session } }) => {
19 setSession(session)
20 })
21
22 // Listen for auth state changes (including magic link callback)
23 const { data: { subscription } } = supabase.auth.onAuthStateChange(
24 (_event, session) => {
25 setSession(session)
26 }
27 )
28
29 return () => subscription.unsubscribe()
30 }, [])
31
32 async function handleSendMagicLink(e: React.FormEvent) {
33 e.preventDefault()
34 setStatus('loading')
35
36 const { error } = await supabase.auth.signInWithOtp({
37 email,
38 options: {
39 emailRedirectTo: `${window.location.origin}/auth/callback`,
40 },
41 })
42
43 if (error) {
44 setStatus('error')
45 setMessage(error.message)
46 } else {
47 setStatus('sent')
48 setMessage('Check your email for a login link!')
49 }
50 }
51
52 async function handleSignOut() {
53 await supabase.auth.signOut()
54 setSession(null)
55 }
56
57 // User is signed in
58 if (session) {
59 return (
60 <div>
61 <p>Signed in as {session.user.email}</p>
62 <button onClick={handleSignOut}>Sign out</button>
63 </div>
64 )
65 }
66
67 // Magic link sent
68 if (status === 'sent') {
69 return (
70 <div>
71 <p>{message}</p>
72 <button onClick={() => setStatus('idle')}>Try another email</button>
73 </div>
74 )
75 }
76
77 // Login form
78 return (
79 <form onSubmit={handleSendMagicLink}>
80 <h2>Sign in with magic link</h2>
81 <input
82 type="email"
83 value={email}
84 onChange={(e) => setEmail(e.target.value)}
85 placeholder="your@email.com"
86 required
87 />
88 <button type="submit" disabled={status === 'loading'}>
89 {status === 'loading' ? 'Sending...' : 'Send magic link'}
90 </button>
91 {status === 'error' && <p style={{ color: 'red' }}>{message}</p>}
92 </form>
93 )
94}

Common mistakes when enabling Magic Link Login in Supabase

Why it's a problem: Not setting emailRedirectTo, causing the magic link to redirect to the default Supabase URL instead of your application

How to avoid: Always set the emailRedirectTo option in signInWithOtp to your application's callback URL. Add this URL to the Redirect URLs allowlist in Dashboard > Authentication > URL Configuration.

Why it's a problem: Using the default SMTP in production and hitting the 2 emails/hour rate limit

How to avoid: Configure a custom SMTP provider (Resend, SendGrid, Mailgun) in Dashboard > Authentication > SMTP Settings before going to production.

Why it's a problem: Not adding the redirect URL to the allowlist in the Supabase Dashboard

How to avoid: Go to Dashboard > Authentication > URL Configuration and add your callback URL (e.g., https://your-app.com/auth/callback) to the Redirect URLs list.

Why it's a problem: Not handling the auth callback on the redirect page, leaving the user in a loading state

How to avoid: For Next.js, create a /auth/callback route handler that calls exchangeCodeForSession. For SPAs, set up an onAuthStateChange listener that detects the SIGNED_IN event.

Best practices

  • Configure custom SMTP before going to production to avoid the 2 emails/hour default limit
  • Add all your deployment URLs (production, preview, local) to the redirect URL allowlist
  • Show clear feedback after sending the magic link so users know to check their email
  • Set shouldCreateUser: false if you want to prevent new accounts from being created via magic link
  • Use onAuthStateChange to detect when the user returns from the magic link callback
  • Disable the form submit button while the magic link is being sent to prevent duplicate emails
  • Set the sender email to an address on your own domain for better deliverability

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I want to add passwordless magic link login to my React app using Supabase. Show me how to enable the email provider, call signInWithOtp, handle the redirect callback, and build a simple login form with loading and success states.

Supabase Prompt

Create a React component that implements magic link login with Supabase. It should have an email input form, call signInWithOtp with emailRedirectTo, show a success message after sending, and use onAuthStateChange to detect when the user is signed in after clicking the magic link.

Frequently asked questions

Does magic link login create a new account if the user does not exist?

Yes, by default signInWithOtp creates a new user if the email is not registered. Set shouldCreateUser: false in the options to prevent this and only allow existing users to receive magic links.

How long does a magic link stay valid?

The default magic link expiration is 24 hours. You can configure this in Dashboard > Authentication > Auth Settings by changing the OTP expiry time.

Can I use magic links alongside password login?

Yes. Both methods can coexist. Users can sign up with email/password and later use magic links, or vice versa. The same user account works with both methods.

Why are my magic link emails going to spam?

The default Supabase SMTP sender may be flagged by spam filters. Configure a custom SMTP provider with your own domain and set up SPF, DKIM, and DMARC records for your sending domain to improve deliverability.

Can I customize the magic link email content?

Yes. Go to Dashboard > Authentication > Email Templates and edit the Magic Link template. Use {{ .ConfirmationURL }} for the link and customize the HTML to match your branding.

What is the difference between magic link and OTP code?

Both use signInWithOtp. Magic links send a clickable URL via email. Phone OTP sends a numeric code via SMS. For email, Supabase sends a magic link by default. You can also enable email OTP codes in the Dashboard if you prefer a code-based flow.

Can RapidDev help implement magic link authentication for my app?

Yes. RapidDev can set up magic link login with custom SMTP, branded email templates, proper callback handling for your framework, and additional features like invite-only access or domain restrictions.

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.