PatternRegex

Validate a username

Check that a signup username starts with a letter and uses only letters, digits or underscores, 3 to 16 characters long.

Last updated

Pattern
^[a-zA-Z][a-zA-Z0-9_]{2,15}$
Open in Regex Tester & Builder

Matches

  • john_doe123
  • abc
  • User_1

Doesn’t match

  • ab
  • 1abc
  • user-name

Why it’s written this way

The anchors ^ and $ pin the pattern to the whole field, so a signup form can't accept a value just because the start looks fine — an anchored validator has to consume every character, not just a promising substring.

[a-zA-Z] forces the first character to be a letter, then [a-zA-Z0-9_]{2,15} allows 2 to 15 more letters, digits or underscores, which caps the total length at 16 characters — a common ceiling for names that also get used as URL slugs or @handles.

Edge cases to know

  • →It only recognizes ASCII letters — a name typed in Cyrillic or Devanagari script is rejected outright, which may not match a product's internationalization goals.
  • →It says nothing about whether the username is already taken, or whether it collides case-insensitively with an existing one — that check belongs server-side, not in the regex.
  • →Reserved words like 'admin' or 'root' pass the pattern fine; blocking those needs a separate lookup, not a tighter regex.

Part by part

^[a-zA-Z]
A required letter first. Forcing a letter avoids names that could be mistaken for numeric ids, and sidesteps a class of parsing bugs in systems that accept either.
[a-zA-Z0-9_]{2,15}
The remainder: letters, digits and underscores, between two and fifteen characters — so the total length is three to sixteen.
$
Anchored at both ends, because this validates a whole field. Without anchors it would find a valid username inside an invalid one.

Common mistakes

  • ^[a-zA-Z0-9_]{3,16}$

    Dropping the letter-first rule allows a purely numeric username, which then collides with numeric user ids in URLs and lookups — the ambiguity is a real source of bugs rather than a style preference.

    ^[a-zA-Z][a-zA-Z0-9_]{2,15}$
  • ^[a-zA-Z][a-zA-Z0-9_]{2,15}

    Only the start anchored means a longer string passes as long as its first sixteen characters are valid, so a two-hundred-character input is accepted and truncated somewhere downstream.

  • ^[a-zA-Z][a-zA-Z0-9_.-]{2,15}$

    Adding dots and hyphens seems generous but creates look-alike names: john.doe and john-doe are visually similar and register as different accounts, which is an impersonation vector.

Questions

Why require a letter at the start?

Mostly to keep usernames distinguishable from numeric identifiers. If a URL can contain either, an all-numeric name is ambiguous, and resolving that ambiguity later is harder than preventing it at signup.

Is sixteen characters the right maximum?

It is a convention rather than a rule, borrowed from platforms that display names in constrained space. Pick your limit from where names actually appear in your interface, and keep it consistent between validation and display.

Should I check for offensive or reserved names too?

Yes, and no pattern will do it. Reserved words like admin, api and support need an explicit denylist, because they collide with routes and imply authority the account does not have.

Does it prevent look-alike impersonation?

Only partly. It blocks the punctuation tricks, but not homoglyphs from other scripts or the classic zero-for-O substitution. Normalising and comparing against existing names catches far more than a pattern can.

Related in Patterns