E.164 Phone Number Format: Complete Guide to International Normalization and Validation
The global standard for phone number formatting that ensures 99.6% SMS delivery accuracy across all countries. Learn normalization strategies, validation best practices, and implementation patterns for enterprise applications.
What is E.164 Phone Number Format?
E.164 is the international telecommunications standard for phone number formatting defined by the International Telecommunication Union (ITU). It specifies a universal format that ensures phone numbers are uniquely identifiable worldwide, enabling proper routing of calls and SMS messages across international borders.
The E.164 format consists of:
- Plus sign (+) - International call prefix
- Country code - 1-3 digit country identifier
- National number - Local phone number without trunk prefix
- Maximum 15 digits - Total length excluding the +
// E.164 Format Examples
+14155552671 // United States (San Francisco)
+442071234567 // United Kingdom (London)
+8613812345678 // China (Shanghai)
+61412345678 // Australia (Mobile)
+493012345678 // Germany (Berlin)
+81312345678 // Japan (Tokyo)Phone Number Normalization: Step-by-Step Process
Converting user input to E.164 format requires handling multiple formatting variations, country codes, and trunk prefixes. Here's the systematic approach:
Step 1: Remove All Formatting
Strip spaces, dashes, parentheses, dots, and other visual separators:
// Input examples requiring normalization
"(415) 555-2671" → Remove: ( ) - → "4155552671"
"415.555.2671" → Remove: . → "4155552671"
"415 555 2671" → Remove: spaces → "4155552671"
"+1 (415) 555-2671" → Remove: + ( ) - → "14155552671"
function stripFormatting(phone) {
return phone.replace(/[^0-9+]/g, '');
}Step 2: Handle Country Exit Codes
Remove international dialing prefixes (trunk codes) that users include:
// Country exit codes to remove
const exitCodes = {
'00': 'most countries (Europe, Africa, Asia)',
'011': 'United States, Canada',
'0011': 'Australia',
'010': 'Japan',
'810': 'Russia (some carriers)'
};
function removeExitCode(phone) {
// Check for common exit codes
if (phone.startsWith('0011')) return phone.substring(4);
if (phone.startsWith('011') || phone.startsWith('010')) return phone.substring(3);
if (phone.startsWith('00')) return phone.substring(2);
return phone;
}
// Examples
"011442071234567" → removeExitCode → "442071234567"
"00493012345678" → removeExitCode → "493012345678"
"442071234567" → removeExitCode → "442071234567" (no change)Step 3: Remove Trunk Prefixes
Many countries include a leading zero for domestic calls that must be removed for E.164:
// Countries with trunk prefixes (leading zero)
const countriesWithTrunkZero = [
'GB', // United Kingdom: 020 xxxxxxxx
'FR', // France: 0x xx xx xx xx
'DE', // Germany: 0xxx xxxxxxx
'IT', // Italy: 0xx xxxxxxx
'ES', // Spain: 0xx xxx xxxx
'NL', // Netherlands: 0xx xxxxxxx
'AU', // Australia: 0x xxxx xxxx
'JP', // Japan: 0x-xxxx-xxxx
];
function removeTrunkPrefix(phone, countryCode) {
// Only remove if country has trunk prefix
if (countriesWithTrunkZero.includes(countryCode)) {
// Remove leading zero after country code
const pattern = new RegExp(`^${countryCode}0`);
return phone.replace(pattern, countryCode);
}
return phone;
}
// Examples (UK numbers)
"+4402071234567" → removeTrunkPrefix(GB) → "+442071234567"
"+442071234567" → removeTrunkPrefix(GB) → "+442071234567" (no change)Step 4: Add Country Code
If input doesn't include country code, add it based on user's selected country:
// Country codes mapping
const countryCodes = {
'US': '1',
'GB': '44',
'FR': '33',
'DE': '49',
'IT': '39',
'ES': '34',
'JP': '81',
'CN': '86',
'AU': '61',
'IN': '91'
};
function addCountryCode(phone, userCountry) {
// Check if already has country code (starts with +)
if (phone.startsWith('+')) return phone;
// Add country code from user's selected country
const code = countryCodes[userCountry];
if (code) {
return `+${code}${phone}`;
}
// Cannot normalize - country unknown
throw new Error('Country code required for normalization');
}
// Examples (user in United States)
"4155552671" → addCountryCode(US) → "+14155552671"
"+14155552671" → addCountryCode(US) → "+14155552671" (unchanged)Complete Normalization Function
// Complete E.164 normalization
function normalizeToE164(phone, userCountry = 'US') {
// Step 1: Remove all formatting
let cleaned = phone.replace(/[^0-9+]/g, '');
// Step 2: Remove exit codes
cleaned = removeExitCode(cleaned);
// Step 3: Add country code if missing
if (!cleaned.startsWith('+')) {
cleaned = addCountryCode(cleaned, userCountry);
}
// Step 4: Remove trunk prefix
const countryCode = extractCountryCode(cleaned);
cleaned = removeTrunkPrefix(cleaned, countryCode);
// Validate E.164 format
if (!isValidE164(cleaned)) {
throw new Error('Invalid E.164 format');
}
return cleaned;
}
function extractCountryCode(phone) {
// Extract country code (first 1-3 digits after +)
const match = phone.match(/^+(d{1,3})/);
return match ? match[1] : null;
}
function isValidE164(phone) {
// Must start with +
if (!phone.startsWith('+')) return false;
// Max 15 digits after +
const digits = phone.substring(1);
if (digits.length > 15) return false;
// Must be all digits
if (!/^d+$/.test(digits)) return false;
return true;
}
// Usage examples
normalizeToE164("(415) 555-2671", "US"); // "+14155552671"
normalizeToE164("020 7123 4567", "GB"); // "+442071234567"
normalizeToE164("+1 415-555-2671", "US"); // "+14155552671"Using Google libphonenumber: Industry-Standard Library
Google's libphonenumber is the most comprehensive phone number validation library, covering 232 countries with carrier-specific rules, number type detection, and formatting capabilities.
JavaScript/TypeScript Implementation
Use libphonenumber-js for Node.js and browser applications:
// Install: npm install libphonenumber-js
import { parsePhoneNumberFromString } from 'libphonenumber-js';
function validateAndNormalize(phone, countryCode = 'US') {
// Parse the phone number
const phoneNumber = parsePhoneNumberFromString(phone, countryCode);
if (!phoneNumber) {
return { valid: false, error: 'Invalid phone number' };
}
// Check if valid
if (!phoneNumber.isValid()) {
return { valid: false, error: 'Number not valid for country' };
}
// Get E.164 format
const e164 = phoneNumber.number; // +14155552671
// Get additional information
const info = {
valid: true,
e164: e164,
national: phoneNumber.formatNational(), // (415) 555-2671
international: phoneNumber.formatInternational(), // +1 415-555-2671
country: phoneNumber.country, // US
type: phoneNumber.getType(), // 'fixed-line' | 'mobile' | 'toll-free' | 'voip'
};
return info;
}
// Usage examples
validateAndNormalize("4155552671", "US");
// {
// valid: true,
// e164: "+14155552671",
// national: "(415) 555-2671",
// international: "+1 415-555-2671",
// country: "US",
// type: "fixed-line-or-mobile"
// }
validateAndNormalize("020 7123 4567", "GB");
// {
// valid: true,
// e164: "+442071234567",
// national: "020 7123 4567",
// international: "+44 20 7123 4567",
// country: "GB",
// type: "fixed-line"
// }Python Implementation
# Install: pip install phonenumbers
import phonenumbers
from phonenumbers import carrier, timezone, geocoder
def validate_and_normalize(phone, country_code='US'):
try:
# Parse the phone number
parsed = phonenumbers.parse(phone, country_code)
# Check if valid
if not phonenumbers.is_valid_number(parsed):
return {'valid': False, 'error': 'Invalid number'}
# Get E.164 format
e164 = phonenumbers.format_number(
parsed,
phonenumbers.PhoneNumberFormat.E164
)
# Get additional information
info = {
'valid': True,
'e164': e164,
'national': phonenumbers.format_number(
parsed,
phonenumbers.PhoneNumberFormat.NATIONAL
),
'international': phonenumbers.format_number(
parsed,
phonenumbers.PhoneNumberFormat.INTERNATIONAL
),
'country_code': parsed.country_code,
'national_number': parsed.national_number,
'type': phonenumbers.number_type(parsed),
'carrier': carrier.name_for_number(parsed, 'en'),
'timezone': timezone.time_zones_for_number(parsed),
'region': geocoder.description_for_number(parsed, 'en')
}
return info
except phonenumbers.NumberParseException as e:
return {'valid': False, 'error': str(e)}
# Usage examples
validate_and_normalize("4155552671", "US")
# {
# 'valid': True,
# 'e164': '+14155552671',
# 'national': '(415) 555-2671',
# 'international': '+1 415-555-2671',
# 'country_code': 1,
# 'national_number': 4155552671,
# 'type': 1, # FIXED_LINE_OR_MOBILE
# 'carrier': 'AT&T',
# 'timezone': ['America/Los_Angeles'],
# 'region': 'United States'
# }Real-Time API Integration
For production applications, use a dedicated phone validation API with global carrier data:
// Phone-Check.app API integration
async function validatePhoneWithAPI(phone, country = 'US') {
const response = await fetch(
'https://api.phone-check.app/v1/validate',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
phone: phone,
country_code: country,
normalize: true,
check_line_type: true,
check_carrier: true
})
}
);
const data = await response.json();
return {
valid: data.valid,
e164: data.e164_format,
national: data.national_format,
country: data.country,
line_type: data.line_type, // mobile, landline, voip, toll-free
carrier: data.carrier,
carrier_name: data.carrier_name,
is_active: data.is_active,
risk_score: data.risk_score
};
}
// Usage
const result = await validatePhoneWithAPI("4155552671", "US");
console.log(result);
// {
// valid: true,
// e164: "+14155552671",
// national: "(415) 555-2671",
// country: "US",
// line_type: "mobile",
// carrier: "310", // Verizon
// carrier_name: "Verizon Wireless",
// is_active: true,
// risk_score: 0.12
// }Country-Specific Formatting Rules
Each country has unique numbering plans, trunk prefixes, and formatting conventions. Understanding these variations ensures accurate normalization and validation.
| Country | Country Code | Trunk Prefix | Number Format | E.164 Example |
|---|---|---|---|---|
| United States | +1 | None | (NXX) NXX-XXXX | +14155552671 |
| United Kingdom | +44 | 0 | 0X XXXX XXXX | +442071234567 |
| France | +33 | 0 | X XX XX XX XX | +33123456789 |
| Germany | +49 | 0 | 0XXX XXXXXXX | +493012345678 |
| Japan | +81 | 0 | XX-XXXX-XXXX | +81312345678 |
| China | +86 | 0 | XXX XXXX XXXX | +8613812345678 |
| Brazil | +55 | 0 | (XX) XXXXX-XXXX | +5511912345678 |
| Australia | +61 | 0 | X XXXX XXXX | +61412345678 |
| India | +91 | 0 | XXXXX XXXXX | +919876543210 |
| South Korea | +82 | 0 | XX-XXXX-XXXX | +821012345678 |
Common E.164 Normalization Pitfalls and Solutions
Pitfall 1: Forgetting to Remove Trunk Prefixes
Problem: Including the leading zero in E.164 format causes validation failures.
Solution: Always remove trunk prefixes (leading zero) after country code for countries that use them: UK, France, Germany, Italy, Spain, Japan, Australia.
Wrong: +4402071234567 | Correct: +442071234567Pitfall 2: Assuming All Countries Use Trunk Prefixes
Problem: Removing leading zero from countries that don't use trunk prefixes breaks the number.
Solution: Only remove trunk prefixes for specific countries. The US, Canada, and China don't use trunk prefixes.
US numbers don't have trunk prefixes: +14155552671 (not +14015552671)Pitfall 3: Storing Non-E.164 Formats
Problem: Storing phone numbers in various formats causes consistency issues and delivery failures.
Solution: Always store E.164 format in your database. Display in local format only when showing to users.
Database stores: +14155552671 | Display shows: (415) 555-2671Pitfall 4: Hardcoding Country Code Logic
Problem: Country-specific rules change frequently. Hardcoded logic becomes outdated.
Solution: Use established libraries like libphonenumber or APIs that update regularly.
Don't reinvent the wheel—use libphonenumber-jsPitfall 5: Ignoring Number Portability
Problem: Assuming carrier based on number range is incorrect when numbers are ported.
Solution: Use real-time carrier lookup APIs to detect current carrier after porting.
Ported numbers require live carrier lookup, not prefix detectionE.164 Implementation Best Practices for Production
Store Only E.164 Format
Normalize all phone numbers to E.164 before storing in your database. Display in local format only for user-facing UI.
Collect Country Separately
Ask users to select their country from a dropdown rather than parsing from the phone number. This eliminates ambiguity.
Use Established Libraries
Don't build your own normalization logic. Use libphonenumber (Java/Python) or libphonenumber-js (JavaScript) for proven accuracy.
Validate Before Sending
Always validate phone numbers before sending SMS or making calls. Real-time validation prevents 40% of delivery failures.
Provide Clear Error Messages
When validation fails, explain why and show examples of correct format for the selected country.
Implement Test Coverage
Test normalization with edge cases: international numbers, ported numbers, special formats, and invalid inputs.
Validate International Phone Numbers with 99.6% Accuracy
Real-time E.164 validation across 232 countries. Detect line types, verify carrier information, and ensure SMS delivery with global phone intelligence.
Related Articles
Landline vs Mobile vs VoIP: Complete Detection Guide
Learn how to detect phone line types for better SMS marketing and fraud prevention.
Carrier Detection & Timezone Enrichment Guide
Use carrier and timezone data to optimize marketing outreach timing and improve engagement.