Email OTP

The Email OTP plugin allows user to sign in, verify their email, or reset their password using a one-time password (OTP) sent to their email address.

Installation

Add the plugin to your auth config

To enable email otp in your app, you need to add the emailOTP plugin to your auth config.

auth.ts
import { betterAuth } from "better-auth"
import { emailOTP } from "better-auth/plugins"
 
export const auth = betterAuth({
    // ... other config options
    plugins: [
        emailOTP({ 
                async sendVerificationOTP({ email, otp, type}) { 
					// Implement the sendVerificationOTP method to send the OTP to the user's email address
				}, 
        }) 
    ]
})

Add the client plugin

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { emailOTPClient } from "better-auth/client/plugins"
 
export const authClient = createAuthClient({
    plugins: [
        emailOTPClient()
    ]
})

Usage

Send OTP

First, send an OTP to the user's email address.

POST
/email-otp/send-verification-otp
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
    email: "[email protected]", // required
    type: "sign-in", // required
});
PropDescriptionType
email
Email address to send the OTP.
string
type
Type of the OTP. sign-in, email-verification, or forget-password.
"email-verification" | "sign-in" | "forget-password"

Sign in with OTP

Once the user provides the OTP, you can sign in the user using the signIn.emailOtp() method.

POST
/sign-in/email-otp
const { data, error } = await authClient.signIn.emailOtp({
    email: "[email protected]", // required
    otp: "123456", // required
});
PropDescriptionType
email
Email address to sign in.
string
otp
OTP sent to the email.
string

If the user is not registered, they'll be automatically registered. If you want to prevent this, you can pass disableSignUp as true in the options.

Verify Email

To verify the user's email address, use the verifyEmail() method.

POST
/email-otp/verify-email
const { data, error } = await authClient.emailOtp.verifyEmail({
    email: "[email protected]", // required
    otp: "123456", // required
});
PropDescriptionType
email
Email address to verify.
string
otp
OTP to verify.
string

Forgot & Reset Password

To reset the user's password, you must use the forgotPassword method:

POST
/forget-password/email-otp
const { data, error } = await authClient.forgetPassword.emailOtp({
    email: "[email protected]", // required
});
PropDescriptionType
email
Email address to send the OTP.
string

After that, you may use the resetPassword() method to apply the password reset.

POST
/email-otp/reset-password
const { data, error } = await authClient.emailOtp.resetPassword({
    email: "[email protected]", // required
    otp: "123456", // required
    password: "new-secure-password", // required
});
PropDescriptionType
email
Email address to reset the password.
string
otp
OTP sent to the email.
string
password
New password.
string

Override Default Email Verification

To override the default email verification, pass overrideDefaultEmailVerification: true in the options. This will make the system use an email OTP instead of the default verification link whenever email verification is triggered. In other words, the user will verify their email using an OTP rather than clicking a link.

auth.ts
import { betterAuth } from "better-auth";
 
export const auth = betterAuth({
  plugins: [
    emailOTP({
      overrideDefaultEmailVerification: true, 
      async sendVerificationOTP({ email, otp, type }) {
        // Implement the sendVerificationOTP method to send the OTP to the user's email address
      },
    }),
  ],
});

Options

  • sendVerificationOTP: A function that sends the OTP to the user's email address. The function receives an object with the following properties:
    • email: The user's email address.
    • otp: The OTP to send.
    • type: The type of OTP to send. Can be "sign-in", "email-verification", or "forget-password".

Example

auth.ts
import { betterAuth } from "better-auth"
 
export const auth = betterAuth({
    plugins: [
        emailOTP({
            async sendVerificationOTP({
                email,
                otp,
                type
            }) {
                if (type === "sign-in") {
                    // Send the OTP for sign-in
                } else if (type === "email-verification") {
                    // Send the OTP for email verification
                } else {
                    // Send the OTP for password reset
                }
            },
        })
    ]
})
  • otpLength: The length of the OTP. Defaults to 6.
  • expiresIn: The expiry time of the OTP in seconds. Defaults to 300 seconds.
auth.ts
import { betterAuth } from "better-auth"
 
export const auth = betterAuth({
    plugins: [
        emailOTP({
            otpLength: 8,
            expiresIn: 600
        })
    ]
})
  • sendVerificationOnSignUp: A boolean value that determines whether to send the OTP when a user signs up. Defaults to false.

  • disableSignUp: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to false.

  • generateOTP: A function that generates the OTP. Defaults to a random 6-digit number.

  • allowedAttempts: The maximum number of attempts allowed for verifying an OTP. Defaults to 3. After exceeding this limit, the OTP becomes invalid and the user needs to request a new one.

auth.ts
import { betterAuth } from "better-auth"
 
export const auth = betterAuth({
    plugins: [
        emailOTP({
            allowedAttempts: 5, // Allow 5 attempts before invalidating the OTP
            expiresIn: 300
        })
    ]
})

When the maximum attempts are exceeded, the verifyOTP, signIn.emailOtp, verifyEmail, and resetPassword methods will return an error with code MAX_ATTEMPTS_EXCEEDED.

  • storeOTP: The method to store the OTP in your database, wether encrypted, hashed or plain text. Default is plain text.

Note: This will not affect the OTP sent to the user, it will only affect the OTP stored in your database.

Alternatively, you can pass a custom encryptor or hasher to store the OTP in your database.

Custom encryptor

auth.ts
emailOTP({
    storeOTP: { 
        encrypt: async (otp) => {
            return myCustomEncryptor(otp);
        },
        decrypt: async (otp) => {
            return myCustomDecryptor(otp);
        },
    }
})

Custom hasher

auth.ts
emailOTP({
    storeOTP: {
        hash: async (otp) => {
            return myCustomHasher(otp);
        },
    }
})

On this page