Unprofane

Better Auth username check.

A before-create hook on the user table.

Runs in a database hook. Every name is checked with context: "username" and the teen policy; change the policy to suit the audience. Replace the key.

// auth.ts: Better Auth database hook
import { betterAuth } from 'better-auth';
import { Unprofane } from '@unprofane/sdk';
const unprofane = new Unprofane(process.env.UNPROFANE_API_KEY!);

/** Returns the reason a username cannot be shown, or null when it can. */
export async function usernameProblem(username: string): Promise<string | null> {
  const r = await unprofane.check({ text: username, context: 'username', policy: 'teen' });
  if (r.allowed) return null;
  return r.matches[0]?.category === 'reserved'
    ? 'That name is reserved.'
    : 'Pick a different username.';
}

export const auth = betterAuth({
  databaseHooks: {
    user: {
      create: {
        before: async (user) => {
          const problem = await usernameProblem(user.name);
          if (problem) throw new Error(problem);
          return { data: user };
        },
      },
    },
  },
});

Notes

Throwing in the hook cancels the create and returns the message to the client.

What the response holds, and how to read a reserved name apart from a profane one, is in the API reference. The other recipes are on the username page.