Connect username sign-in, registration, and account updates while retaining email recovery and server validation.

A username can make sign-in easier to remember while email remains the account's recovery address. BTST Better Auth UI can accept either identifier in the same form, provided the server, client plugin, database schema, and UI options agree.
This guide targets @btst/better-auth-ui@2.0.1, @btst/stack@3.1.2, and better-auth@1.6.16. Start with an existing BTST auth integration; the fragments below extend its configuration.
Add this plugin to your existing server-side betterAuth({ plugins: [...] }) array. Preserve the other plugins and keep emailAndPassword.enabled: true.
import { username } from "better-auth/plugins/username";
export const usernamePlugin = username({
minUsernameLength: 3,
maxUsernameLength: 30,
});
Generate and review the auth schema changes using the tooling for your pinned Better Auth version and database adapter, then apply the migration through your normal deployment process. This release stores username and displayUsername on the user model; adding a client option does not create those columns. Preserve the generated uniqueness constraint.
Add the matching plugin to the existing client. If that client already has plugins for organizations, email codes, or passkeys, retain them in the array.
import { createAuthClient } from "better-auth/react";
import { usernameClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [usernameClient()],
});
The upstream username guide describes the APIs. Check its examples against your installed release: this article uses the options verified in 1.6.16 rather than assuming every option in the latest documentation exists there.
Merge this fragment into overrides.auth in the existing Stack provider:
import type { AuthPluginOverrides } from "@btst/better-auth-ui/client";
export const usernameUI = {
credentials: {
username: true,
usernameRequired: true,
},
} satisfies Partial<AuthPluginOverrides>;
Preserve other credential settings when merging. The released sign-in form checks whether the identifier looks like an email address. It calls signIn.email for email-shaped input and signIn.username otherwise. Enabling usernames therefore retains email sign-in; it does not replace the email/password authenticator.
The sign-up form adds a username input, while account settings offer an update-username card when username credentials are enabled. With the usual mount these pages live at /p/auth/sign-up, /p/auth/sign-in, and /p/account/settings. The account route still requires the account client plugin to be mounted.
usernameRequired is a form requirement. It does not migrate existing users or make every server registration path require a username. If your application requires all accounts to have one, enforce that policy on the server and account for social sign-up and older users. Do not lock out an existing user who has not chosen a username yet.
In the pinned server plugin, usernames are lowercased by default. A sign-up with River.Dev can store river.dev as the identifier while retaining the display spelling separately. The defaults allow letters, numbers, underscores, and dots, with the configured length bounds. Keep those rules consistent when presenting validation guidance.
Do not add email-shaped usernames to a custom validator without also reviewing the BTST form's email-versus-username routing. A username accepted by your new server rule may be sent to the email endpoint by this released form.
A preflight availability check improves feedback but cannot reserve a name. Submit the actual registration or update and handle its server error: another request can claim the same normalized username between the check and the write. Keep database uniqueness and endpoint validation in place.
Use the stable user ID for authored content, organization membership, and other relationships. A username change should not transfer those records. If you expose username-based profile URLs, decide separately whether an old URL redirects and how reused names behave.
Test these cases against a disposable account and your real adapter before rollout:
Keep password recovery and email verification available according to the application's policy. A username is an account identifier, not proof that its email address is verified and not a replacement for authorization on protected routes.
The snippets were type-checked against the pinned packages. Focused in-memory handler checks cover normalization, duplicate rejection, identifier changes, and both sign-in paths; they do not verify a production migration or the complete browser flow. Use the BTST auth documentation for provider wiring and the password-recovery guide for recovery delivery.