Regex Examples: Validating Phones, Emails and URLs
Practice4 minInnovialabs editorial team
In short
Regex checks format, not existence: normalize a phone to digits and match a pattern, check email with a simple pattern plus a confirmation message, and validate URLs with a built-in parser.
The short answer
A regular expression only answers "does this look like a phone number, email or link". It cannot tell you whether the number exists, whether mail gets delivered or whether the site opens. So the working approach is:
- Normalize the input: strip spaces, parentheses and dashes.
- Check the format with a simple, readable pattern.
- Confirm it is real: an SMS code, an email with a link, a request to the site.
- Repeat the check on the server, because client-side validation is easy to bypass.
Uzbek phone numbers (+998)
A number in Uzbekistan is the country code 998 followed by 9 digits: a two-digit operator or region code and a seven-digit subscriber number. People type it in many ways: +998 90 123 45 67, 998901234567, (90) 123-45-67.
The most reliable approach is to keep only the digits first, then check:
function normalizeUzPhone(input) {
const digits = input.replace(/\D/g, "");
if (/^998\d{9}$/.test(digits)) return "+" + digits;
if (/^\d{9}$/.test(digits)) return "+998" + digits;
return null;
}
normalizeUzPhone("(90) 123-45-67"); // "+998901234567"
normalizeUzPhone("12345"); // null
If you need to check the number "as typed", without normalizing, this pattern works:
^(?:\+?998)?[\s-]?\(?\d{2}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$
Store numbers in one format, +998XXXXXXXXX (E.164). That makes it easier to find duplicates and pass numbers to SMS gateways and CRMs.
Do not hardcode a list of operator codes into the regex: new codes appear, and your form will start rejecting real numbers. If you truly need to check codes, keep the list in configuration.
The full email address standard is so complex that a "perfect" regex becomes unreadable and still does not guarantee delivery. In practice a simple pattern is enough:
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
EMAIL_RE.test("user@example.com"); // true
EMAIL_RE.test("user@localhost"); // false
EMAIL_RE.test("user @mail.com"); // false
It catches typos such as a missing @, spaces or no domain, and does not reject rare but valid addresses with +, dashes or subdomains. Whether the mailbox exists can only be verified with a confirmation email.
A typical mistake is an overly strict pattern like [a-z0-9.]+@[a-z]+\.[a-z]{2,3}. It rejects addresses with +, multi-level domains and longer top-level domains.
URLs
Regex is a poor tool for links: URLs have many parts, encodings and edge cases. Use the built-in parser and check the protocol:
function isHttpUrl(value) {
try {
const url = new URL(value);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
The protocol check matters: without it, links like javascript:... pass, and those are dangerous to insert into a page. If you redirect to a URL taken from a parameter, compare url.hostname against an allowlist of domains.
To simply find links in text, for example in a chatbot message, a rough pattern like https?://[^\s<>"]+ is enough.
Where regex falls short
- Existence: a number, mailbox or site can be correctly formatted and still not work.
- International addresses: domains and emails in Cyrillic or other scripts often fail ASCII-only patterns.
- Business rules: "mobile numbers only" or "corporate email only" belong in code, not in an ever-growing pattern.
- Security: regex does not protect against XSS or SQL injection; that requires escaping and parameterized queries.
Catastrophic backtracking
Some patterns take exponential time on an unlucky input and can block a server. A classic example:
^([a-z0-9]+\.?)+@
The nested quantifier and the optional dot let the same run of letters be split in a huge number of ways. On a long string without @, the engine tries them all.
How to protect yourself:
- Do not nest quantifiers over the same characters. A safe version of the same pattern is
^[a-z0-9]+(?:\.[a-z0-9]+)*@, where every repetition must start with a dot. - Limit input length before running the regex.
- Test patterns against long "almost matching" strings.
- Where patterns come from outside, use engines with linear-time guarantees, such as RE2 or Go's standard
regexppackage.