Skip to content

Phone Number

SMS one-time codes for sign-in, phone verification and password reset. Mirrors the TS phoneNumber() plugin.

Enable

python
from better_auth import BetterAuth
from better_auth.plugins_ext import PhoneNumberPlugin

async def send_otp(phone_number, code):
    ...  # send the SMS

auth = BetterAuth(
    secret="a-strong-32-character-minimum-secret",
    plugins=[PhoneNumberPlugin(send_otp=send_otp)],
)

Options

OptionTypeDefaultDescription
otp_lengthint6Number of digits.
expires_inint300Code lifetime in seconds.
allowed_attemptsint3Wrong-code budget per stored OTP.
send_otpcallable | NoneNone(phone_number, code) -> None. Required in practice: endpoints answer SEND_OTP_NOT_IMPLEMENTED (501) without it.
verify_otpcallable | NoneNoneCustom verifier replacing the stored-code comparison.
send_password_reset_otpcallable | NoneNoneSeparate sender for password-reset codes.
phone_number_validatorcallable | NoneNone(phone_number) -> bool format check.
require_verificationboolFalseBlock /sign-in/phone-number until the number is verified.
callback_on_verificationcallable | NoneNoneCalled after a successful verification.
sign_up_on_verificationdict | NoneNoneAuto-create a user on first verification (temp-email settings).

Endpoints

MethodPath
POST/sign-in/phone-number
POST/phone-number/send-otp
POST/phone-number/verify
POST/phone-number/request-password-reset
POST/phone-number/reset-password

Schema

TableAdded columns
userphoneNumber, phoneNumberVerified

Notes

  • Storage parity with TS: codes stored as "<code>:<attempts>" under the raw phone number; reset OTPs under "<phoneNumber>-request-password-reset".
  • Codes are consumed atomically — one code never satisfies two verifications.
  • Deliberate simplifications: the TS per-instance schema field-name override is not exposed, and SMS-send failures are not isolated in a background task (no advanced.backgroundTasks seam in this port).

MIT licensed · API-compatible with better-auth