OTP Plugin
Email-based one-time password authentication
Send 6-digit verification codes via email for passwordless sign-in, email verification, and password reset - no additional configuration required beyond your mailer.
This page is part of the Authentication documentation.
The OTP (One-Time Password) plugin enables authentication via 6-digit codes sent to the user's email instead of clicking links.
Use it for passwordless sign-in, email verification, or password reset flows. The plugin is enabled by default and uses your configured mailer. Codes expire after a short window (typically 10 minutes) and can only be used once.
OTP (One-Time Password) is a 6-digit numeric code sent via email that expires after a single use, providing secure verification without requiring users to click links.
Use OTP when: you want email verification without link-clicking (better mobile UX), passwordless sign-in as an alternative to magic links, or numeric codes for password reset.
How It Works
- User requests an OTP (sign-in, verification, or password reset)
- A 6-digit code is generated and sent via email
- User enters the code in the application
- Code is validated and action is completed
- Code is invalidated (single use)
OTP Types
| Type | Purpose | Email Template |
|---|---|---|
sign-in | Passwordless authentication | otp-sign-in.email.tsx |
email-verification | Verify email address | otp-email-verification.email.tsx |
forget-password | Password reset | otp-password-reset.email.tsx |
Configuration
The plugin is enabled by default. It uses your existing email configuration:
apps/web/.env.local
MAILER_PROVIDER=nodemailerEMAIL_SENDER=noreply@yourapp.comEMAIL_HOST=smtp.example.comEMAIL_PORT=587EMAIL_USER=your_userEMAIL_PASSWORD=your_passwordSee Email Configuration for complete mailer setup.
Usage Example
import { authClient } from '@kit/better-auth/client';// Request OTPawait authClient.emailOtp.sendVerificationOtp({ email: 'user@example.com', type: 'sign-in',});// Verify OTP (user enters code from email)const result = await authClient.emailOtp.verifyOtp({ email: 'user@example.com', otp: '123456',});Development Mode
In development (NODE_ENV=development), OTP codes are logged to the console:
[DEV] OTP for user@example.com (sign-in): 847293This allows testing without checking email.
Email Templates
Templates are in packages/email-templates/src/emails/:
otp-sign-in.email.tsx- Sign-in OTPotp-email-verification.email.tsx- Email verification OTPotp-password-reset.email.tsx- Password reset OTP
Common Pitfalls
- Email not configured: OTP requires a working mailer. Test email delivery before relying on OTP.
- Codes expiring too quickly: Default expiration is short for security. If users complain, check your email delivery speed.
- Not handling invalid codes gracefully: Show clear error messages when codes are wrong or expired.
- Allowing unlimited OTP requests: The plugin has built-in rate limiting, but verify it's not disabled.
- Forgetting development console output: In dev mode, check the terminal for OTP codes instead of email.
Frequently Asked Questions
How long do OTP codes last?
Can I customize the OTP email templates?
Is OTP more secure than magic links?
Can I use OTP for MFA?
Why are OTP emails not being sent?
Next: Rate Limiting →