Normalize messy phone numbers, flag invalid ones, and generate tel links with plain JavaScript.
Scraped or hand-entered phone data is messy. While building a directory of bank branches, I found numbers that were one digit short. Users would have tapped a tel: link and reached nothing.
Here is a small toolkit to prevent that.
Normalize
function normalizeUS(raw) {
let digits = String(raw).replace(/\D/g, "");
if (digits.length === 11 && digits.startsWith("1")) digits = digits.slice(1);
return digits.length === 10 ? digits : null; // null = invalid
}
normalizeUS("(281) 646-6600"); // "2816466600"
normalizeUS("+1 281-646-6600"); // "2816466600"
normalizeUS("281-646-660"); // null (only 9 digits)
Format for display and links
function formatUS(digits) {
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;
}
function telHref(digits) {
return `tel:+1${digits}`;
}
Audit a whole dataset
function auditPhones(rows) {
return rows
.map((r) => ({ ...r, clean: normalizeUS(r.phone) }))
.filter((r) => r.clean === null);
}
const broken = auditPhones([
{ name: "Branch A", phone: "281-646-6600" },
{ name: "Branch B", phone: "972-690-860" },
]);
console.log(broken); // [{ name: "Branch B", ... }]
Go further
For international support or strict validation, use libphonenumber-js:
import { parsePhoneNumberFromString } from "libphonenumber-js";
const p = parsePhoneNumberFromString("281-646-6600", "US");
console.log(p?.isValid(), p?.format("E.164"));
Takeaways
- Validate at import time, not at render time.
- Store the clean digits and format on output.
- Log invalid rows so you can fix the source.
- A US number is exactly 10 digits after removing the country code.
Top comments (0)