Skip to content

Migrate from Google libphonenumber

Composes: ensureEngineReady + parsePhoneNumber + createInternationalInputController

This guide maps each Google libphonenumber method to its @telixon/core equivalent and lists the behavioral differences that matter while porting. Telixon is built on the same foundation: digits drive one deterministic automaton compiled from Google’s metadata, so a migration changes the API surface, not the answers.

The before code uses the google-libphonenumber npm package, a third-party wrapper of Google’s JavaScript implementation.

// Before: google-libphonenumber
const { PhoneNumberUtil, PhoneNumberFormat } = require('google-libphonenumber');
const phoneUtil = PhoneNumberUtil.getInstance();
const parsed = phoneUtil.parseAndKeepRawInput('415 555 0132', 'US');
phoneUtil.isValidNumber(parsed); // true
phoneUtil.format(parsed, PhoneNumberFormat.E164); // '+14155550132'
phoneUtil.format(parsed, PhoneNumberFormat.NATIONAL); // '(415) 555-0132'
phoneUtil.parse('+999 123 4567', 'US');
// throws Error: Invalid country calling code
// After: @telixon/core
import { ensureEngineReady, parsePhoneNumber } from '@telixon/core';
await ensureEngineReady(); // once per process
const phoneNumber = parsePhoneNumber('415 555 0132', { defaultRegion: 'US' });
phoneNumber.isValid(); // true
phoneNumber.formatE164(); // '+14155550132'
phoneNumber.formatNational(); // '(415) 555-0132'
parsePhoneNumber('+999 123 4567').getValidationError();
// { kind: 'INVALID_CALLING_CODE' }

Both programs report the same validity and the same formatted strings. That holds for every input, not only this one: Telixon has 100% geographic parity with Google libphonenumber, verified by exhaustive enumeration (Verified against Google libphonenumber).

Google libphonenumber @telixon/core Notes
parse(input, region), parseAndKeepRawInput(input, region) parsePhoneNumber(input, { defaultRegion }) One function covers both call forms; the region argument becomes the defaultRegion option. Never throws for bad input.
isValidNumber(number) phoneNumber.isValid()
isValidNumberForRegion(number, region) phoneNumber.isValidForRegion(region) or strict mode isValidForRegion for a one-off check; strict: true at parse time to restrict the whole instance.
isPossibleNumber(number) phoneNumber.isPossible()
isPossibleNumberWithReason(number) phoneNumber.isPossibleWithReason() Same reason vocabulary, returned as string literals instead of a numeric enum; Google’s INVALID_COUNTRY_CODE is named INVALID_CALLING_CODE. See PossibilityResult.
getNumberType(number) phoneNumber.getNumberType() Returns string literals such as 'MOBILE', not a numeric enum.
getRegionCodeForNumber(number) phoneNumber.getRegion()
format(number, PhoneNumberFormat.E164) phoneNumber.formatE164() One method per format; each returns string | null.
format(number, PhoneNumberFormat.NATIONAL) phoneNumber.formatNational()
format(number, PhoneNumberFormat.INTERNATIONAL) phoneNumber.formatInternational()
format(number, PhoneNumberFormat.RFC3966) phoneNumber.formatRfc3966()
getExampleNumber(region), getExampleNumberForType(region, type) getPlaceholders(region, type) Returns preformatted national, nationalWithPrefix, and international strings instead of a number object.
getCountryCodeForRegion(region) getCallingCodeForRegion(region) Returns the calling code as a string ('44'), not a number.
AsYouTypeFormatter The input controller A full input controller with caret positions, undo/redo history, and mid-typing number queries, not a formatter.
  • The engine ships precompiled. The wrapper bundles Google’s metadata and parses it at runtime. Telixon compiles the same metadata into one deterministic automaton at build time and loads it as a binary artifact, so no metadata is parsed at runtime. Mechanics are on Engine.
  • Initialization is one await up front. PhoneNumberUtil.getInstance() is synchronous because the metadata travels in the bundle. Telixon loads the engine when you call ensureEngineReady, once per process; before it resolves, every query throws EngineNotReadyError, and after it, every call in this guide is synchronous.
  • Bad input is a value, not an exception. parse throws NumberParseException (a thrown Error in the npm wrapper). parsePhoneNumber never throws for bad input: every input resolves to a PhoneNumber, and getValidationError() names the failure as a discriminated union. Validity is an accepting state, and every ValidationError variant is a named way of not being in one.

Replace PhoneNumberUtil.getInstance() with one await ensureEngineReady() at process startup.

import { ensureEngineReady } from '@telixon/core';
await ensureEngineReady();

Per-environment entry points and loading strategies, including the synchronous sync-init entry, are covered in Engine loading.

Ported parse sites branch on the returned value instead of catching.

import { parsePhoneNumber } from '@telixon/core';
const phoneNumber = parsePhoneNumber('415 555', { defaultRegion: 'US' });
if (!phoneNumber.isValid()) {
phoneNumber.getValidationError(); // { kind: 'TOO_SHORT', minLength: 10 }
return;
}

Variants carry the data a message needs (minLength, maxLength, possibleLengths, expectedPrefix); the full union is on ValidationError, and mapping each variant to user-facing copy is Show form errors.

Google’s queries are methods on PhoneNumberUtil that take the parsed number back; Telixon’s are methods on the returned PhoneNumber. Rewrite each call site with the API mapping table. The one decision point is isValidNumberForRegion, which has two ports:

// One-off check: query the region directly.
const phoneNumber = parsePhoneNumber('+1 415 555 0132');
phoneNumber.isValidForRegion('US'); // true
phoneNumber.isValidForRegion('GB'); // false
// Whole-instance restriction: parse in strict mode.
const restricted = parsePhoneNumber('+1 212 555 0198', {
defaultRegion: 'CA',
strict: true,
});
restricted.isValid(); // false, valid in US but not in CA

When to prefer which, and exactly what strict does and does not restrict, is covered in Strict mode.

4. Replace AsYouTypeFormatter with an input controller

Section titled “4. Replace AsYouTypeFormatter with an input controller”

AsYouTypeFormatter accepts one digit through inputDigit and returns a formatted string; caret placement, deletion, and mid-string edits are left to the caller. The input controller consumes editing intents (insert, deleteBackward, setValue) and returns the next value with caret positions, keeps undo/redo history, and answers number queries mid-typing. parsePhoneNumber and the input controller are the same shared resolve layer, queried in batch versus per keystroke.

import { createInternationalInputController } from '@telixon/core';
const controller = createInternationalInputController({
display: { callingCodeInInput: true, plusPrefix: 'fixed' },
});
let state = controller.currentState; // value: '+'
for (const digit of '442079') {
state = controller.insert(state.value, digit, state.selectionStart, state.selectionEnd);
}
state.value; // '+44 20 79'
state.region; // 'GB'
controller.getPhoneNumber().isValid(); // false, the number is still incomplete

For a single-country field, use createNationalInputController instead. Wiring a controller into a real text field is Drive an input; for a prebuilt DOM binding, see @telixon/web-sdk.