mrkeyoor.com_
Thu 06 Aug 07:42 UTC
npmUtilsupdated 06 Aug 2026

libphonenumber-js

libphonenumber-js parses, validates, and formats personal phone numbers using Google's own numbering-plan data, rewritten from scratch in JavaScript instead of compiled from Google's Closure-based port. You give it a string like ' 8 (800) 555-35-35 ' and a default country, and you get back a PhoneNumber object that knows the E.164 form (+78005553535), the country, the national number, and how to print it internationally or nationally. It also ships an AsYouType class for formatting a number while the user is still typing, and findPhoneNumbersInText for pulling numbers out of prose. The headline difference from Google's port is size: Google's compiled bundle is around 550 kB, and this one is roughly 145 kB with full metadata and about 80 kB with the default reduced set, because you pick which metadata bundle to import. It has zero runtime dependencies and ships TypeScript definitions.

Verdict

The default choice for phone input on the web: correct enough because it uses Google's metadata, small enough because you choose how much of it to ship. Decide between the min and max entry points on day one, and prefer isPossible() over isValid() unless you will keep the dependency updated.

API stability5/5Still on 1.x with the modern parsePhoneNumber and AsYouType API unchanged for years; the older parseNumber and formatNumber functions are documented as a legacy API and still work, so upgrades within 1.x have been drop-in.
Docs4/5One very long README that documents every function, every PhoneNumber method, and the metadata trade-offs, plus a live demo page for reproducing behavior; the length makes it hard to skim, and the most important choice (min versus max) is buried a third of the way down.
Maintenance4/51.13.10 published 30 July 2026, and the README describes a daily GitLab CI job that pulls Google's updated metadata and republishes automatically; the GitHub mirror is a backup that lags (last pushed 18 June 2026) and carries 36 open issues (38 counting PRs), so judge activity by the npm release feed rather than by GitHub.
Ecosystem5/5About 23.2M weekly downloads, TypeScript definitions in the package, and the engine behind react-phone-number-input and a long list of form libraries and admin frameworks that need phone fields.

Use it if

  • You need real E.164 normalization before storing a phone number, so that +1 213 373 4253, (213) 373-4253, and 213.373.4253 all land in the database as the same string
  • You are validating international phone input and a regex is not going to cut it: numbering plans differ per country and change several times a year
  • You want an as-you-type formatter for a phone input, with parentheses and spacing appearing correctly for whichever country the user picked
  • You want Google's data without Google's bundle: importing from libphonenumber-js/min gives you length validation and formatting for every country in about 80 kB of metadata
  • You need to detect number type (mobile, fixed line, toll free) to decide whether to offer SMS, which the /max metadata bundle supports through getType()
Skip it if

Setup reality

npm install libphonenumber-js and there are no peer dependencies and no build step, but the first real decision is which entry point to import from, and getting it wrong is the most common complaint. Plain 'libphonenumber-js' is an alias for the min metadata: isPossible() works, but isValid() is loose and getType() returns undefined for most countries. If you need strict digit validation or number type, import from 'libphonenumber-js/max' and accept roughly 145 kB of metadata; 'libphonenumber-js/mobile' is the middle option; 'libphonenumber-js/core' ships no metadata at all and expects you to pass your own as the last argument to every function. Second gotcha, and the one that generates the most confused bug reports: the default export returns undefined on unparseable input, while the named export literally called parsePhoneNumber is an alias for parsePhoneNumberWithError and throws a ParseError. Autocomplete offers the named one first, so people end up with unhandled exceptions in a form handler. Whichever you choose, every call needs a guard before you touch .country. Third: without a defaultCountry, a national-format string with no + is unparseable, so a country selector has to exist in your UI or come from somewhere. Fourth: the metadata JSON files are large enough that some bundlers with aggressive JSON inlining produce noticeably slower builds.

Patterns

Parse user input into a phone number objectparse-number

// the DEFAULT export returns undefined on bad input
import parsePhoneNumber from 'libphonenumber-js';

const phone = parsePhoneNumber(' 8 (800) 555-35-35 ', 'RU');

if (phone) {
  phone.country;            //=> 'RU'
  phone.number;             //=> '+78005553535'
  phone.nationalNumber;     //=> '8005553535'
  phone.countryCallingCode; //=> '7'
  phone.isPossible();       //=> true
}

Import this as the default export. The named export called parsePhoneNumber is an alias for parsePhoneNumberWithError and throws a ParseError instead of returning undefined, which is the single easiest way to get this library wrong.

Normalize to E.164 before storingnormalize-e164

import parsePhoneNumber from 'libphonenumber-js';

function toE164(input, country) {
  const phone = parsePhoneNumber(input, country);
  if (!phone || !phone.isPossible()) return null;
  return phone.number; // always '+<digits>'
}

toE164('(213) 373-4253', 'US'); //=> '+12133734253'

Store phone.number and nothing else: it is the one representation that stays stable and comparable. Formatting for display is cheap to redo on read, and keeping the user's punctuation in the database guarantees duplicate rows.

Validate a number without parsing it firstvalidate-input

import {
  isPossiblePhoneNumber,
  isValidPhoneNumber,
} from 'libphonenumber-js';

isPossiblePhoneNumber('8 (800) 555-35-35', 'RU'); //=> true (length only)
isValidPhoneNumber('8 (800) 555-35-35', 'RU');    //=> true (length + digits)

isPossible() checks length; isValid() also checks the digit ranges against the numbering plan. The author recommends isPossible() for forms, because isValid() goes stale on a site that stops updating dependencies and starts rejecting real numbers.

Format for display and for tel: linksformat-display

import parsePhoneNumber from 'libphonenumber-js';

const phone = parsePhoneNumber('+12133734253');

phone.formatInternational(); //=> '+1 213 373 4253'
phone.formatNational();      //=> '(213) 373-4253'
phone.getURI();              //=> 'tel:+12133734253'
phone.format('E.164');       //=> '+12133734253'
phone.format('IDD', { fromCountry: 'US' });

International format uses spaces rather than hyphens or brackets by design, so output will not match Google's library character for character. Use getURI() for a tel: href rather than concatenating digits, and note that format('IDD') returns undefined when options.fromCountry is missing or has no IDD prefix.

Format a phone field while the user typesas-you-type

import { AsYouType } from 'libphonenumber-js';

const formatter = new AsYouType('US');
formatter.input('213');     //=> '213'
formatter.input('3734');    //=> '(213) 373-4'

// in a controlled input, build a fresh formatter each keystroke
function onChange(value) {
  setValue(new AsYouType('US').input(value));
}

An AsYouType instance is stateful and appends: calling .input() twice on the same instance concatenates the two strings. Either call .reset() or construct a new instance per render, which is why the controlled-input version above looks wasteful but is correct.

Tell a mobile number from a landlinenumber-type

// requires the full metadata bundle
import parsePhoneNumber from 'libphonenumber-js/max';

const phone = parsePhoneNumber('+12133734253');
phone.getType(); //=> 'FIXED_LINE_OR_MOBILE'

if (phone.getType() === 'MOBILE') {
  offerSmsVerification();
}

getType() returns undefined on the default min metadata, which is the single most reported non-bug. Also expect FIXED_LINE_OR_MOBILE for North America, where the plan does not separate the two, so never treat undefined or that value as 'not a mobile'.

Pick the metadata bundle you actually pay forchoose-metadata

// ~80 kB metadata: length checks and formatting only
import parsePhoneNumber from 'libphonenumber-js/min';

// ~145 kB metadata: strict isValid() and getType()
import parsePhoneNumber from 'libphonenumber-js/max';

// ~95 kB metadata: max capabilities, mobile numbers only
import parsePhoneNumber from 'libphonenumber-js/mobile';

Bare 'libphonenumber-js' is an alias for /min. Mixing entry points in one app ships both metadata files, so pick one and enforce it with an import lint rule.

Ship metadata for only the countries you supportcustom-metadata

// generate a trimmed metadata file, then:
import parsePhoneNumber, { isValidPhoneNumber } from 'libphonenumber-js/core';
import metadata from './metadata.custom.json';

parsePhoneNumber('(213) 373-4253', 'US', metadata);
isValidPhoneNumber('(213) 373-4253', 'US', metadata);

Every function in the core entry point takes metadata as its last argument, and forgetting it throws rather than falling back. Worth it only when you support a handful of countries and the bundle budget is genuinely tight.

Extract phone numbers from free textfind-in-text

import { findPhoneNumbersInText } from 'libphonenumber-js';

const found = findPhoneNumbersInText(
  'Call +7 (800) 555-35-35 or the US branch at (213) 373-4253 ext. 1234.',
  'US',
);

for (const { number, startsAt, endsAt } of found) {
  console.log(number.number, number.ext, startsAt, endsAt);
}

Each result carries the PhoneNumber plus character offsets, which is what you need for highlighting or link substitution. This is ported from Google's Java matcher, so it is deliberately conservative and will miss loosely written numbers.

Build a country selector from the library's own datacountry-helpers

import {
  getCountries,
  getCountryCallingCode,
  isSupportedCountry,
} from 'libphonenumber-js';

const options = getCountries().map(code => ({
  code,
  dial: '+' + getCountryCallingCode(code),
}));

isSupportedCountry('ZZ'); //=> false

getCountries() reflects whichever metadata bundle you imported, so a custom build returns a shorter list. Guard user-supplied country codes with isSupportedCountry() before passing them in, because an unknown code throws.

Tell the user why the number is rejectedlength-feedback

import { validatePhoneNumberLength } from 'libphonenumber-js';

validatePhoneNumberLength('+1213373');        //=> 'TOO_SHORT'
validatePhoneNumberLength('+121337342531234'); //=> 'TOO_LONG'
validatePhoneNumberLength('+12133734253');     //=> undefined (ok)

Returns undefined when the length is acceptable, which reads backwards but lets you write `const error = validatePhoneNumberLength(v)`. Other possible values include INVALID_COUNTRY and NOT_A_NUMBER, so map them to messages rather than showing the raw string.

Accept numbers from one country onlycountry-restricted

import parsePhoneNumber from 'libphonenumber-js/max';

function isValidForCountry(input, country) {
  const phone = parsePhoneNumber(input, {
    defaultCountry: country,
    extract: false,
  });
  if (!phone) return 'NOT_A_NUMBER';
  if (phone.country !== country) return 'WRONG_COUNTRY';
  return phone.isValid() ? 'OK' : 'INVALID';
}

The library deliberately does not export a helper for this; the README shows this exact implementation and explains why. isValidPhoneNumber('+44...', 'US') returns true, because a leading + makes the default country irrelevant, so a naive check accepts the whole world.

Alternatives

PackageRegistryPick it when
google-libphonenumbernpmYou need feature parity with Google's Java library, including geocoding, carrier lookup, and short codes, and can afford the much larger bundle
awesome-phonenumbernpmYou want Google's compiled logic behind a modern ESM and TypeScript API, with the metadata still bundled in
react-phone-number-inputnpmYou want a ready-made React input with country selector and flags rather than wiring AsYouType up yourself