Skip to main content
The username plugin extends email/password authentication so users can sign in with a username instead of (or in addition to) their email address.

Installation

1

Add the server plugin

Enable emailAndPassword and add the username plugin:
auth.ts
import { betterAuth } from "better-auth";
import { username } from "better-auth/plugins";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
  },
  plugins: [
    username(),
  ],
});
2

Run the database migration

The plugin adds username and displayUsername columns to the user table:
npx auth migrate
3

Add the client plugin

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { usernameClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    usernameClient(),
  ],
});

Usage

Sign up with a username

Pass a username field to the existing signUp.email method:
sign-up.ts
import { authClient } from "@/lib/auth-client";

const { data, error } = await authClient.signUp.email({
  email: "[email protected]",
  password: "password1234",
  name: "Jane Doe",
  username: "janedoe",           // normalized username (e.g. lowercased)
  displayUsername: "JaneDoe123", // optional: the un-normalized display version
});
If only username is provided, displayUsername is automatically set to the pre-normalized value of username. Usernames are lowercased by default — "JaneDoe" and "janedoe" are treated as the same username.

Sign in with a username

Use the signIn.username method added by the plugin:
sign-in.ts
import { authClient } from "@/lib/auth-client";

const { data, error } = await authClient.signIn.username({
  username: "janedoe",
  password: "password1234",
});

Update a username

update-username.ts
import { authClient } from "@/lib/auth-client";

await authClient.updateUser({
  username: "new-username",
});

Check username availability

check-username.ts
import { authClient } from "@/lib/auth-client";

const { data } = await authClient.isUsernameAvailable({
  username: "janedoe",
});

if (data?.available) {
  console.log("Username is available");
}

Configuration options

All options are passed to username() on the server:
auth.ts
import { betterAuth } from "better-auth";
import { username } from "better-auth/plugins";

export const auth = betterAuth({
  emailAndPassword: { enabled: true },
  plugins: [
    username({
      minUsernameLength: 3,     // default: 3
      maxUsernameLength: 30,    // default: 30
      usernameValidator: (username) => {
        // Return false to reject the username
        if (username === "admin") return false;
        return true;
      },
      usernameNormalization: (username) => username.toLowerCase(),
      displayUsernameValidator: (displayUsername) => {
        return /^[a-zA-Z0-9_-]+$/.test(displayUsername);
      },
    }),
  ],
});
OptionTypeDefaultDescription
minUsernameLengthnumber3Minimum username length.
maxUsernameLengthnumber30Maximum username length.
usernameValidatorfunctionCustom validation function. Return false to reject.
displayUsernameValidatorfunctionCustom validation for the display username.
usernameNormalizationfunction | falselowercaseNormalization applied before storing. Pass false to disable.
displayUsernameNormalizationfunction | falsenoneNormalization applied to displayUsername.
validationOrderobjectpre-normalizationSet to "post-normalization" to validate after normalizing.

Database schema

The plugin adds two optional columns to the user table:
ColumnTypeDescription
usernamestring (unique)Normalized username.
displayUsernamestringUn-normalized display version of the username.

Build docs developers (and LLMs) love