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 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-ui/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-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 works 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
Options
Override the route segment or localization by configuring the copied magicLinkPlugin() factory:
magicLinkPlugin({
path: "email-link",
localization: {
sendMagicLink: "Email me a link"
}
})If you override path, use the same plugin options in both your provider registration and your auth-route validation.
Prop
Type
Localization
Prop
Type
Custom Solid components should read Magic Link labels from the registered plugin metadata so the form and the toggle button stay in sync.
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, but route behavior still depends on the setup steps above: register magicLinkPlugin() and allow the plugin auth path in /auth/$path.
Last updated on