Phone number regex with a live tester
A phone number regex checks format, not whether the number exists; for international input, normalise to E.164 (a plus sign followed by at most 15 digits) before matching. The 15-digit limit comes from ITU-T Recommendation E.164. Test the four patterns below against your own numbers, then copy the version for HTML, JavaScript, Python or PHP.
/^\+[1-9]\d{1,14}$/Expects normalised input: strip separators first. Basis: ITU-T Recommendation E.164
Normalise strips spaces, brackets, dots and dashes and keeps the leading +, so "+1 (415) 555-0132" becomes "+14155550132".
- ✓ Match
+14155550132 - ✓ Match
+442079460958 - ✗ No match
4155550132 - ✗ No match
(415) 555-0132 - ✗ No match
415.555.0132 - ~ After normalise
+1 415 555 0132matches as+14155550132 - ✗ No match
1-415-555-0132 - ~ After normalise
+44 20 7946 0958matches as+442079460958 - ✗ No match
555-0132 - ✗ No match
123-456-7890 - ✗ No match
415-155-0132 - ✗ No match
(415 555-0132 - ✗ No match
+0123456789 - ✗ No match
+1234567890123456 - ✗ No match
415-555-0132 ext. 7 - ✗ No match
call me maybe
Key facts
| Question | Answer | Source |
|---|---|---|
| E.164 maximum length | 15 digits, country code included | ITU-T E.164 |
| E.164 regex | ^\+[1-9]\d{1,14}$ | ITU-T E.164 |
| US / Canada number shape | 3-digit area code, 3-digit exchange, 4-digit line; area code and exchange start with 2-9 | NANPA |
| Does type=tel validate? | No; only pattern, required, minlength and maxlength constrain it | MDN |
| How browsers run pattern= | Anchored as ^(?:…)$, compiled with the v flag; an invalid pattern is ignored | WHATWG HTML |
| Does splitforms validate phone numbers? | No; it stores the field as submitted | splitforms (first-party) |
Which phone number regex should I use?
Use E.164 to store and send numbers, the lenient US/Canada pattern to check what US visitors type, and the digits-only pattern as a loose check for any country. Each pattern below has a stated basis; per-country patterns are left to libphonenumber, which keeps per-country numbering metadata.
| Pattern | Regex | Accepts | Rejects | Use when |
|---|---|---|---|---|
| E.164 (international)Basis: ITU-T Recommendation E.164 | ^\+[1-9]\d{1,14}$ |
|
| Storing or sending a number: SMS APIs, CRMs and databases expect E.164. Normalise the input, then match. |
| US / Canada (NANP) strictBasis: NANPA (North American Numbering Plan) | ^(?:\+1)?[2-9]\d{2}[2-9]\d{6}$ |
|
| US/Canada-only forms, after normalising. It catches the area-code and exchange mistakes a plain \d{10} check lets through. |
| US / Canada (NANP) lenientBasis: NANPA (North American Numbering Plan) | ^(?:\+?1[-.\s]?)?(?:\([2-9]\d{2}\)|[2-9]\d{2})[-.\s]?[2-9]\d{2}[-.\s]?\d{4}$ |
|
| Checking what a US or Canadian visitor types, before you normalise. Pair it with type=tel on the input. |
| Digits only, 7-15Basis: ITU-T E.164 (15-digit maximum) | ^\+?\d{7,15}$ |
|
| Forms that take numbers from any country and only need a sanity check before a library or a person looks at them. |
The 7-digit floor on the digits-only pattern is a practical heuristic, not a standard; the 15-digit ceiling is E.164's.
What is the best regex for a US phone number?
For numbers as people type them, use ^(?:\+?1[-.\s]?)?(?:\([2-9]\d{2}\)|[2-9]\d{2})[-.\s]?[2-9]\d{2}[-.\s]?\d{4}$. It accepts (415) 555-0132, 415.555.0132 and +1 415 555 0132, and rejects area codes or exchanges that start with 0 or 1, which the North American Numbering Plan does not allow. Once separators are stripped, the strict form ^(?:\+1)?[2-9]\d{2}[2-9]\d{6}$ is enough.
US and Canadian numbers follow the North American Numbering Plan: a 3-digit area code, a 3-digit exchange and a 4-digit line number, where the area code and the exchange both start with a digit from 2 to 9 (NANPA). The common shortcut \d{3}-\d{3}-\d{4} misses both rules: it accepts 123-456-7890 and 415-155-0132, neither of which can be a NANP number, and it rejects the brackets and dots people actually type.
Neither pattern knows which area codes are in service; that list changes as NANPA assigns new codes. Toll-free numbers such as 800 and 888 pass, which is usually what a contact form wants. If the form only ever sees US and Canadian visitors, check with the lenient pattern on the input, then store the normalised number with a +1 prefix.
How do I validate international numbers?
Normalise first, then match E.164. Strip spaces, brackets, dots and dashes, keep the leading plus, and test the result against ^\+[1-9]\d{1,14}$: a plus sign, a country code that never starts with 0, and at most 15 digits in total under ITU-T Recommendation E.164. For per-country rules such as valid lengths and prefixes, use libphonenumber rather than one regex per country.
Normalising can't add what the visitor left out. "020 7946 0958" is a London number only if you know the visitor is in the UK, and some people type 00 or 011 instead of +; those are dialling prefixes that depend on the country they call from, not part of the number. Two ways to get a clean E.164 value:
- Ask for the country with a dial-code dropdown next to the number, as in the contact form with phone number template, and join the two before you match.
- Parse the input with libphonenumber and the visitor's country as the default region; it drops the national trunk prefix and returns E.164.
Which inputs does each pattern accept?
This matrix runs every test number against all four patterns, as typed. "After normalise" means the number fails as typed but passes once spaces, brackets, dots and dashes are stripped. It is generated from the same code as the tester above.
| Input | Case | E.164 (international) | US / Canada (NANP) strict | US / Canada (NANP) lenient | Digits only, 7-15 |
|---|---|---|---|---|---|
+14155550132 | E.164, US | Match | Match | Match | Match |
+442079460958 | E.164, UK | Match | No match | No match | Match |
4155550132 | 10 digits, no separators | No match | Match | Match | Match |
(415) 555-0132 | US national format | No match | After normalise | Match | After normalise |
415.555.0132 | Dots | No match | After normalise | Match | After normalise |
+1 415 555 0132 | International format with spaces | After normalise | After normalise | Match | After normalise |
1-415-555-0132 | Leading 1, no plus | No match | No match | Match | After normalise |
+44 20 7946 0958 | UK, international format | After normalise | No match | No match | After normalise |
555-0132 | 7-digit local number | No match | No match | No match | After normalise |
123-456-7890 | Area code starts with 1 | No match | No match | No match | After normalise |
415-155-0132 | Exchange starts with 1 | No match | No match | No match | After normalise |
(415 555-0132 | Unbalanced bracket | No match | After normalise | After normalise | After normalise |
+0123456789 | Country code starts with 0 | No match | No match | No match | Match |
+1234567890123456 | 16 digits, over the E.164 limit | No match | No match | No match | No match |
415-555-0132 ext. 7 | Extension | No match | No match | No match | No match |
call me maybe | Not a number | No match | No match | No match | No match |
Test numbers use ranges set aside for fiction where one exists: NANP 555-0100 to 555-0199 and the UK's 020 7946 0xxx drama range.
How do I use a phone regex in HTML, JavaScript, Python and PHP?
Normalise the value, then test it against the whole string: an anchored regex in JavaScript and PHP, fullmatch() in Python, and the pattern attribute in HTML, which the browser anchors for you. The snippets below use E.164 for stored values and the US/Canada or digits-only patterns for the input.
HTML: type=tel with a pattern
<!-- US / Canada: accepts (415) 555-0132, 415.555.0132, +1 415 555 0132 -->
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone"
inputmode="tel" autocomplete="tel"
pattern="(?:\+?1[\-.\s]?)?(?:\([2-9]\d{2}\)|[2-9]\d{2})[\-.\s]?[2-9]\d{2}[\-.\s]?\d{4}"
title="10-digit US or Canada number, e.g. (415) 555-0132">
<!-- Any country: 7-15 digits; spaces, dashes, dots and brackets allowed -->
<label for="phone-intl">Phone</label>
<input type="tel" id="phone-intl" name="phone"
inputmode="tel" autocomplete="tel"
pattern="\+?(?:[\s\(\)\.\-]*\d){7,15}[\s\(\)\.\-]*"
title="Phone number with country code, e.g. +44 20 7946 0958">Browsers anchor pattern as ^(?:…)$ and compile it with the v flag, so ( ) and - inside [ ] are escaped. Re-check the value on the server: anyone can bypass the browser.
JavaScript: normalise, then test E.164
// E.164: "+", a country code, at most 15 digits (ITU-T E.164)
const E164 = /^\+[1-9]\d{1,14}$/;
function normalisePhone(value) {
// Drop spaces, brackets, dots and dashes; keep the leading "+".
return value.trim().replace(/[\s().\-\u2010-\u2015]/g, "");
}
E164.test(normalisePhone("+1 (415) 555-0132")); // true
E164.test(normalisePhone("(415) 555-0132")); // false: no country codeLeave off the g flag: a global regex keeps lastIndex between .test() calls, so checking a second number can wrongly return false.
Python: re.fullmatch with re.ASCII
import re
E164 = re.compile(r"\+[1-9]\d{1,14}", re.ASCII)
SEPARATORS = re.compile(r"[\s().\-\u2010-\u2015]")
def normalise_phone(value: str) -> str:
return SEPARATORS.sub("", value.strip())
bool(E164.fullmatch(normalise_phone("+1 (415) 555-0132"))) # True
bool(E164.fullmatch(normalise_phone("(415) 555-0132"))) # Falsefullmatch() anchors both ends; re.match() with $ would also accept a trailing newline. re.ASCII keeps \d to 0-9.
PHP: preg_match with the D modifier
<?php
function normalise_phone(string $value): string {
return preg_replace('/[\s().\-\x{00A0}\x{2010}-\x{2015}]/u', '', trim($value));
}
$phone = normalise_phone($_POST['phone'] ?? '');
$isE164 = preg_match('/^\+[1-9]\d{1,14}$/D', $phone) === 1;Without /D, $ also matches before a trailing newline, so "+14155550132\n" would pass.
Why doesn't type=tel validate?
Phone formats vary too much between countries, so browsers apply no format check to <input type="tel">; MDN notes that, unlike type=email and type=url, its value is not validated automatically. type=tel gives mobile visitors a phone keypad, and the only checks come from the pattern, required, minlength and maxlength attributes.
Source: MDN, <input type="tel">. What to add, and what trips people up:
- pattern is matched against the whole value (the browser wraps it in
^(?:…)$) and compiled with the v flag, so escape(,)and-inside square brackets. An invalid pattern is ignored, not enforced (WHATWG HTML). - title describes the expected format; browsers can show it in the error message when the pattern fails.
- autocomplete="tel" asks for the full number including the country code, so autofill can insert a leading +1. Make sure your pattern accepts it; the lenient US/Canada pattern does.
- inputmode="tel" is redundant on type=tel but harmless, and it is what you need if you use type=text instead.
- Browser checks are easy to bypass. Validate again on the server; see server-side form validation and the HTML input reference.
Can a regex tell if a number is real?
No. A regex checks characters and length, nothing more. +14155550132 passes every pattern on this page, yet it sits in the 555-0100 to 555-0199 block NANPA reserves for fictional use. To know a number is assigned and reachable, send a one-time code by SMS or voice call, or use the number lookup API your SMS provider offers.
The 555 block is documented by the North American Numbering Plan Administrator (nanpa.com). Use the regex to catch typos before submit, and verification for numbers you will charge, text or call.
When should I use libphonenumber instead of a regex?
Use libphonenumber when you accept numbers from more than one country, need to turn whatever people type into E.164, or want a number's type where the numbering plan reveals it. It is Google's open-source library for parsing, formatting and validating international phone numbers, and it carries per-region metadata that a single regex can't. Ports exist for JavaScript (libphonenumber-js), Python (phonenumbers) and PHP (libphonenumber-for-php).
Project: github.com/google/libphonenumber. Ports: libphonenumber-js, phonenumbers and libphonenumber-for-php. A regex is still the right tool for a single-country form or a cheap pre-check in the browser.
import { parsePhoneNumberFromString } from "libphonenumber-js";
// Parse what the visitor typed, with their country as the default region.
const phone = parsePhoneNumberFromString("020 7946 0958", "GB");
phone?.isValid(); // checks the number against libphonenumber's UK metadata
phone?.number; // "+442079460958", the E.164 form to storeNeed the form too?
A working contact form with a phone field
Free form backend, 500 submissions total. Point the form at splitforms, paste your access key, and every accepted submission is saved to your dashboard and emailed to you, phone number included. No server code.
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<label for="name">Name</label>
<input type="text" id="name" name="name" autocomplete="name" required>
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email" required>
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" inputmode="tel" autocomplete="tel"
pattern="\+?(?:[\s\(\)\.\-]*\d){7,15}[\s\(\)\.\-]*"
title="7 to 15 digits; spaces, dashes and brackets are fine">
<label for="message">Message</label>
<textarea id="message" name="message" rows="4" required></textarea>
<input type="checkbox" name="botcheck" style="display:none">
<button type="submit">Send</button>
</form>- The pattern on the tel field is the format check: splitforms doesn't validate or reformat phone numbers, it stores the value as submitted.
botcheckis a hidden honeypot. splitforms drops submissions where it is filled; see the honeypot generator.- Smart spam filtering is rule-based: honeypot and time-trap hits are dropped, and content-rule hits go to your spam folder.
- Pro is $5/mo for 5,000 submissions a month when you outgrow the free plan.
More phone regex questions
Does splitforms validate phone numbers?
No. splitforms stores the phone field as submitted and does not validate or reformat it. Put a pattern on the input for a browser-side check, and if you need strict validation, run libphonenumber in a webhook receiver before the number reaches your CRM.
Why does my pattern attribute do nothing?
Usually the browser couldn't compile it. Browsers compile pattern with the RegExp v flag, where ( ) [ ] { } / - \ and | must be escaped inside a character class, so [-.\s] is an error. The HTML standard says an invalid pattern is ignored and encourages browsers to log the error to the developer console. Write [\-.\s] instead and check the console.
Should I store phone numbers with formatting?
Store E.164, such as +14155550132, and format it only for display. One canonical form makes deduplication, search and SMS sending work, and libphonenumber can format it back to (415) 555-0132 for people to read.
Does \d only match 0-9?
In JavaScript, yes, with or without the u and v flags. In Python 3, \d in a str pattern also matches other Unicode decimal digits, such as Arabic-Indic digits, unless you pass re.ASCII, which is why the Python snippet on this page uses it.