Developers

Phone number validation in code: libraries, the validation ladder and when to call an API

The phone validation ladder: syntax, numbering plan, line type and live checks, with the right library for JavaScript, Python, PHP, Go and Java.

By Published 7 min read

On this page

Phone number validation in code has four rungs: check the syntax, check the number against its country's numbering plan, find the current line type, and confirm that the number is live or reachable on a channel. A libphonenumber port covers the first two for free, in every major language. The last two need network data from an API. This hub explains each rung and links to a tested tutorial per language.

What does "validating a phone number" actually mean?

Developers ask four different questions under one name, and most bugs come from answering the wrong one. The ladder below orders them by cost and certainty:

RungQuestionHowCostChanges when
1. SyntaxIs it shaped like a phone number?Normalization, a loose pre-filterFree, localNever
2. Numbering planCould this number exist in its country?libphonenumber: isValidFree, localPlans change every few weeks
3. Line type and carrierIs it a mobile, a landline or VoIP today?Carrier lookupPaid per conclusive answerOn porting
4. Live status and channelsIs it in service? Does the person use WhatsApp?Network status, registration checksPaid per conclusive answerAny day

Each rung filters the input for the next one, and each costs more. So run them in order: never pay for rung 3 on a number that fails rung 2. The popular Stack Overflow question "Validate phone number with JavaScript" had over 806,000 views on 2026-09-25, and most of its answers stop at rung 1.

A ladder with four rungs labelled syntax, plan, line type and live, beside five language tabs: JS, PY, PHP, GO and Java.A ladder with four rungs labelled syntax, plan, line type and live, beside five language tabs: JS, PY, PHP, GO and Java.
Validation is a ladder: syntax, numbering plan, line type, then a live check.

Why is a regex only rung one?

A regex can check shape. It can't know which country codes exist, how long numbers are in each country, or which ranges are allocated. In our test of 11 strings, 8 passed the common E.164 pattern ^\+[1-9]\d{1,14}$ and failed real validation, including +999… (no such country code) and +1 123 456 7890 (no US area code starts with 1).

The details are in E.164 regex: why a pattern is not enough. There's one good use for the pattern: as a database constraint on values you've already normalized, so no code path can store 07911 123456 in an E.164 column.

Which library should you use in each language?

Google's libphonenumber is the reference. Every recommended option below uses its metadata, so the same input gets the same answer, as long as the versions match. Versions were checked on 2026-09-25:

LanguageLibraryLicenceVersion trackingTutorial
JavaScript / TypeScriptlibphonenumber-js (import /max on the server)MITOwn versions; metadata updated frequentlyJavaScript and Node
Pythonphonenumbers (or phonenumberslite)Apache 2.0Same numbers as Google (9.0.40)Python
PHPgiggsey/libphonenumber-for-php (or -lite)Apache 2.0Same numbers as Google (9.0.40)PHP and Laravel
Gogithub.com/nyaruka/phonenumbers/v2MITOwn versions; metadata may trail by a few releasesGo
Java / Kotlincom.googlecode.libphonenumber:libphonenumberApache 2.0The original (9.0.40)Java and Kotlin

Two cautions apply to all of them. First, pick the full metadata where there's a choice. libphonenumber-js defaults to "min" metadata, which checks little more than length: +49 1112 3456789 passed isValidPhoneNumber() with min and failed with max in our test. Second, keep the library current. Google's release notes list 25 releases in 2025 and 19 between January and 23 September 2026, and every one updated phone metadata for at least one region.

What's the difference between isPossible and isValid?

It's the most common confusion on rung 2. The Stack Overflow question "isPossibleNumber vs. isValidNumber" has been viewed over 8,000 times.

  • isPossible checks only the length for the country. Fast, forgiving, good for "keep typing" hints.
  • isValid also checks the leading digits against the ranges the country has allocated. Use it before you store a number or pay for a lookup.

+1 123 456 7890 is the classic example: ten digits is a possible US length, but the number isn't valid. The same names appear in every port (is_possible_number and is_valid_number in Python, IsValidNumber in Go).

What do all the tutorials have in common?

We wrote the five language tutorials to the same pattern, and ran all the code against our API in test mode. The shared rules are worth knowing even if you use another stack:

  1. Normalize to E.164 once, at the edge. Parse with a default region from a country picker, check validity, store the E.164 string. Keep the raw input separately if support needs it.
  2. Take the region from the parser. +44 7911 123456 is a Guernsey number (GG), and +1 covers more than 20 countries and territories. A dialling-code lookup table gets these wrong.
  3. Validate twice. Client-side for the user's benefit; server-side because it's the only check you can trust.
  4. Send numbers in POST bodies, never in URLs, where they end up in logs.
  5. Treat unknown as missing data. A null answer is not false. Keep your default and re-check later.
  6. Retry only what the API marks retryable, and wait as long as Retry-After says.

The E.164 format guide goes deeper on storage and deduplication.

When should you call an API instead of relying on a library?

When the answer depends on the network rather than the numbering plan. A library knows that +1 202 555 0143 is a valid US number of type FIXED_LINE_OR_MOBILE. It can't know which, because US ranges don't separate the two, and it can't know whether the number was ported to a VoIP provider last month. Rung 3 and 4 questions that come up in practice:

How does the MobileValidate API fit the ladder?

The API runs rungs 1 and 2 on every request, for free, with libphonenumber metadata. Numbers that fail come back as invalid_number, repeats as duplicate, and neither is checked. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Rungs 3 and 4 are the checks you request:

Shell
curl https://api.mobilevalidate.com/v1/lookup \
  -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numbers": ["+447700900001", "07700 900002", "12345"], "checks": ["carrier"],
       "default_country": "GB", "wait": 10}'

With a test key, the three rows come back as valid with line_type: "mobile", valid with status unknown (reason NO_DATA, not billed), and invalid_number. Test keys are free and return fixed answers for the test numbers. The quickstart takes about five minutes.

One detail shows up in every tutorial: libphonenumber rejects +44 7700 900xxx, because the UK regulator Ofcom reserves it for drama. Our test numbers come from that range, so each tutorial lets it through locally only when the key starts with mv_test_.

Where should each rung run?

Put each check where it's cheapest and where you can trust it:

WhereRungsWhy
Browser or mobile app1–2Instant feedback while the user types; format with AsYouType or AsYouTypeFormatter
Your API server1–2, then 3–4The only validation you can trust; the only place an API key may live
Background jobs3–4Slow checks or bulk jobs, re-checks of stored numbers, retries after unknown
Database1A CHECK constraint that stored values are E.164

In a sign-up flow, rungs 1 and 2 decide whether the form is accepted. Rungs 3 and 4 add information: use it when it's conclusive, and don't block a real user because a lookup timed out. In batch work the order is the same, but everything runs server-side, and a free estimate first tells you how many rows are invalid or duplicated before you spend anything.

How often should stored numbers be re-validated?

Rung 2 answers change only when a numbering plan changes, so re-running the library after an upgrade is enough, and it's free. Rung 3 and 4 answers age. Numbers get ported, disconnected and reassigned, and people install and delete apps. Store every answer with its checked_at timestamp, and decide per use:

  • Before an important message, such as an OTP or a delivery notice: use an answer from the last few days, or check again.
  • For list hygiene, such as a marketing list with consent: re-check on a schedule, for example monthly or quarterly.
  • After a delivery failure: re-check that number now.

Repeat checks within the freshness window are served from your account's cache and aren't billed again. How to clean a phone number list in bulk shows a full workflow.

How should you test phone validation?

Split tests by rung, because they fail for different reasons:

  • Rungs 1–2 (offline, deterministic): one valid number per country you serve, in national and international format; an impossible area code; too short and too long; no region and no +; a shared country code (+44 7911… → GG). Pin the library version, and expect a few fixtures to change on upgrades.
  • Rungs 3–4 (network): use test keys and test numbers. +447700900001 returns data, …002 and …003 return unknown, …004 stays pending for about five seconds, and …429 returns rate_limited. Nothing is billed and no real network is touched.
  • Never real customer numbers in fixtures. They're personal data, and they change owners.

What are the key takeaways?

  • Validation is a ladder: syntax, numbering plan, line type, live status and channels. Run the rungs in order of cost.
  • Use a libphonenumber port for rungs 1 and 2, with full metadata, and update it roughly monthly.
  • isValid, not isPossible, before you store a number or pay for anything.
  • A regex is a storage constraint, not a validator.
  • Line type, porting and reachability need network data from an API, called from your server.
  • Unknown is missing data. Retry only retryable errors, and test every branch with free test numbers.

Sources

  1. libphonenumber — Google, 2026
  2. libphonenumber release notes — Google, 2026
  3. ITU-T Recommendation E.164 — International Telecommunication Union, 2026
  4. Validate phone number with JavaScript (question 4338267) — Stack Overflow, 2026
  5. isPossibleNumber vs. isValidNumber (question 39036633) — Stack Overflow, 2026

Frequently asked questions

What is the best library for phone number validation?

Google's libphonenumber or a port of it in your language: libphonenumber-js for JavaScript, phonenumbers for Python, giggsey/libphonenumber-for-php for PHP, nyaruka/phonenumbers for Go, and the original library for Java and Kotlin. They share Google's numbering-plan metadata.

Is a validation library enough, or do I need an API?

A library answers whether a number fits its country's numbering plan. It can't tell you the current line type after porting, whether the number is in service, or whether the person uses a messaging app. For those you need a lookup API.

How often should I update my phone validation library?

Monthly is a reasonable default. Google published 25 metadata releases in 2025, roughly one every two weeks, and each changed the numbering data of at least one country.

Where should phone validation run: client or server?

Both, for different reasons. Validate in the browser or app for instant feedback, then validate again on the server, which is the only check you can trust. Paid lookups always run on the server so API keys stay secret.

All articles

Know before you send.

Tell us about your use case. We review every request and set you up with test and live keys.