Magic Link
Add passwordless email sign-in to your Solid/Zaidan authentication flow.
The Magic Link plugin adds a passwordless email sign-in flow to the copied Solid/Zaidan auth UI. Users enter an email address, receive a one-time link, and sign in when they open it.
It contributes:
- A copied
<MagicLink />view rendered at/auth/magic-link - A copied
<MagicLinkSent />confirmation view rendered at/auth/magic-link-sent - A Magic Link toggle button that links between password sign-in and the magic-link view
- Solid runtime wiring through
signInMagicLinkOptions
Setup
Install the Better Auth plugin
Add magicLink({ sendMagicLink }) to your Better Auth server config and connect sendMagicLink to your email provider:
import { betterAuth } from "better-auth"
import { magicLink } from "better-auth/plugins"
export const auth = betterAuth({
// ...
plugins: [
magicLink({
sendMagicLink: async ({ email, url }) => {
// Send `url` to `email` with your provider.
}
})
]
})Replace example email logging with a real email provider before production.
Install the matching Solid client plugin
Add magicLinkClient() to your Solid auth client so authClient.signIn.magicLink is available:
import { magicLinkClient } from "better-auth/client/plugins"
import { createAuthClient } from "better-auth/solid"
export const authClient = createAuthClient({
plugins: [
magicLinkClient()
]
})Install the Solid/Zaidan components
npx shadcn@latest add https://better-auth-ui.com/r/solid/magic-link.jsonThis copies the following files into your project:
src/lib/auth/magic-link-plugin.tssrc/components/auth/magic-link.tsxsrc/components/auth/magic-link-sent.tsxsrc/components/auth/magic-link-button.tsxsrc/components/auth/open-email-button.tsx- local form UI primitives under
src/components/ui/**
Register the UI plugin
Register magicLinkPlugin() in your copied Solid auth provider:
import { AuthProvider } from "@/components/auth/auth-provider"
import { magicLinkPlugin } from "@/lib/auth/magic-link-plugin"
<AuthProvider
authClient={authClient}
plugins={[magicLinkPlugin()]}
>
{children}
</AuthProvider>Allow the plugin auth route
The copied route must accept the plugin-contributed auth path. Keep provider registration and route validation aligned, especially if you customize magicLinkPlugin({ path }):
import { viewPaths } from "@better-auth-ui/core"
import { createFileRoute, redirect } from "@tanstack/solid-router"
import { Auth } from "@/components/auth/auth"
import { magicLinkPlugin } from "@/lib/auth/magic-link-plugin"
const validAuthPathSegments = new Set([
...Object.values(viewPaths.auth),
...Object.values(magicLinkPlugin().viewPaths.auth)
])
export const Route = createFileRoute("/auth/$path")({
beforeLoad({ params: { path } }) {
if (!validAuthPathSegments.has(path)) {
throw redirect({ to: "/" })
}
},
component: AuthPage
})
function AuthPage() {
const { path } = Route.useParams()()
return <Auth path={path} />
}/auth/magic-link and /auth/magic-link-sent work after both the provider registration step and the route-wiring step are in place.
Components
<MagicLink />
The copied <MagicLink /> view is rendered when the Magic Link plugin is registered and the auth route accepts the plugin path.
import { MagicLink } from "@/components/auth/magic-link"
<MagicLink />Prop
Type
<MagicLinkSent />
After a magic-link request succeeds, the form stores the submitted email in session storage and navigates to this confirmation view. It redirects back to the magic-link form when that state is missing and shows an email-provider shortcut when one is available. Hover or focus the button to show a QR code for opening the same provider URL on another device.
import { MagicLinkSent } from "@/components/auth/magic-link-sent"
<MagicLinkSent />Prop
Type
Options
Override the route segment or localization by configuring the copied magicLinkPlugin() factory:
magicLinkPlugin({
path: "email-link",
sentPath: "email-link-sent",
localization: {
sendMagicLink: "Email me a link"
}
})If you override path or sentPath, use the same plugin options in both your provider registration and your auth-route validation.
Prop
Type
Localization
Prop
Type
In a custom Solid component, read Magic Link labels from the registered plugin metadata. This keeps the form and toggle button consistent.
Email template
You can pair sendMagicLink with the existing <MagicLinkEmail /> example, or render your own email template on the server.
Passwordless-only
If your app disables email and password auth, you can keep the Magic Link flow as the primary sign-in surface:
<AuthProvider
authClient={authClient}
emailAndPassword={{ enabled: false }}
plugins={[magicLinkPlugin()]}
>
{children}
</AuthProvider>The copied plugin already provides fallbackViews.auth.signIn. The signUp, forgotPassword, resetPassword, and resetLinkSent views redirect there when password auth is disabled. Route behavior still depends on the setup steps above: register magicLinkPlugin() and allow both plugin auth paths in /auth/$path.
Last updated on