ts-phonenumber
v2.0.0
Published
TypeScript-first phone-number parsing/formatting/validation library based on Google's libphonenumber
Maintainers
Readme
ts-phonenumber
The fastest phone number library for JavaScript/TypeScript. Parse, validate, and format phone numbers with blazing speed and tiny bundles.
Features
- Blazing fast — Up to 11x faster than alternatives (see benchmarks)
- TypeScript-first — Full type safety with comprehensive type definitions
- Dual API — Async for lazy loading, sync for maximum performance
- Tiny bundles — ~5KB core + on-demand metadata via
import() - Modern runtimes — Node.js 20+, Bun, and modern browsers
- CLI included — Command-line tool for validation and formatting
Installation
npm install ts-phonenumber
# or
pnpm add ts-phonenumber
# or
yarn add ts-phonenumberUsage
Synchronous API (Maximum Performance)
For servers, CLIs, and performance-critical code. Pre-load metadata once, then use sync functions:
import { parseSync, validateSync, formatSync, registerMetadata } from "ts-phonenumber"
// Pre-load metadata (do this once at startup)
import DE from "ts-phonenumber/metadata/countries/DE"
registerMetadata(DE)
// Now use sync functions - no async overhead!
const parsed = parseSync("+491701234567")
console.log(parsed.regionCode) // "DE"
console.log(parsed.type) // "mobile"
const isValid = validateSync("+491701234567")
console.log(isValid.isValid) // true
const formatted = formatSync(parsed, "international")
console.log(formatted) // "+49 170 123 4567"Async API (Lazy Loading)
For web apps where you want to load metadata on-demand:
import { parse, validate, format, getType } from "ts-phonenumber"
// Metadata is loaded automatically when needed
const parsed = await parse("+491701234567")
console.log(parsed)
// {
// countryCode: 49,
// nationalNumber: "1701234567",
// regionCode: "DE",
// type: "mobile",
// isValid: true,
// rawInput: "+491701234567"
// }
// Validate a phone number
const result = await validate("+491701234567")
console.log(result.isValid) // true
console.log(result.type) // "mobile"
// Format a phone number
const e164 = await format("+491701234567", "e164")
console.log(e164) // "+491701234567"
const intl = await format("+491701234567", "international")
console.log(intl) // "+49 170 123 4567"
const national = await format("+491701234567", "national")
console.log(national) // "0170 123 4567"
// Get number type
const type = await getType("+491701234567")
console.log(type) // "mobile"Parsing National Numbers
When parsing numbers without a country code, provide a default region:
import { parse } from "ts-phonenumber"
const parsed = await parse("0170 1234567", { defaultRegion: "DE" })
console.log(parsed.countryCode) // 49
console.log(parsed.nationalNumber) // "1701234567"Loading Metadata
Metadata is loaded on-demand when you first use a region. You can also preload metadata:
import { loadRegionMetadata, preloadRegions, loadMetadataBundle } from "ts-phonenumber"
// Load a single region
await loadRegionMetadata("DE")
// Preload multiple regions
await preloadRegions(["DE", "AT", "CH"])
// Load a group bundle (loads all regions in the group)
await loadMetadataBundle("DACH")Type Checking
import { isMobile, isLandline, isVoIP } from "ts-phonenumber"
console.log(await isMobile("+491701234567")) // true
console.log(await isLandline("+493012345678")) // true
console.log(await isVoIP("+493212345678901")) // trueCLI Usage
The package includes a CLI for quick validation and formatting:
# Validate a number
ts-phonenumber validate "+491701234567"
ts-phonenumber validate "0170 1234567" --region DE
# Parse and show details
ts-phonenumber parse "+491701234567"
ts-phonenumber parse "0170 1234567" --region DE --json
# Format a number
ts-phonenumber format "+491701234567" --format e164
ts-phonenumber format "+491701234567" --format international
ts-phonenumber format "+491701234567" --format national
ts-phonenumber format "+491701234567" --format rfc3966
# Get number type
ts-phonenumber type "+491701234567"Supported Number Types
This library only supports the following number types:
"landline"- Fixed-line/geographic numbers"mobile"- Mobile phone numbers"voip"- Voice over IP numbers
All other types (toll-free, premium rate, shared cost, etc.) are treated as "invalid".
Scope & Testing Transparency
Why This Library?
Google's libphonenumber is the industry standard, but it comes with trade-offs:
| Aspect | libphonenumber | ts-phonenumber | | --------------- | ------------------------------ | --------------------------------- | | Performance | Baseline | 5-11x faster (see benchmarks) | | Language | Java (with JS port) | TypeScript-native | | Bundle size | ~200-300KB (all metadata) | ~5KB core + on-demand metadata | | Number types | All (toll-free, premium, etc.) | Only LANDLINE, MOBILE, VOIP | | API style | Synchronous only | Dual: async + sync | | Runtime | Any | Node 20+, Bun, modern browsers |
Our focus: Most applications only need to validate user-provided phone numbers (mobile/landline). By excluding special service numbers, we keep bundles small, validation strict, and performance blazing fast.
Test Coverage
We validate our implementation against Google's original test suite:
| Source | Original Tests | Converted | Coverage |
| ---------------------------- | -------------- | --------- | ------------ |
| phonenumberutil_test.js | 111 | 96 | 86% |
| asyoutypeformatter_test.js | 33 | 0 | Not in scope |
| Total | 144 | 129 | 90% |
Skipped tests (5) are explicitly marked with reasons:
- Toll-free number formatting (not in scope)
- Premium rate number formatting (not in scope)
- Complex edge cases (documented individually)
What We Explicitly Don't Support
| Feature | Reason | | -------------------------------------- | ----------------------------------- | | Toll-free numbers (800, 0800, etc.) | Outside user-input validation scope | | Premium rate numbers (900, 0900, etc.) | Outside user-input validation scope | | Short codes (SMS short numbers) | Outside user-input validation scope | | Shared cost, UAN, Voicemail | Outside user-input validation scope | | AsYouTypeFormatter | Complexity vs. value trade-off | | Legacy runtimes (Node < 20) | Modern tooling requirement |
Numbers of unsupported types will parse successfully but return type: "invalid" and isValid: false.
Metadata Updates
Metadata is derived from Google's libphonenumber and updated via semi-automatic tooling:
pnpm upstream:update # Check for updates, convert, testThe current upstream version is tracked in upstream/.version.
Metadata Bundles
Metadata is organized into individual country modules and group bundles:
Individual Countries
import DE from "ts-phonenumber/metadata/countries/DE"
import US from "ts-phonenumber/metadata/countries/US"Group Bundles
import DACH from "ts-phonenumber/metadata/bundles/DACH" // DE, AT, CHAPI Reference
Types
PhoneNumberType-"landline" | "mobile" | "voip" | "invalid"PhoneNumberFormat-"e164" | "international" | "national" | "rfc3966"ParsedPhoneNumber- Result of parsing a phone numberValidationResult- Result of validating a phone numberParseOptions- Options for parsing (includesdefaultRegion)
Async Functions
parse(input, options?)- Parse a phone number stringvalidate(input, options?)- Validate a phone numberisValidNumber(input, options?)- Simple boolean validationformat(input, format, options?)- Format a phone numbergetType(input, options?)- Get the phone number typeisMobile(input, options?)- Check if number is mobileisLandline(input, options?)- Check if number is landlineisVoIP(input, options?)- Check if number is VoIP
Sync Functions (Maximum Performance)
parseSync(input, options?)- Parse a phone number (requires pre-loaded metadata)validateSync(input, options?)- Validate a phone numberisValidNumberSync(input, options?)- Simple boolean validationformatSync(input, format, options?)- Format a phone number
Metadata Functions
registerMetadata(metadata)- Register metadata for sync APIloadRegionMetadata(regionCode)- Load metadata for a region (async)loadMetadataBundle(bundleName)- Load a group bundle (async)preloadRegions(regionCodes)- Preload multiple regions (async)isMetadataLoaded(regionCode)- Check if metadata is loadedclearMetadataCache()- Clear the metadata cache
Benchmarks
Up to 11x faster than alternatives — ts-phonenumber wins all 7 benchmarks against the most popular phone number libraries.
| Benchmark | ts-phonenumber | vs libphonenumber-js | vs google-libphonenumber | vs awesome-phonenumber | | --------------- | -------------: | :--------------------: | :------------------------: | :----------------------: | | Parse E.164 | 1.56M/s | 3.3x faster | 4.9x faster | 25x faster | | Parse national | 2.14M/s | 6.4x faster | 9.9x faster | 37x faster | | Validate | 1.53M/s | 5.4x faster | 6.2x faster | — | | Format E.164 | 32.4M/s | 4.3x faster | 1.5x faster | 1.9x faster | | Format Intl | 2.30M/s | 7.3x faster | 6.1x faster | 6.2x faster | | Full pipeline | 1.51M/s | 4.8x faster | 11.2x faster | — | | Batch (10 nums) | 160K/s | 7.4x faster | 5.7x faster | 38x faster |
Benchmarks on Apple M1 Ultra, Node.js v24. Sync API with pre-loaded metadata. Run pnpm benchmark to reproduce.
Why so fast?
Hand-optimized data structures from official Google data:
- Pre-compiled RegExp literals — All patterns (validation, formatting, leading digits) are compiled at build time, not runtime. Zero
new RegExp()calls during validation. - Bitmap-based length validation — Possible lengths stored as bit flags for O(1) membership checks instead of array iteration.
- Combined leading digit patterns — Multiple patterns merged into single RegExp with
|alternation, eliminating array traversal. - Zero-overhead sync API — When metadata is pre-loaded, no async overhead at all.
- Lean metadata — Only LANDLINE, MOBILE, VOIP patterns included. No toll-free, premium, or special service bloat.
All metadata is automatically generated from Google's official PhoneNumberMetadata.xml — the same source used by libphonenumber — ensuring correctness while maximizing performance.
Development
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run type checking
pnpm typecheck
# Run linting
pnpm lint
# Build
pnpm build
# Run benchmarks
pnpm benchmarkLicense
Apache-2.0
Based on concepts from Google's libphonenumber.
Contributing
See CONTRIBUTING.md for guidelines.
