Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

Parsing and validating North American (NANP) phone numbers in JavaScript without rejecting real callers

Many web forms validate U.S. phone numbers by stripping non-digits and checking /^\d{10}$/. That accepts 000-000-0000, 911-555-0100, and 212-988-1234—none of which can be assigned to a subscriber—while stricter regexes c

Many web forms validate U.S. phone numbers by stripping non-digits and checking /^\d{10}$/. That accepts 000-000-0000, 911-555-0100, and 212-988-1234—none of which can be assigned to a subscriber—while stricter regexes copied from Stack Overflow often reject newer overlay area codes.

Here is how the North American Numbering Plan (NANP) actually structures a 10-digit number (NPA-NXX-XXXX), along with a small JavaScript parser that checks structural rules, flags reserved ranges, and separates geographic area codes from toll-free codes.

The three parts of a 10-digit NANP number

Under NANPA's numbering plan, a 10-digit number is split into:

  1. NPA (Numbering Plan Area / area code, 3 digits NXX):
    • The first digit N must be 2–9 (0 and 1 are used for operator and trunk prefixes).
    • Codes ending in 11 (211 through 911) are N11 service codes, not area codes.
    • Codes with 9 as the middle digit (N9X) are reserved for future NANP expansion.
    • 555, 958, and 959 are reserved non-geographic/test NPAs.
  2. NXX (Central Office / exchange code, 3 digits):
    • The first digit must also be 2–9.
    • Exchanges ending in 11 (N11) are excluded.
    • 988 is reserved nationwide as the Suicide & Crisis Lifeline prefix. In 2020 (FCC 20-100), the FCC ordered mandatory 10-digit dialing across 87 area codes that previously permitted 7-digit local dialing and had assigned 988 as a central office prefix, before 988 went live nationwide in July 2022.
  3. XXXX (Line number, 4 digits 0000–9999):
    • Within the 555 exchange, 555-0100 through 555-0199 are set aside for fictional/entertainment use.

The parser

const TOLL_FREE = new Set(["800", "833", "844", "855", "866", "877", "888"]);

function parseNanpNumber(input) {
  const digits = String(input).replace(/\D/g, "");
  const ten = digits.length === 11 && digits.startsWith("1") ? digits.slice(1) : digits;
  if (ten.length !== 10) return { valid: false, reason: "wrong_length" };

  const npa = ten.slice(0, 3);
  const nxx = ten.slice(3, 6);
  const line = ten.slice(6);

  if (npa[0] < "2" || npa[0] > "9") return { valid: false, reason: "invalid_npa_first_digit" };
  if (npa.slice(1) === "11") return { valid: false, reason: "n11_service_code" };
  if (npa[1] === "9") return { valid: false, reason: "reserved_expansion_code" };
  if (npa === "555" || npa === "958" || npa === "959") return { valid: false, reason: "reserved_npa" };

  if (nxx[0] < "2" || nxx[0] > "9") return { valid: false, reason: "invalid_nxx_first_digit" };
  if (nxx.slice(1) === "11") return { valid: false, reason: "n11_central_office_code" };
  if (nxx === "988") return { valid: false, reason: "reserved_988_crisis_prefix" };
  if (nxx === "555" && Number(line) >= 100 && Number(line) <= 199) {
    return { valid: false, reason: "fictional_555_01xx" };
  }

  return {
    valid: true,
    e164: `+1${ten}`,
    npa,
    nxx,
    line,
    kind: TOLL_FREE.has(npa) ? "toll-free" : npa === "900" ? "premium" : "geographic_or_non_geo",
  };
}

Testing real vs. reserved numbers

const samples = [
  "(212) 456-7890",   // Valid Manhattan format
  "+1-833-240-9100",  // Valid toll-free
  "202-555-0143",     // Fictional 555-01XX range
  "617-988-2000",     // Reserved 988 prefix
  "411-234-5678",     // N11 service code in NPA position
  "215-911-3000",     // N11 service code in NXX position
];

for (const s of samples) {
  console.log(s, "->", parseNanpNumber(s));
}

Output:

Input valid e164 kind / reason
(212) 456-7890 true +12124567890 geographic_or_non_geo
+1-833-240-9100 true +18332409100 toll-free
202-555-0143 false — fictional_555_01xx
617-988-2000 false — reserved_988_crisis_prefix
411-234-5678 false — n11_service_code
215-911-3000 false — n11_central_office_code

Why structural validity is not the same as an active area code

A number like 222-234-5678 passes every structural NXX rule above, yet 222 is not an assigned U.S. area code. NANPA's public database lists 372 geographic area codes in service across the 50 U.S. states and D.C. (plus 6 in U.S. territories and 7 active toll-free codes: 800, 833, 844, 855, 866, 877, and 888).

Even historic East Coast codes added overlays as their original pools filled up:

  • Manhattan (212 area code guide): One of the 86 original 1947 codes; shares Manhattan (except Marble Hill) with overlays 646, 332, and citywide 917.
  • Boston (617 area code guide): Assigned in 1947; shares Boston and surrounding eastern Massachusetts communities with overlay 857.
  • Philadelphia (215 area code guide): Assigned in 1947; shares Philadelphia and its suburbs with overlays 267 and 445.
  • Washington, D.C. (202 area code guide): Assigned in 1947; stood alone for 74 years before overlay 771 entered service in 2021.
  • Baltimore & Eastern Maryland (410 area code guide): Split from 301 in 1991 and now shares its territory with overlays 443 and 667.

If you want to validate against the exact list of active U.S. codes instead of only structural rules, you can browse the complete state-by-state table on the all U.S. area codes by state page or search individual metro guides in the U.S. area code directory.

Disclosure: I work with the team at EaseDial, which publishes the area code directory and state dataset linked above.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.