BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 19, 2026ReactBetter Auth UI

Add Username Sign-In to BTST Better Auth UI

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

Add Username Sign-In to BTST Better Auth UI

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 username support to the server and client#

Add this plugin to your existing server-side betterAuth({ plugins: [...] }) array. Preserve the other plugins and keep emailAndPassword.enabled: true.

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
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.

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
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.

Enable the BTST form controls#

Merge this fragment into overrides.auth in the existing Stack provider:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
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.

Treat the normalized username as the identifier#

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.

Verify both sign-in paths and existing accounts#

Test these cases against a disposable account and your real adapter before rollout:

  1. Register with mixed-case spelling, then sign in through both the normalized username and the original email. Both sessions should identify the same user.
  2. Attempt a duplicate normalized name, an invalid character, and names outside the length bounds. Confirm no second account is created.
  3. Change the username in account settings. Confirm the old identifier stops working, the new one works, and the stable user ID is unchanged.
  4. Try the wrong password and an anonymous profile update. Confirm the server rejects them.
  5. Sign in as a pre-existing email-only user and let that user choose a username without losing access.

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.

In This Post

Add username support to the server and clientEnable the BTST form controlsTreat the normalized username as the identifierVerify both sign-in paths and existing accounts