A CRM that refuses '(415) 555-2671'

The import fails at row 1. The CRM's validation message says something polite about phone number format, and the column it is rejecting contains perfectly good phone numbers — formatted the way humans write them, with parentheses, dots, dashes, extensions, maybe a stray 1 in front of a US number, maybe a UK number with its 020 trunk prefix and no country code at all. Every one of those is readable to a person. Almost none of them are acceptable to a system.

The format systems want is E.164: plus sign, country code, subscriber number, digits only, nothing else. It is the ITU's international standard, it is what Twilio expects, what Salesforce fields prefer, what WhatsApp registration requires. Converting a messy human column into clean E.164 is pure parsing work — country rules, trunk prefixes, digit validation — and it is exactly what this tool does, in bulk, in your browser, with an invalid-row report for the numbers it could not confidently fix.

The engine underneath is the interesting part: real phone normalization is not string formatting, it is numbering-plan knowledge. The rules differ per country — the US uses 10 digits with an area code that cannot start with 0 or 1, the UK drops its leading 0 when you add +44, Germany does the same with +49, and mobile-only countries skip trunk prefixes entirely. Guessing with regexes produces confident garbage, which is why this tool is built on libphonenumber-js, the JavaScript port of Google's numbering library, the same metadata that Android uses to parse numbers correctly worldwide.

Below: the three output formats defined precisely, the country and extension machinery, a worked seven-number example with the arithmetic in the open, the fake-number ranges you should use in your own test data, and the honest boundary between structural validation and carrier lookup.

Normalizing numbers in five steps

The tool sits above this section. The standard run on a contact column:

  1. Paste or load the numbers

    A bare list (one number per line) or a full CSV with a phone column. In CSV mode the other columns travel with the rows — names, emails, notes — so the clean export remains a complete contact file.

  2. Set the default country

    The picker covers all numbering plans. It applies only to numbers lacking a + prefix or an international 00 prefix; explicitly international numbers override it automatically.

  3. Choose the output format

    E.164 for databases and APIs, national for local display, international for human-readable cross-border lists. Extensions are preserved in all three via the RFC 3966 ;ext= syntax.

  4. Run and read the report

    Valid rows normalize instantly with their country noted; invalid rows get reasons — too few digits, invalid area code pattern, unparseable characters — and land in a separate review file, never silently dropped.

  5. Export the clean column

    Download the normalized CSV with the original value kept beside the normalized one, or copy the clean list alone. The original-plus-normalized pairing is the audit trail: any row can be checked by eye.

The original-value column in the export deserves a note: keeping input beside output turns every normalization into a reviewable operation. When a downstream system complains about one number, you can see exactly what the raw value was — which is the difference between a two-minute fix and an archaeology project.

E.164 and the other formats, precisely

The format definitions, stated exactly, because the differences are the whole subject. E.164 is the ITU-T recommendation that defines international numbers: a country code of one to three digits, a subscriber number, and a hard total limit of 15 digits, written with a leading plus. The plus is not decoration — it is the signal that what follows is a fully qualified international number rather than a local convention. +14155552671 is E.164 for a US number; +442079460958 is E.164 for London; +81312345678 is Tokyo.

FormatSame number as…Where it belongs
E.164+14155552671Databases, CRM fields, SMS and WhatsApp APIs, dedupe keys
National(415) 555-2671Local display, printed flyers, in-country UI
International+1 415 555 2671Cross-border lists, conference materials, support rosters
RFC 3966 tel: URItel:+14155552671;ext=123Web links, click-to-call, contact schemas
One number, four spellings. The first is the one systems agree on; the rest are for humans.

The trunk-prefix rule is where most manual conversions go wrong, so here it is explicitly: many countries write a leading 0 before domestic numbers — 020 for London, 030 for Berlin, 02 for Paris — and that 0 exists only inside the country. Converting to international means dropping it and prepending the country code: London's 020 7946 0958 becomes +44 20 7946 0958, not +44 020…. The tool applies this per country because the rule is numbering-plan knowledge, not formatting taste; and the one numbering family without a trunk prefix — the North American plan — is exactly the one where people wrongly add a 0 out of habit.

The NANP — the North American Numbering Plan covering the US, Canada and much of the Caribbean — deserves its own paragraph because so much data is NANP data. Its numbers are exactly 10 digits under country code 1, structured NPA-NXX-XXXX, where both the area code (NPA) and the exchange (NXX) must start with a digit from 2 through 9 — never 0 or 1. That constraint is what structural validation checks: 415-555-2671 is a plausible NANP number; 115-555-2671 is not, and the tool says so. The N11 codes (211, 311, 911 and friends) are service codes, not assignable subscriber numbers, and fail validation accordingly.

Worked example: seven messy numbers

A realistic column: US numbers in three different human formats, one with an extension, one with a leading 1, one international number with a plus, one UK number written domestically, and one broken value. Default country: United States.

One column, three output formats, one rejection

Input

(415) 555-2671
415-555-2671 x123
415.555.2671
1-415-555-2671
+44 20 7946 0958
020 7946 0958
555-0100

Output

E.164 output (6 of 7 valid):
+14155552671
+14155552671;ext=123
+14155552671
+14155552671
+442079460958
+442079460958  (UK default applied — see note)
[INVALID: 555-0100 — 7 digits; NANP numbers require 10]

National output (US rows): (415) 555-2671 ×4 · UK rows: 020 7946 0958
International output: +1 415 555 2671 ×4 · +44 20 7946 0958 ×2

Report: 7 rows, 6 valid, 1 invalid with reason. Rows 1–4 collapse to one number in four spellings — a dedupe of 4→1 discovered as a side effect. Row 6 needed country = United Kingdom to resolve its trunk prefix 020; run it in a UK-default pass if your file is mostly US.

The details worth reading: rows 1 through 4 are the same phone number wearing four outfits — parentheses, dashes, dots, and the long-distance 1 that NANP callers dial but E.164 does not want. All four normalize to +14155552671, which means this column secretly contained three duplicates, and the report says so. The extension in row 2 rides along correctly as ;ext=123 instead of corrupting the digits. The UK row with the + prefix resolved itself with no country knowledge needed, while its twin without the + required the UK default — the exact asymmetry the country picker exists to manage.

The invalid row is invalid for a structural reason you can verify: 555-0100 is seven digits, and NANP numbers are ten. It is also, not coincidentally, inside the 555-0100 to 555-0199 range that NANP reserves for fictional use — which makes it a perfect test value and a terrible real contact, and a nice segue to the test-data note below.

Country codes and the extension problem

Country handling has one rule for explicitly international numbers and another for domestic ones. A number starting with + is already country-qualified: +44… is UK regardless of any picker, and the tool reads the country straight off the digits. A number starting with 00 — the international dialing prefix used across Europe and much of the world — is likewise international, with the 00 stripped and replaced by +. Everything else is domestic notation and needs a country context; that is the picker's job, and it defaults to your browser's locale as a usually-correct guess.

Extensions are their own small discipline. A desktop phone column in any real organization contains values like 415-555-2671 x123, 415-555-2671 ext. 123, and 415-555-2671 #123 — three spellings of the same underlying number plus extension 123. The parser recognizes x, ext, extension, and the hash separator, extracts the digits, and formats the result per RFC 3966: +14155552671;ext=123. The alternative — appending the extension digits to the number — produces a 13-digit monster that fails every validation downstream and can even dial a wrong number in plans where digit count is meaningful. Extensions and numbers are different things; the tool keeps them different.

Vanity numbers get the same two-column respect: 1-800-FLOWERS decodes to +18003569377 (F=3, L=5, O=6, W=9, E=3, R=7, S=7 — the keypad map applied letter by letter), with the original spelling preserved beside the digits. The decode is deterministic keypad arithmetic, and keeping both spellings means your marketing team can still recognize the number while your database stores the one true form.

Edge cases: short codes, letters, and fake numbers

The input shapes that stress a normalizer, and what this one does with each:

  • Short codes: 5- and 6-digit numbers like 555882 or text-to-donate codes are not telephone numbers in the E.164 sense and cannot be normalized — they are flagged as short codes rather than mangled into fake E.164.
  • Alphanumeric soup: values typed as call bob land in the invalid file with a reason, not an exception. A normalizer that crashes on garbage input is a normalizer that cannot run on real spreadsheets.
  • Two numbers in one cell: 415-555-2671 / 415-555-2682 — the parser takes the first and flags the cell as containing multiple values, because silently keeping one of two phone numbers is data loss wearing a success message.
  • The plus-less international: 44 20 7946 0958 typed without the + parses as an invalid 11-digit US-style number under a US default — the report's reason string points at the fix (run a UK-default pass), and the row stays in the review file rather than being silently reformatted into a wrong number.
  • Leading zeros in subscriber digits: a US number typed as 0415… is not a thing in NANP, and the tool refuses to guess whether the 0 is damage or a country clue.
  • Unicode digits: numbers pasted with fullwidth or Arabic-Indic digit forms are normalized to ASCII digits before parsing — a genuinely international contact file contains these more often than anyone expects.

The refusal pattern across all of these is deliberate. A normalizer's failure mode should be a labeled rejection, never a confident invention — because the difference between a wrong number and a right one is invisible until someone dials.

Who normalizes phone lists

The audiences, in the order they arrive:

  • CRM administrators preparing contact imports — Salesforce, HubSpot and friends reject or silently mangle non-E.164 values, and the pre-import normalization pass is the difference between a clean import and a support ticket.
  • Marketing and messaging teams loading SMS and WhatsApp campaigns, where the APIs require E.164 strictly and a malformed column fails the entire batch upload.
  • Recruiters and staffing agencies normalizing candidate phone columns pulled from multiple job boards, each board formatting numbers its own way.
  • Customer support leads cleaning ticket exports before routing callbacks, where duplicates in different formats mean one customer called twice.
  • Event organizers with international attendee lists — the classic mixed-country file, where the + prefixed rows and the domestic rows need different handling.
  • Developers seeding test databases, converting human-entered fixtures into the canonical form their schema validates against.

The unifying motive: a system downstream enforces a format, and the file upstream contains humans. The normalizer is the translation, and its trustworthiness lives in the numbering-plan metadata — which is why we ship the real library rather than a regex.

Regex versus libphonenumber: why guessing fails

The tempting implementation is a regex: strip non-digits, prepend a country code, done. It works on a demo and fails on a contact list, and the reasons are worth naming. First, length validation is country-specific: a valid French mobile is 9 digits after the trunk 0, a valid UK national number is 10, and both become different-length E.164 strings. A regex that does not know the plans cannot tell 9-digit damage from a 9-digit Czech number. Second, area-code and exchange rules carry meaning: NANP's 2–9 constraint on leading digits catches transposed typos that pure digit-count validation passes. Third, trunk prefixes differ — dropping the domestic 0 for +44 and +49 but not for +1 — and a uniform rule is wrong for everyone.

This tool is built on libphonenumber-js, the maintained JavaScript port of Google's libphonenumber — the numbering-plan metadata that Android ships with, covering every country's rules in a data file of roughly 145 KB that loads once with the page and then does all its work locally. The same library family is what open-source projects in every language embed for this problem, and it is the difference between formatting and understanding. When the tool says a UK number is valid, that is the numbering plan talking, not a character count.

What that buys you concretely: per-country validation with real rules, correct trunk-prefix handling, extension parsing, vanity decoding, and invalid-row reasons that name the actual problem — all offline, all in-tab. The paid API services do one thing this tool deliberately does not: carrier and reachability lookups over the network. That is their business; structure is ours.

No uploads, no APIs, no leaks

Phone numbers are personal data — under GDPR, under CCPA, under every privacy framework that has opinions about identifiers — and a contact column usually travels with names and email addresses beside it. The normalizer's design point is that none of it moves: the numbering metadata loads with the page, and parsing, validation and formatting all run as local JavaScript. There is no API call per number, no batch upload, no vendor log with your customers' phone numbers in it. Disconnect from the network after the page loads and everything still works — an experiment we recommend running once, because seeing is believing.

The contrast with the paid validator model is worth stating plainly, since the search results for phone formatting are full of them: those services exist to do carrier lookups, which genuinely require network queries and per-check pricing. But a file of 50,000 numbers pasted into their upload form has crossed a boundary regardless of whether you buy the lookups. Structure-first cleaning belongs client-side; existence verification is the paid step you run after — on the smaller, already-clean list.

FAQ

The questions that arrive from people mid-import.

What is E.164 and why does my CRM want it?

E.164 is the ITU standard for international telephone numbers: a plus sign, then the country code, then the subscriber number, with a hard limit of 15 digits total — +14155552671 for a San Francisco number, +442079460958 for London. CRMs and messaging APIs demand it because it is the one spelling of a phone number that is globally unique and unambiguous. Two differently formatted numbers may or may not be the same person; two E.164 strings are the same person if and only if the strings are equal.

How does the tool know the country when a number has no plus sign?

You set a default country, and the tool applies that country's rules for trunk prefixes and lengths — 10 digits with a valid area code becomes +1…, a 020-prefixed number with country United Kingdom becomes +44 20…, and so on. Numbers that already carry a + prefix or a 00 international prefix override the default. If a file mixes countries without prefixes, run it once per country with the picker set accordingly.

Does it handle extensions like 415-555-2671 x123?

Yes. Extensions are parsed out of the input (x, ext, extension, and the hash separator are all recognized), kept separate from the main number, and emitted in RFC 3966 style as ;ext=123 after the E.164 value. The extension never leaks into the digits of the number itself — a classic corruption in naive conversions where x123 becomes part of the subscriber number.

What is the difference between the three output formats?

E.164 is the machine format: +14155552671, digits only, globally unique. National is how the number is written inside its own country: (415) 555-2671. International is the hybrid for humans abroad: +1 415 555 2671 — the country code spelled out with spacing. Use E.164 for databases and APIs, national for local display, international for cross-border lists like event attendees or support rosters.

Does the normalizer verify that numbers are real and reachable?

No. It validates structure — plausible length for the country, valid area-code and exchange patterns where the national numbering plan defines them, correctly parsed country codes. Whether the number is assigned, in service, or reachable is carrier-side knowledge that requires API lookups with per-query costs. The tool does structure; it says so plainly; and unlike paid lookup services it never needs your list to leave the machine.

Can it convert vanity numbers like 1-800-FLOWERS?

Yes — letters on a keypad are decoded to their digits (FLOWERS becomes 3569377) with the original spelling preserved in a separate column so no information is lost. The decoded digits are what every system downstream needs; the vanity text is what humans recognize. You get both, correctly separated.

My file has numbers from several countries. What is the best approach?

Normalize with the most common country as the default, then re-run the flagged rows — numbers that failed validation under the default country are usually from the minority countries, and the second pass with a different country picker resolves them. Files where every number carries a + prefix need no default at all; the country comes embedded in every row.

Is my list of phone numbers uploaded anywhere?

No. The normalization engine and its numbering-plan metadata load once into the page, and all parsing and formatting happen in your browser. Phone lists are personal data, and the entire design here is that they never cross the network — load the page, go offline, and it still normalizes.