Phone Number Regex Patterns by Country
Phone number formats are a famously messy dataset. The US uses a fixed 10-digit (XXX) XXX-XXXX structure, the UK has variable-length area codes, Germany allows 10–13 digit subscriber numbers, and countries like India and Brazil have shifted prefixes — adding digits to millions of active numbers.
A single regex can’t reliably validate every global phone number without false positives or negatives. But with country-specific patterns and a foundation built on telecom standards, you can handle most real-world inputs well.
This guide gives tested, production-ready patterns for the most common formats, explains the reasoning behind each, covers the pitfalls that cause production failures, and shows how to work with SMS APIs like Twilio and AWS SNS.
Don’t deploy untested regex. Open our local Regex Tester and paste any pattern below to validate it against your data in real time, in the browser.
1. The Global Standard: E.164
The E.164 standard, defined by the ITU-T, is the safest format for storing and transmitting phone numbers. Every valid, routable number on the planet can be represented in E.164.
^\+[1-9]\d{6,14}$
The rules: a literal +, then the country code (which never starts with 0), then the subscriber number. Total length is 7–15 digits. No spaces, dashes, or parentheses.
Valid: +14155551234 (USA), +442071234567 (UK), +905551234567 (Turkey), +81312345678 (Japan)
Why E.164 matters
E.164 solves the ambiguity problem. The string 07911123456 could be a valid UK mobile number (if dialed within the UK) or invalid (if dialed internationally). Storing the full E.164 format removes that context dependence.
Major telephony APIs — Twilio, SendGrid SMS, AWS SNS, Google Cloud Telephony — require E.164 input.
| Property | E.164 | Local Format |
|---|---|---|
| Globally unique | ✅ Yes | ❌ No — depends on dialing context |
| Machine-comparable | ✅ Yes — string equality works | ❌ No — (415) 555-1234 ≠ 415.555.1234 |
| API compatibility | ✅ Twilio, AWS, Google accept it natively | ❌ Needs parsing and normalization first |
| Human readable | ❌ Unintuitive on forms | ✅ Familiar, localized |
Recommendation: accept flexible input on the frontend, normalize it to E.164 in the backend, store the E.164 string, and convert back to a localized format only for display.
2. United States & Canada (+1)
North America uses the North American Numbering Plan (NANP): a 10-digit format — a 3-digit area code plus a 7-digit subscriber number.
^(\+1)?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$
Matches: (415) 555-1234, 415-555-1234, 415.555.1234, +1 415 555 1234, 4155551234
NANP edge cases
The pattern above is permissive, but the NANP has strict rules a real validator should respect:
- Area codes never start with
0or1(012,115are invalid). - Exchange codes (the middle 3 digits) also never start with
0or1. - Numbers with
555in the0100–0199range are reserved for fictional use and never connect.
A stricter pattern enforcing these with character classes:
^(\+1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?[2-9]\d{2}[-.\s]?\d{4}$
Node.js validation and normalization
function validateAndNormalizeUSPhone(input) {
// 1. Strip formatting characters
const cleaned = input.replace(/[\s\-\(\)\.]/g, '');
// 2. Validate against the NANP rules
const pattern = /^(\+?1)?[2-9]\d{2}[2-9]\d{6}$/;
if (!pattern.test(cleaned)) {
throw new Error('400 Bad Request: Invalid North American phone number.');
}
// 3. Extract the digits
const digits = cleaned.replace(/\D/g, '');
// 4. Build the E.164 string for storage
const e164 = digits.length === 10 ? `+1${digits}` : `+${digits}`;
// 5. Build a display string for the UI
const display = `(${digits.slice(-10, -7)}) ${digits.slice(-7, -4)}-${digits.slice(-4)}`;
return { e164, display };
}
3. United Kingdom (+44)
UK numbers are tricky: variable-length area codes (2–5 digits) and total lengths of 10 or 11 digits (excluding the leading 0 or +44).
^(\+44|0)\d{10,11}$
Matches: +447911123456, 07911123456, 02071234567
UK number types
| Type | Prefix | Example | Digits after 0/+44 |
|---|---|---|---|
| Mobile | 07xxx | 07911 123456 | 10 |
| London | 020 | 020 7123 4567 | 10 |
| Other geo | 01xxx / 011x | 0161 234 5678 | 10 |
| Non-geo | 03xx | 0345 123 4567 | 10 |
| Premium | 09xx | 0906 123 4567 | 10 |
A precise pattern for UK mobile numbers (important for 2FA SMS):
^(\+44|0)7\d{9}$
4. Germany (+49)
German numbers vary in length. Mobile numbers are typically 11 digits, but landlines range from 10 to 13 depending on the area code.
^(\+49|0)\d{10,13}$
Matches: +4915112345678, 015112345678, +4930123456
German mobile prefixes
To avoid sending SMS to landlines, identify mobile networks. All mobile numbers begin with 015x, 016x, or 017x:
^(\+49|0)(1[567]\d)\d{7,8}$
5. France (+33)
France uses a consistent 10-digit structure (including the leading 0). Mobile numbers start with 06 or 07.
^(\+33|0)[1-9]\d{8}$
Matches: +33612345678, 0612345678, 0145678901
6. Turkey (+90)
Turkish numbers use a 10-digit subscriber number, with all mobile numbers starting with 5.
^(\+90|0)?\s?\(?\d{3}\)?\s?\d{3}\s?\d{2}\s?\d{2}$
Matches: +90 555 123 45 67, 0555 123 45 67, 05551234567
Turkish mobile-only pattern
^(\+90|0)?5\d{9}$
7. India (+91)
Indian mobile numbers are 10 digits, starting with 6, 7, 8, or 9.
^(\+91|0)?[6-9]\d{9}$
Matches: +919876543210, 09876543210, 9876543210
8. Brazil (+55)
Brazilian numbers include a mandatory 2-digit area code (DDD). After a recent change, mobile numbers have 9 digits (all starting with 9), while landlines keep 8.
^(\+55|0)?\d{2}9?\d{8}$
Matches: +5511987654321, 011987654321, 1187654321
9. Japan (+81)
Japanese numbers use variable-length area codes (1–5 digits). Mobile numbers start with 070, 080, or 090.
^(\+81|0)\d{9,10}$
Mobile-only:
^(\+81|0)[789]0\d{8}$
10. A Multi-Country Validation Function
A routing function that checks incoming numbers against several national patterns:
const PHONE_DICTIONARY = {
US: { pattern: /^(\+?1)?[2-9]\d{2}[2-9]\d{6}$/, code: '+1', digits: 10 },
GB: { pattern: /^(\+?44|0)\d{10,11}$/, code: '+44', digits: 10 },
DE: { pattern: /^(\+?49|0)\d{10,13}$/, code: '+49', digits: 10 },
FR: { pattern: /^(\+?33|0)[1-9]\d{8}$/, code: '+33', digits: 9 },
TR: { pattern: /^(\+?90|0)?5\d{9}$/, code: '+90', digits: 10 },
IN: { pattern: /^(\+?91|0)?[6-9]\d{9}$/, code: '+91', digits: 10 },
BR: { pattern: /^(\+?55|0)?\d{2}9?\d{8}$/, code: '+55', digits: 11 },
JP: { pattern: /^(\+?81|0)\d{9,10}$/, code: '+81', digits: 10 },
};
function validateGlobalPhone(input, targetCountryCode = null) {
// Strip non-numeric characters
const cleaned = input.replace(/[\s\-\(\)\.]/g, '');
// If a country is specified, test only that pattern
if (targetCountryCode && PHONE_DICTIONARY[targetCountryCode]) {
const { pattern } = PHONE_DICTIONARY[targetCountryCode];
return pattern.test(cleaned);
}
// Otherwise check the baseline E.164 standard
if (/^\+[1-9]\d{6,14}$/.test(cleaned)) return true;
// Fallback: test against the whole dictionary
return Object.values(PHONE_DICTIONARY).some(({ pattern }) => pattern.test(cleaned));
}
The Python equivalent
import re
PHONE_DICTIONARY = {
'US': r'^(\+?1)?[2-9]\d{2}[2-9]\d{6}$',
'GB': r'^(\+?44|0)\d{10,11}$',
'DE': r'^(\+?49|0)\d{10,13}$',
'FR': r'^(\+?33|0)[1-9]\d{8}$',
'TR': r'^(\+?90|0)?5\d{9}$',
'IN': r'^(\+?91|0)?[6-9]\d{9}$',
}
def validate_global_phone(number_string: str, country_code: str = None) -> bool:
cleaned = re.sub(r'[\s\-\(\)\.]', '', number_string)
if country_code and country_code in PHONE_DICTIONARY:
return bool(re.match(PHONE_DICTIONARY[country_code], cleaned))
# Baseline E.164 check
if re.match(r'^\+[1-9]\d{6,14}$', cleaned):
return True
# Dictionary fallback
return any(re.match(pattern, cleaned) for pattern in PHONE_DICTIONARY.values())
11. Five Common Production Mistakes
Mistake 1: forcing exact formatting
❌ Rejecting a valid 4155551234 because the user didn’t type dashes.
✅ Strip dashes, spaces, and periods with replace() before validating.
Mistake 2: a single global pattern
❌ Enforcing \d{3}-\d{3}-\d{4} on an international form.
✅ Accept E.164, or detect the country via locale, then apply the matching national pattern.
Mistake 3: trusting regex for reachability
❌ Assuming a number that passes the regex actually exists and can receive SMS. ✅ Regex validates format. For reachability, call a carrier lookup API (Twilio Lookup, NumVerify).
Mistake 4: inconsistent storage
❌ Storing (415) 555-1234 and +14155551234 as two different strings.
✅ Normalize to E.164 before the INSERT, so equality matching works for deduplication.
Mistake 5: over-strict length limits
❌ Forcing a rigid 10-digit global limit. ✅ Germany allows up to 13 digits; the UK is 10–11. Your baseline E.164 regex should allow the full 15-digit maximum.
12. Regex vs Dedicated Libraries
| Scenario | Use Regex | Use libphonenumber |
|---|---|---|
| Client-side forms | ✅ Fast, non-blocking | ❌ Heavy (250KB+ bundle) |
| Server-side API | 🟡 OK for basics | ✅ Preferred |
| Carrier detection | ❌ Not possible | ✅ Built-in lookups |
| Locale-aware UI formatting | ❌ Error-prone | ✅ Accurate |
Google’s libphonenumber is the standard for server-side validation. It’s available in Java, JavaScript (google-libphonenumber), Python (phonenumbers), and Go.
// Using the lighter libphonenumber-js port (~140KB)
import { parsePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js';
const phone = parsePhoneNumber('+14155551234');
console.log(phone.country); // 'US'
console.log(phone.formatNational()); // '(415) 555-1234'
console.log(phone.isValid()); // true
Further Reading
- Regular Expressions: The Complete Syntax Guide
- Email Address Regex: Escaping the Complexity Trap
- API Data Structures: The JSON vs YAML Debate
- Handling Forms and Inputs in Modern React
Don’t deploy patterns that block paying customers. Paste these into our local Regex Tester to verify them against your data. To measure the size of large payload files, use our Text Counter.