Username

The username plugin adds username support to the email/password authenticator. Users can sign up with a username, sign in with that username instead of their email, update it later, and check availability.

This plugin is implemented in the Ruby port. Ruby uses snake_case option names and auth.api method names, while HTTP route paths and JSON keys preserve the upstream wire contract.

Installation

Add the plugin on the server:

config/auth.rb
require "better_auth"

auth = BetterAuth.auth(
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  base_url: ENV.fetch("BETTER_AUTH_URL", "http://localhost:3000"),
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username
  ]
)

Migrate The Database

The plugin adds username and displayUsername fields to the user schema.

For Rails, configure the plugin before generating the migration:

config/initializers/better_auth.rb
BetterAuth::Rails.configure do |config|
  config.plugins = [
    BetterAuth::Plugins.username
  ]
end
Terminal
bin/rails generate better_auth:migration
bin/rails db:migrate

For direct SQL apps, generate or apply the SQL schema for your configured adapter after the plugin is present in plugins.

Usage

Sign Up

Use the existing email sign-up endpoint and include username. If displayUsername is omitted, it is set from the pre-normalized username.

server.rb
result = auth.api.sign_up_email(
  body: {
    email: "email@domain.com",
    name: "Test User",
    password: "password1234",
    username: "test",
    displayUsername: "Test User123"
  }
)

HTTP route:

POST /api/auth/sign-up/email
{
  "email": "email@domain.com",
  "name": "Test User",
  "password": "password1234",
  "username": "test",
  "displayUsername": "Test User123"
}

Sign In

The plugin adds /sign-in/username and the matching Ruby API method sign_in_username.

server.rb
result = auth.api.sign_in_username(
  body: {
    username: "test",
    password: "password1234"
  }
)

token = result[:token]
user = result[:user]

HTTP route:

POST /api/auth/sign-in/username
{
  "username": "test",
  "password": "password1234"
}

Update Username

Use the core update-user endpoint with an authenticated session cookie.

server.rb
result = auth.api.update_user(
  headers: {
    "cookie" => request.env["HTTP_COOKIE"]
  },
  body: {
    username: "new-username"
  }
)

HTTP route:

POST /api/auth/update-user
{
  "username": "new-username"
}

You can update the display value separately:

server.rb
auth.api.update_user(
  headers: {
    "cookie" => request.env["HTTP_COOKIE"]
  },
  body: {
    username: "new-username",
    displayUsername: "New Username"
  }
)

Check Availability

server.rb
response = auth.api.is_username_available(
  body: {
    username: "new-username"
  }
)

if response[:available]
  # username is available
else
  # username is already taken
end

HTTP route:

POST /api/auth/is-username-available
{
  "username": "new-username"
}

Options

Min Username Length

The minimum length of the username. Default is 3.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      min_username_length: 5
    )
  ]
)

Max Username Length

The maximum length of the username. Default is 30.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      max_username_length: 100
    )
  ]
)

Username Validator

Provide a callable that returns false when a username should be rejected. By default, usernames may contain letters, numbers, underscores, and dots.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      username_validator: ->(username) {
        username != "admin" && username.match?(/\A[a-zA-Z0-9_.]+\z/)
      }
    )
  ]
)

Display Username Validator

Provide a callable to validate displayUsername. By default, display usernames do not get additional validation.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      display_username_validator: ->(display_username) {
        display_username.match?(/\A[a-zA-Z0-9_\-\s]+\z/)
      }
    )
  ]
)

Username Normalization

By default, usernames are normalized to lowercase. Set username_normalization: false to disable normalization or provide a callable.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      username_normalization: ->(username) {
        username
          .downcase
          .tr("034", "oea")
      }
    )
  ]
)

Display Username Normalization

By default, display usernames are preserved as provided. You can provide a callable to normalize them.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      display_username_normalization: ->(display_username) {
        display_username.strip
      }
    )
  ]
)

Validation Order

By default, username and display username validation happens before normalization. Set a field to "post-normalization" to validate the normalized value instead.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  plugins: [
    BetterAuth::Plugins.username(
      validation_order: {
        username: "post-normalization",
        display_username: "post-normalization"
      }
    )
  ]
)

Disable Availability Checks

The plugin exposes /is-username-available by default. You can disable the route through the core disabled_paths option.

config/auth.rb
auth = BetterAuth.auth(
  email_and_password: {
    enabled: true
  },
  disabled_paths: ["/is-username-available"],
  plugins: [
    BetterAuth::Plugins.username
  ]
)

Schema

The plugin adds these fields to the user table:

FieldTypeRequiredDescription
usernamestringNoNormalized username. It is unique and returned in user output.
displayUsernamestringNoNon-normalized username for display.

Errors

ErrorMeaning
INVALID_USERNAME_OR_PASSWORDThe username/password pair is invalid.
EMAIL_NOT_VERIFIEDEmail verification is required before sign-in.
USERNAME_IS_ALREADY_TAKENAnother user already owns the username.
USERNAME_TOO_SHORTThe username is shorter than min_username_length.
USERNAME_TOO_LONGThe username is longer than max_username_length.
INVALID_USERNAMEThe username validator rejected the value.
INVALID_DISPLAY_USERNAMEThe display username validator rejected the value.

On this page