Library / SDK
AfterShip/phone avatar
AfterShip/phone

phone: validating and normalising phone numbers into E.164

With a given country and phone number, validate and reformat the mobile phone number to the E.164 standard. The purpose of this is to allow us to send SMS to mobile phones only.

925 stars290 forksTypeScriptMIT

At a glance

What is it?
AfterShip's phone package takes the messy strings users type into a checkout form and turns them into a validated E.164 number with a detected country, while keeping a mobile prefix check that most other formatters do not enforce. It is a small npm dependency with a documented set of sharp edges around country defaults and the plus sign.
Who is it for?
phone suits a team that needs an E.164 value it can hand to an SMS provider, and it is more useful than a regex because it rejects landline numbers in mobile-only fields and reports the country it detected. Skip it if you also need to validate expiry-style non-geographic numbers, since the result shape carries only a country code and the README describes no other number types.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 72 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: users type phone numbers every way but E.164

The stated purpose is to validate and reformat a mobile phone number to the E.164 standard, so that SMS can be sent to mobile phones only. The problem it solves is concrete. Users enter a number as (817) 569-8900, or 817569-8900, or 1(817) 569-8900, or +1(817) 569-8900, and the shape of the input carries almost no information you can rely on. What a downstream provider needs is the single form +18175698900. A package that turns the first group into the second, and tells you which country it assumed while doing so, removes an entire category of bug from a signup or delivery form. The audience is a backend or full stack developer who has a form field and a downstream service that insists on E.164, and who would rather not maintain a table of national number plans by hand.

One function, one options object, two result shapes

The whole public surface is a single exported function, usable from CommonJS or as an ES module import:

javascript
const {phone} = require('phone');

// or

import {phone} from 'phone';

The TypeScript signature shows how little there is to learn:

typescript
phone(phoneNumber: string, { country, validateMobilePrefix, strictDetection }?: {
    country?: string;
    validateMobilePrefix?: boolean;
    strictDetection?: boolean;
})

The return value is a discriminated union. A valid number gives isValid true, phoneNumber in E.164, countryIso2, countryIso3 and the calling code with its plus sign. An invalid number gives isValid false and null in every other field, so a caller never has to guard against partially populated output. That single choice removes the most common bug in this class of library, where a formatter returns an empty string or undefined for bad input and the caller silently stores it. The options are all optional, with country defaulting to null, validateMobilePrefix defaulting to true and strictDetection defaulting to false.

Country handling and the USA or Canada default

The country option takes an ISO 3166 code in alpha 2 or alpha 3 form, and it changes the outcome. With a country supplied, a local number is interpreted in that country: phone('(817) 569-8900', {country: 'USA'}) returns +18175698900, while the same number with {country: 'HKG'} returns isValid false because it is not a valid Hong Kong mobile number. The documented default is the one that surprises people. If neither a country code nor a country phone prefix is supplied, the number is treated as USA or Canada. That is why '780-569-8900' resolves to Canada, since 780 is a Canadian prefix, and why '6123-6123' with no country comes back invalid, because it fits neither country under their rules. The rule is documented clearly, but it means a form that collects numbers without a country selector silently applies North American rules, and the README does not describe a way to change that default other than by passing a country yourself.

The missing plus sign is the most common failure

The README calls this out directly. Even a valid number with a valid prefix will not work as expected if the plus sign is missing, because without it the module assumes the number is a USA or Canada number and finds no match:

javascript
phone('85291234567');
// or
phone('85291234567', {country: null});

// { isValid: false }

852 is a valid Hong Kong prefix and 91234567 is a valid Hong Kong mobile number, but the result is invalid. Adding the plus sign fixes it, and so does passing the country instead:

javascript
phone('+85291234567');
// { isValid: true, phoneNumber: '+85291234567', countryIso2: 'HK', countryIso3: 'HKG', countryCode: '+852' }

So phone('91234567', {country: 'HKG'}) also works and returns +85291234567. The practical lesson for a form is that the library needs either the international prefix or an explicit country, and that a user who strips the plus sign and has no country context gets a rejection rather than a wrong number, which is the safer failure.

validateMobilePrefix is what makes this a mobile checker

Most formatters are happy with any number. This one is not, because the purpose is sending SMS to mobile phones. By default the initial digit is checked against the mobile prefixes of the country, so a Hong Kong landline is rejected:

javascript
phone('+(852) 2356-4902');
// { isValid: false }
// '2' is a Hong Kong landline phone number prefix, not a valid mobile phone number prefix

Setting validateMobilePrefix to false skips that check and accepts the landline number. It also accepts prefixes that do not exist at all, since the README says the initial digit checking logic is disabled completely, so a number starting with 0 in Hong Kong is reported as valid. The README is explicit about the limit of this: the module does not have the capability to determine if the prefix is a valid landline prefix number. In other words, turning the option off turns the library into a general formatter again, and the mobile guarantee disappears. For a field that exists to receive SMS, that trade is rarely worth making without a reason.

Trunk code detection, and how to switch it off

Some numbers carry a trunk code after the country code, and the library tries to repair them. The example in the README is a UK number written as +44 07911 123456, where the 0 after +44 prevents any country match. The module then removes one digit after the country code and tries again with +44 7911 123456, which matches:

javascript
phone('+4407911 123456')
// { isValid: true, phoneNumber: '+447911123456', countryIso2: 'GB', countryIso3: 'GBR', countryCode: '+44' }

This repair is on by default, and strictDetection set to true turns it off, at which point the same input comes back invalid. That switch matters for a different reason than it first appears. If your application stores the E.164 value it receives and later sends to a provider, a silently corrected number may no longer be the number the user typed, and the digit that was removed is gone from your record. If you need the input to round trip exactly, strictDetection is the setting that tells you whether the library accepted the number as written.

Package layout, the rule pipeline and the release rhythm

The repository is a small TypeScript project with the usual parts: src/ holding the implementation, a lib/ directory under it, __tests__/ for the Jest suite, a jest.config.js, and an example/ folder with a client/, an index.html and its own webpack.config.js so you can try the package in a browser build. The build script is rimraf ./dist && tsc, with main pointing at ./dist/index.js and types at ./dist/index.d.ts, and the package ships only the dist directory. The engines field requires Node 12 or newer. One script is worth knowing about: fix:numbers runs ts-node over scripts/add-new-rules, which is the maintainers' path for adding or regenerating number rules. The releases tell their own story, with 3.1.72 on 2026-08-12, 3.1.71 on 2026-02-24 and 3.1.70 on 2026-01-21, so patch level increments arrive a few times a year as the rules data changes. The last push was on 2026-08-12, the repository is not archived, and the licence is MIT, which is the simplest case for a dependency in a commercial codebase.

Editorial conclusion

phone suits a team that needs an E.164 value it can hand to an SMS provider, and it is more useful than a regex because it rejects landline numbers in mobile-only fields and reports the country it detected. Skip it if you also need to validate expiry-style non-geographic numbers, since the result shape carries only a country code and the README describes no other number types. Before adopting, read the default country behaviour, because a number without a plus sign is treated as USA or Canada and will be rejected or misread, and decide whether validateMobilePrefix stays on, since turning it off accepts prefixes that do not exist.

Frequently asked questions

How do I install and use the phone package?

Install it with npm install phone or yarn add phone, then import the phone function with const {phone} = require('phone') or import {phone} from 'phone'. The package ships compiled JavaScript and type declarations, and requires Node 12 or newer.

What happens if the phone number has no country code and no plus sign?

The README states the number is treated as a USA or Canada number by default. So 780-569-8900 resolves to Canada because 780 is a Canadian prefix, while 6123-6123 is invalid because it fits neither country under their rules.

Can phone validate that a number is a mobile number rather than a landline?

Yes, by default. The validateMobilePrefix option defaults to true, so phone('+(852) 2356-4902') is invalid because 2 is a Hong Kong landline prefix. Setting it to false skips the initial digit check entirely, and the README notes the module cannot tell whether a prefix is a valid landline prefix.

What does strictDetection do in the phone package?

Trunk code detection is on by default, so a number like +4407911 123456 is repaired to +447911123456 by removing one digit after the country code. Setting strictDetection to true disables that repair, and the same input is then reported as invalid.

How often is the phone package updated?

The recent versions are 3.1.72 published on 2026-08-12, 3.1.71 on 2026-02-24 and 3.1.70 on 2026-01-21, and the last push was on 2026-08-12. The repository is not archived and the licence is MIT.

Official sources

  1. AfterShip/phone on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aftership-phone.svg)](https://hysenlabs.com/projects/aftership-phone)

Was this useful?