AUTH STARTER · PHONE NUMBER OTP
Phone number OTP
UI and DB fields are wired. The actual SMS sending is up to you — Better Auth doesn't ship an SMS provider.
Phone number auth is interesting because Better Auth generates the OTP code, but DELIVERY is your problem. There's no built-in SMS — you plug in Twilio, Vonage, AWS SNS, or whoever, in src/lib/auth.ts.
Out of the box, phone OTP sends nothing
The sendOTP function in src/lib/auth.ts is a stub that console.warns the code in dev. In production it silently does nothing. If you ship phone OTP without wiring an SMS provider, your users WILL get stuck on a code-entry screen forever. Replace the stub.
Turn it on
// src/auth.config.ts
plugins: {
phoneNumber: { enabled: true },
},Wire your SMS provider (Twilio example)
// src/lib/auth.ts — replace the stub
import twilio from "twilio";
const sms = twilio(env.TWILIO_ACCOUNT_SID, env.TWILIO_AUTH_TOKEN);
phoneNumber({
sendOTP: async ({ phoneNumber: phone, code }) => {
await sms.messages.create({
body: `Your ${authConfig.appName} code: ${code}`,
from: env.TWILIO_FROM_NUMBER,
to: phone,
});
},
}),Client flow
// Request OTP
await authClient.phoneNumber.sendOtp({ phoneNumber: "+14155550123" });
// Verify
await authClient.phoneNumber.verify({
phoneNumber: "+14155550123",
code,
});E.164 format only
Phone numbers must be in E.164 (+14155550123, no spaces, no dashes). The UI component already enforces it — if you build your own form, validate before sending.
SMS costs real money
Twilio is roughly $0.0079 per SMS to US numbers, more internationally. Magic links and email OTP are effectively free at small scale. Use SMS as a last resort, not a default.
Linking a phone after sign-up
The dashboard component src/components/auth/phone-link.tsx lets logged-in users link or change their phone. Drop it into your settings page.