@phonecheck/phone-number-validator-js
v2.1.0
Published
Validate, parse, and enrich international phone numbers — geocoding, carrier lookup, and timezone resolution. Sync (Node) + async (serverless) APIs, platform adapters, and a CLI.
Maintainers
Readme
Advanced Phone Number Validator
🚀 Validate, parse, and enrich international phone numbers — geocoding, carrier lookup, timezone resolution, and number-type detection. Built on Google libphonenumber's metadata, distributed as compact BSON tables, served from sync (Node) and async (edge / serverless) APIs, with first-class adapters for AWS Lambda, Vercel, Cloudflare Workers, GCP Cloud Functions, Netlify, and Azure Functions.
📋 Table of Contents
- Features
- Use Cases
- API / Cloud Service
- Installation
- Quick Start
- API Reference
- Examples
- Command-line Tool (
phone-validate) - Cache Management
- Custom Resource Loaders
- Serverless Usage
- Performance
- Testing
- Architecture
- Contributing
- Documentation index
Features
- ✅ E.164 / national / international parsing via libphonenumber-js (re-exported)
- ✅ Geocoder — city / region in 30+ locales with English fallback
- ✅ Original carrier mapping — Google's carrier-allocation tables
- ✅ Timezone resolution — IANA zone IDs from the E.164 number
- ✅ Number-type detection — mobile, fixed-line, VoIP, toll-free, premium…
- ✅ Sync + async parity —
geocoderfor Node,geocoderAsyncfor edge / KV - ✅ Pluggable resource loader —
node:fs,fetch, Cloudflare KV, or your own - ✅ High-performance LRU cache for deserialized BSON tables
- ✅ Six platform adapters — AWS / Vercel / Cloudflare / GCP / Netlify / Azure
- ✅
phone-validateCLI — single-shot validation + JSON logging - ✅ Strict TypeScript — zero
anyand zero non-null assertions insrc/
Use Cases
- Reduce SMS bounce rate by filtering invalid numbers before send
- Geo-route inbound calls or messages to the right region / carrier
- Detect VoIP / toll-free / premium-rate numbers in fraud screens
- Normalize numbers to E.164 for storage and analytics
- Protect signup / checkout forms from fake numbers and bots
- Embed validation in mobile, web, and back-office workflows
API / Cloud Hosted Service
We offer this — and more advanced features — as a scalable cloud API: Phone Number Verification.
Installation
bun add @phonecheck/phone-number-validator-js
# or
npm install @phonecheck/phone-number-validator-js
# or
pnpm add @phonecheck/phone-number-validator-jsRequirements (consumers)
- Node.js >= 22 (Maintenance LTS) — the published bundle is plain Node.js + ESM/CJS
- TypeScript >= 4.0 (for TypeScript users)
Requirements (contributing)
- Bun >= 1.3 (test runner, package manager, dev tooling)
- Node.js >= 24 only needed for
semantic-releaseduring the publish step
Build System
- Rollup builds CJS + ESM bundles for the main package and per-platform serverless adapters
bun testfor the unit + isolated suites (no jest, no ts-jest)- BSON resource tables (geocodes, carrier mappings, timezones) ship under
resources/next to the publisheddist/
Quick Start
import {
carrier,
geocoder,
parsePhoneNumberFromString,
timezones,
} from '@phonecheck/phone-number-validator-js';
const phone = parsePhoneNumberFromString('+41431234567');
geocoder(phone); // → "Zurich"
geocoder(phone, 'de'); // → "Zürich"
carrier(parsePhoneNumberFromString('01701234567', 'DE'));
// → "T-Mobile"
timezones(parsePhoneNumberFromString('+12124567890'));
// → ["America/New_York"]For a one-shot enrichment call:
import { enrichPhoneNumber, parsePhoneNumberFromString }
from '@phonecheck/phone-number-validator-js';
const phone = parsePhoneNumberFromString('+8619912345678');
await enrichPhoneNumber(phone, { carrierLocale: 'zh' });
// {
// geocode: "Hubei",
// carrier: "中国电信",
// timezones: ["Asia/Shanghai", ...]
// }API Reference
Core Functions
geocoder(phone, locale = 'en'): string | null
Resolve a city / region for the given parsed phone number. Returns null for
landline-only ranges, unknown country codes, or invalid input. Falls back to
English when the locale-specific table is missing.
carrier(phone, locale = 'en'): string | null
Resolve the original carrier from Google's carrier-allocation tables.
Note this does not reflect ports — see the upstream FAQ.
Returns null for landlines and unmapped ranges.
timezones(phone): string[] | null
Resolve one or more IANA timezone IDs for the given E.164 number.
Async variants
Same signatures, returning a Promise:
geocoderAsync(phone, locale?)carrierAsync(phone, locale?)timezonesAsync(phone)enrichPhoneNumber(phone, options?)— runs all three in parallel
The async variants only require loader.loadResource. The sync ones require
loader.loadResourceSync. The bundled NodeFsResourceLoader provides both.
Cache
import {
clearCache,
DEFAULT_CACHE_SIZE, // 100
getCacheSize,
getCacheStats, // { size, maxSize }
setCacheSize,
} from '@phonecheck/phone-number-validator-js';The cache stores deserialized BSON tables (one per country-code/locale
pair). Resize for memory-tight environments, clear it for tests / long-running
workers.
Resource loaders
import { NodeFsResourceLoader, setResourceLoader }
from '@phonecheck/phone-number-validator-js';
setResourceLoader(new NodeFsResourceLoader({ resourcesDir: '/custom/path' }));The Node entry installs a default loader pointing at the bundled resources/.
Swap it for serverless / edge — see Custom Resource Loaders.
Re-exports
Everything from libphonenumber-js is re-exported under the same name —
parsePhoneNumberFromString, parsePhoneNumberWithError, PhoneNumber,
CountryCode, formatters, etc. One import covers parsing + enrichment.
Locale types
GeocoderLocale and CarrierLocale are union types listing the supported
locales (auto-generated by scripts/prepare-metadata.ts). Use them for
exhaustive switches:
import type { GeocoderLocale } from '@phonecheck/phone-number-validator-js';Examples
Detecting number type
import { parsePhoneNumberFromString } from '@phonecheck/phone-number-validator-js';
const phone = parsePhoneNumberFromString('+14155552671');
phone?.getType(); // 'FIXED_LINE_OR_MOBILE'Validating + formatting in one pass
import { parsePhoneNumberFromString } from '@phonecheck/phone-number-validator-js';
function normalize(input: string): string | null {
const phone = parsePhoneNumberFromString(input, 'US');
if (!phone?.isValid()) return null;
return phone.format('E.164');
}
normalize('(415) 555-2671'); // '+14155552671'
normalize('not a number'); // nullIterating with locale fallback
import { geocoder, parsePhoneNumberFromString }
from '@phonecheck/phone-number-validator-js';
const numbers = ['+41431234567', '+8619912345678', '+12124567890'];
for (const n of numbers) {
console.log(n, '→', geocoder(parsePhoneNumberFromString(n), 'de'));
}
// +41431234567 → Zürich (de table)
// +8619912345678 → Hubei (en fallback — no de carrier table for 86)
// +12124567890 → New York (en fallback)Command-line Tool (phone-validate)
Installing the package globally exposes a phone-validate binary:
$ bun add -g @phonecheck/phone-number-validator-js
$ phone-validate +14155552671✓ VALID +14155552671
Summary
country: US (+1)
type: FIXED_LINE_OR_MOBILE
Formatted
E.164: +14155552671
national: (415) 555-2671
international: +1 415 555 2671
RFC3966: tel:+14155552671
Enrichment
geocode: San Francisco
timezones: America/Los_AngelesOutput formats:
phone-validate +14155552671 --format json --quiet --no-log-file | jq
phone-validate "(415) 555-2671" --country US --format textExit codes: 0 valid, 1 invalid / unparseable, 2 bad CLI args.
Run phone-validate --help for the full flag list.
Cache Management
import {
clearCache,
getCacheStats,
setCacheSize,
} from '@phonecheck/phone-number-validator-js';
setCacheSize(500); // default 100
getCacheStats(); // { size: 17, maxSize: 500 }
clearCache(); // drop all entriesThe cache is keyed by the loader path (geocodes/en/41.bson, …) so a single
process shares hits across the Node entry and any serverless adapter that
also runs in it.
Custom Resource Loaders
Implement ResourceLoader to host the BSON tables anywhere — KV, R2, S3, a
public CDN, an in-memory map. The loader is the only thing that changes
between runtimes; the resolver is the same.
import {
setResourceLoader,
type ResourceLoader,
} from '@phonecheck/phone-number-validator-js/serverless';
class S3Loader implements ResourceLoader {
async loadResource(path: string): Promise<Uint8Array | null> {
const res = await fetch(`https://my-bucket.s3.amazonaws.com/phone/${path}`);
if (res.status === 404) return null;
if (!res.ok) throw new Error(`S3 ${res.status} for ${path}`);
return new Uint8Array(await res.arrayBuffer());
}
}
setResourceLoader(new S3Loader());Two implementations ship in the package:
| Loader | Backed by | Sync? | Use when |
|-|-|-|-|
| NodeFsResourceLoader | node:fs | ✓ | Running on Node.js with the bundled resources/ |
| FetchResourceLoader | fetch | ✗ | Tables hosted on a CDN / R2 / S3 with public reads |
| KvResourceLoader | Workers KV-shape | ✗ | Cloudflare KV or any KV-shaped store |
Serverless Usage
For deployment recipes — wrangler.toml, Vercel routes, lambda zips, GCP function entries, Netlify redirects, Azure host.json — see SERVERLESS.md.
A minimal Cloudflare Worker:
// src/worker.ts
import worker from '@phonecheck/phone-number-validator-js/serverless/cloudflare';
// Bind PHONE_RESOURCES (KV) in wrangler.toml. The adapter auto-installs a
// loader from it.
export default worker;# wrangler.toml
[[kv_namespaces]]
binding = "PHONE_RESOURCES"
id = "..."$ curl -X POST https://my.workers.dev/ \
-H 'content-type: application/json' \
-d '{ "phoneNumber": "+14155552671" }'
{ "success": true, "data": { "valid": true, ... } }Performance
- BSON tables are loaded once per
country-code/localeand cached as decodedDocuments. - Default cache size is 100 entries — enough for the top ~50 country codes in English and a fallback locale at the same time.
- Per-lookup cost after warm-up is a single object access + a
whileloop over the prefix (~2-4 iterations on average). - The pure verifier bundle is ~80 KB minified (libphonenumber-js
dominates); Workers-only deployments can shrink further by importing
/serverless/verifierdirectly instead of/serverless.
Testing
bun install
bun run typecheck
bun run lint
bun run test # unit + isolated, no network, no real fs
bun run test:integration # opt-in: requires bundled resources/The unit suite uses an in-memory MockResourceLoader so it's fast (under a
second) and deterministic. The isolated suite exercises the bundled
NodeFsResourceLoader against the real resources/ directory.
For the full test layout, run commands, BSON-key conventions, and CI matrix, see TESTING.md.
Architecture
For the module map, request lifecycle, adapter pattern, and how the sync/async API parity works, see ARCHITECTURE.md.
Contributing
- Read AGENTS.md — code style, branching, commit conventions
- Branch off
develop; PRs targetdevelop bun run typecheck && bun run test && bun run buildmust be clean- Conventional Commits drive the release:
feat:,fix:,perf:are releasable;chore:,docs:,test:are not - Pre-commit hook runs Biome via
lint-staged; please don't--no-verify
Documentation index
| Doc | What's in it | | - | - | | README.md | Public API, quick start, CLI, examples | | ARCHITECTURE.md | Module map, data flow, adapter pattern | | SERVERLESS.md | AWS / Vercel / Cloudflare / GCP / Netlify / Azure deployment | | TESTING.md | Test layout, run commands, BSON-key conventions | | AGENTS.md | Code-style rules, code-pattern conventions | | examples/README.md | Runnable example index | | CHANGELOG.md | Release history (auto-managed) | | LICENSE.md | License terms |
License
See LICENSE.md.
