splitforms.com
Free tool · runs in your browser

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.

Last verified: · By Raman Makkar

Pattern
Regex
/^\+[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".

Results2 of 16 match as typed · 2 more after normalising
  1. ✓ Match+14155550132
  2. ✓ Match+442079460958
  3. ✗ No match4155550132
  4. ✗ No match(415) 555-0132
  5. ✗ No match415.555.0132
  6. ~ After normalise+1 415 555 0132matches as +14155550132
  7. ✗ No match1-415-555-0132
  8. ~ After normalise+44 20 7946 0958matches as +442079460958
  9. ✗ No match555-0132
  10. ✗ No match123-456-7890
  11. ✗ No match415-155-0132
  12. ✗ No match(415 555-0132
  13. ✗ No match+0123456789
  14. ✗ No match+1234567890123456
  15. ✗ No match415-555-0132 ext. 7
  16. ✗ No matchcall me maybe

Key facts

QuestionAnswerSource
E.164 maximum length15 digits, country code includedITU-T E.164
E.164 regex^\+[1-9]\d{1,14}$ITU-T E.164
US / Canada number shape3-digit area code, 3-digit exchange, 4-digit line; area code and exchange start with 2-9NANPA
Does type=tel validate?No; only pattern, required, minlength and maxlength constrain itMDN
How browsers run pattern=Anchored as ^(?:…)$, compiled with the v flag; an invalid pattern is ignoredWHATWG HTML
Does splitforms validate phone numbers?No; it stores the field as submittedsplitforms (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.

PatternRegexAcceptsRejectsUse when
E.164 (international)Basis: ITU-T Recommendation E.164^\+[1-9]\d{1,14}$
  • +14155550132
  • +442079460958
  • 4155550132 (no + or country code)
  • +1 415 555 0132 (spaces: normalise first)
  • +0123456789 (country codes never start with 0)
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}$
  • 4155550132
  • +14155550132
  • 1234567890 (area code starts with 1)
  • 4151550132 (exchange starts with 1)
  • 415-555-0132 (separators: normalise first)
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}$
  • (415) 555-0132
  • 415.555.0132
  • +1 415 555 0132
  • 1-415-555-0132
  • (415 555-0132 (unbalanced bracket)
  • 123-456-7890 (area code starts with 1)
  • 555-0132 (no area code)
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}$
  • 4155550132
  • +442079460958
  • 5550132
  • 123456 (6 digits)
  • +1234567890123456 (16 digits)
  • 415-555-0132 (separators: normalise first)
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.

InputCaseE.164 (international)US / Canada (NANP) strictUS / Canada (NANP) lenientDigits only, 7-15
+14155550132E.164, USMatchMatchMatchMatch
+442079460958E.164, UKMatchNo matchNo matchMatch
415555013210 digits, no separatorsNo matchMatchMatchMatch
(415) 555-0132US national formatNo matchAfter normaliseMatchAfter normalise
415.555.0132DotsNo matchAfter normaliseMatchAfter normalise
+1 415 555 0132International format with spacesAfter normaliseAfter normaliseMatchAfter normalise
1-415-555-0132Leading 1, no plusNo matchNo matchMatchAfter normalise
+44 20 7946 0958UK, international formatAfter normaliseNo matchNo matchAfter normalise
555-01327-digit local numberNo matchNo matchNo matchAfter normalise
123-456-7890Area code starts with 1No matchNo matchNo matchAfter normalise
415-155-0132Exchange starts with 1No matchNo matchNo matchAfter normalise
(415 555-0132Unbalanced bracketNo matchAfter normaliseAfter normaliseAfter normalise
+0123456789Country code starts with 0No matchNo matchNo matchMatch
+123456789012345616 digits, over the E.164 limitNo matchNo matchNo matchNo match
415-555-0132 ext. 7ExtensionNo matchNo matchNo matchNo match
call me maybeNot a numberNo matchNo matchNo matchNo 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

phone.html
<!-- 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

phone.js
// 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 code

Leave 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

phone.py
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")))     # False

fullmatch() 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

phone.php
<?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.

phone-lib.js
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 store

Need 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.

contact.html
<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.
  • botcheck is 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.

Related