Quick answer
A good username regex is ^(?=.{3,16}$)[A-Za-z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*$. It allows 3 to 16 characters, requires a letter first, and permits single dots, underscores or hyphens only between letters and digits, so jane_doe and player-one pass while _jane, jane__doe and jane. fail. The lookahead at the start checks the length once, which keeps the rest of the pattern simple.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the username regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the username regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the username regex work?
Read it left to right. Each piece below matches one part of the input:
^Start of the string.(?=.{3,16}$)Lookahead: the whole username is 3 to 16 characters. It checks without consuming, so matching restarts at the beginning.[A-Za-z]The first character must be a letter, so handles never look like numbers or flags.[A-Za-z0-9]*Any letters and digits after it.(?:[._-][A-Za-z0-9]+)*Optional groups of one separator followed by letters or digits. Separators can never be doubled or come last.$End of the string.
Which strings does the username regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
jane_doe | Yes | Underscore between two words. |
john.smith | Yes | A single dot is allowed between parts. |
player-one | Yes | Hyphens work the same way. |
dev42 | Yes | Digits are fine after the first letter. |
abc | Yes | The minimum length of 3. |
ab | No | Too short. |
1player | No | Must start with a letter. |
jane__doe | No | Two separators in a row. |
jane_ | No | Cannot end with a separator. |
_jane | No | Cannot start with a separator. |
this_username_is_too_long | No | 25 characters: over the 16-character limit. |
jane doe | No | Spaces are not allowed. |
jané | No | Accented letters are outside A–Z (see the Unicode variant below). |
How do I change the username rules?
Each rule lives in one place, so adjust the part you need:
| Rule | Change |
|---|---|
| Length 3–30 | (?=.{3,30}$) |
| Allow a digit first | replace [A-Za-z] with [A-Za-z0-9] |
| Underscores only | replace [._-] with _ |
| Lowercase only | drop A-Z from both classes |
| Any script (Unicode letters) | [\p{L}][\p{L}\p{N}]*(?:[._-][\p{L}\p{N}]+)* with the u flag |
Two rules belong in code, not in the regex: reserved names (admin, support, root, api, your brand) and uniqueness. Compare usernames case-insensitively, and if you allow Unicode, normalize with NFKC first so look-alike characters cannot impersonate another user.
Why use a lookahead for the length?
Without it you would have to count characters across the separators, which regex is bad at. (?=.{3,16}$) checks the total length once, at the start, and consumes nothing: the rest of the pattern then only describes the shape.
Go's RE2 engine does not support lookaheads. In Go, drop the lookahead and check len(username) (or utf8.RuneCountInString) in code, which is clearer anyway.
How do I use the username regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^(?=.{3,16}$)[A-Za-z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*$/;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("jane_doe"));Python
import re
pattern = re.compile(r"^(?=.{3,16}\Z)[A-Za-z][A-Za-z0-9]*(?:[._\-][A-Za-z0-9]+)*\Z")
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("jane_doe"))- JavaScript's . also refuses \r, \u2028 and \u2029; in Python it only refuses \n.
- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^(?=.{3,16}$)[A-Za-z][A-Za-z0-9]*(?:[._\-][A-Za-z0-9]+)*$`)
fmt.Println(re.MatchString("jane_doe"))
}Go's regexp package (RE2) does not support lookahead or lookbehind, so this pattern will not compile. Match more broadly and check the extra condition in Go code instead.
- JavaScript's . also refuses \r, \u2028 and \u2029; in Go it only refuses \n.
C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^(?=.{3,16}\z)[A-Za-z][A-Za-z0-9]*(?:[._\-][A-Za-z0-9]+)*\z", RegexOptions.None, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("jane_doe"));- JavaScript's . also refuses \r, \u2028 and \u2029; in .NET it only refuses \n.
- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^(?=.{3,16}$)[A-Za-z][A-Za-z0-9]*(?:[._\-][A-Za-z0-9]+)*$/D';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('jane_doe', $pattern));- JavaScript's . also refuses \r, \u2028 and \u2029; in PHP (PCRE) it only refuses \n.
- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for URL Slugs: Lowercase letters and digits separated by single hyphens, like regex-tester.
- Regex for Alphanumeric Characters: Letters A–Z and digits only, with a Unicode variant for every script.
- Regex for Strong Passwords: 12+ characters with lowercase, uppercase, a digit and a symbol, built from lookaheads.
- Regex for Email Validation: Practical email check: dotted local part, real domain labels and a letters-only top-level domain.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.