BETTER-AUTH. UI
Plugins

Phone Number

Add phone verification-code and password sign-in, recovery, and verified phone management.

The phone-number plugin adds a dedicated /auth/phone-number view. It supports passwordless verification codes, phone number and password sign-in, password recovery, and a verified phone-number card in account settings.

Treat phone numbers as normalized identifiers. Validate and convert them to one canonical format, usually E.164, before sending an SMS. The UI intentionally accepts international input and leaves provider-specific parsing to your server.

Setup

Configure Better Auth

lib/auth.ts
import { betterAuth } from "better-auth"
import { phoneNumber } from "better-auth/plugins"

export const auth = betterAuth({
  plugins: [
    phoneNumber({
      otpLength: 6,
      requireVerification: true,
      sendOTP: ({ phoneNumber, code }) => {
        void sms.send({ to: phoneNumber, body: `Your code is ${code}` })
      },
      sendPasswordResetOTP: ({ phoneNumber, code }) => {
        void sms.send({ to: phoneNumber, body: `Your reset code is ${code}` })
      }
    })
  ]
})

Do not log codes in production. Better Auth recommends returning from sendOTP without waiting for the SMS provider. Use your runtime's background-task primitive when delivery must outlive the request.

Update the database

Generate or migrate the Better Auth schema using your existing database workflow. The plugin adds nullable phoneNumber and phoneNumberVerified fields to the user model. Keep phoneNumber unique.

For Drizzle projects, regenerate the Better Auth schema first, then generate the Drizzle migration from that schema. Do not hand-edit generated migration metadata.

Add the client plugin

lib/auth-client.ts
import { createAuthClient } from "better-auth/react"
import { phoneNumberClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
  plugins: [phoneNumberClient()]
})

Install and register the UI plugin

bun x shadcn@latest registry add @better-auth-ui=https://better-auth-ui.com/r/{style}/{name}.json
bunx --bun shadcn@latest add @better-auth-ui/phone-number
components/providers.tsx
import { phoneNumberPlugin } from "@/lib/auth/phone-number-plugin"

<AuthProvider
  authClient={authClient}
  navigate={navigate}
  plugins={[
    phoneNumberPlugin({
      signIn: true,
      passwordSignIn: true,
      passwordReset: true,
      changePhoneNumber: true
    })
  ]}
>
  {children}
</AuthProvider>

Allow the plugin routes

routes/auth/$path.tsx
import { viewPaths } from "@better-auth-ui/core"
import { phoneNumberPlugin } from "@/lib/auth/phone-number-plugin"

const validAuthPaths = new Set([
  ...Object.values(viewPaths.auth),
  ...Object.values(phoneNumberPlugin().viewPaths.auth)
])

Use validAuthPaths in the route guard that renders <Auth path={path} />.

Match UI options to server options

UI optionDefaultServer requirement
signIntruesendOTP
passwordSignInfalseA password credential; use requireVerification when appropriate
passwordResetfalsesendPasswordResetOTP
changePhoneNumbertruesendOTP; verification uses updatePhoneNumber: true
otpLength6Must match Better Auth otpLength

When both sign-in modes are enabled, the form lets users switch between a code and a password. If password sign-in returns PHONE_NUMBER_NOT_VERIFIED, it moves directly to the code step because Better Auth has already started verification.

Passwordless verification is not a second factor. Better Auth applies 2FA to phone number and password sign-in, but not to passwordless phone verification.

Account creation

Set Better Auth signUpOnVerification when a verified unknown number should create a user. It requires a temporary email generator because the Better Auth user model still requires an email.

If your user schema has other required fields, replace the plugin's phoneNumber auth view with a custom component that collects those values and passes them to authClient.phoneNumber.verify.

Components

  • <PhoneNumber /> handles code and password sign-in.
  • <ForgotPhoneNumberPassword /> and <ResetPhoneNumberPassword /> handle phone password recovery.
  • <ChangePhoneNumber /> adds, replaces, verifies, and removes the current user's phone number.

Options

Prop

Type

Localization

Prop

Type

Headless mutations

The UI uses useSendPhoneNumberOtp, useVerifyPhoneNumber, useSignInPhoneNumber, useRequestPhoneNumberPasswordReset, and useResetPhoneNumberPassword.

See the Better Auth phone-number plugin for server options, external OTP verification, attempt limits, and endpoint behavior.

Last updated on

On this page