Unprofane

Supabase username check.

An Edge Function the profile form calls before the update.

Runs in an Edge Function. Every name is checked with context: "username" and the teen policy; change the policy to suit the audience. Replace the key.

// supabase/functions/check-username/index.ts (Edge Function called before the profile update)
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.';
}

Deno.serve(async (req) => {
  const { username } = await req.json();
  const problem = await usernameProblem(username);
  return Response.json(problem ? { ok: false, problem } : { ok: true }, { status: problem ? 422 : 200 });
});

Notes

Put the key in the function’s secrets. The function answers 422 with a reason the form can show, and the row is never written.

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.