Library / SDK
daviddrysdale/python-phonenumbers avatar
daviddrysdale/python-phonenumbers

daviddrysdale/python-phonenumbers: Google's phone number data with a Python API

Python port of Google's libphonenumber

3,774 stars445 forksPythonApache-2.0

At a glance

What is it?
A port of libphonenumber that turns a string of digits into a validated, formatted, geocodable number object, with the metadata load deferred until you need it.
Who is it for?
The single most useful thing this library teaches is that parse, validate and format are three separate steps, and conflating them is the usual source of bad phone data. `parse` produces a `PhoneNumber` object that may still be nonsense, `is_possible_number` checks whether it could exist at all, and `is_valid_number` checks whether the exchange is actually assigned. Most projects need all three and use only the first.
Can I use it commercially?
Yes. Apache-2.0 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 15 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

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

Editorial analysis

One pip install and one parse call

Installation is a single line:

bash
pip install phonenumbers

The library is a port of Google's libphonenumber, and the README is explicit that the original Java code is copyright The Libphonenumber Authors. The Python port is Apache 2.0 licensed, and the release history under `python/HISTORY.md` is derived from the upstream release notes, which is the mechanism by which new numbering data arrives.

The basic call needs a default region, because a national format number is ambiguous without one:

pycon
>>> import phonenumbers
>>> x = phonenumbers.parse("+442083661177", None)
>>> print(x)
Country Code: 44 National Number: 2083661177 Leading Zero: False

The same number in national format, with the region supplied, produces an equal object:

pycon
>>> y = phonenumbers.parse("020 8366 1177", "GB")
>>> x == y
True

That equality is the whole reason the library is worth using rather than writing a normaliser. A number stored as `+442083661177` and a number stored as `020 8366 1177` from a UK web form become the same value, so database comparison works without you maintaining a country lookup table.

Possible, valid, and the gap between them

This is the distinction most callers get wrong. Parsing succeeds on things that cannot be real phone numbers, and the library gives you two separate checks so you can decide which one your use case needs.

pycon
>>> z = phonenumbers.parse("+12001230101", None)
>>> phonenumbers.is_possible_number(z)
True
>>> phonenumbers.is_valid_number(z)
False

That number has enough digits to be possible for the United States, but the area code is not in use, so it is not valid. The README demonstrates both states on the same input: too few digits makes it impossible, and a plausible but unassigned exchange makes it possible but invalid.

Parsing itself can also fail outright, with a `NumberParseException` carrying an error code. A number in national format with no region and no leading plus gives you error code 0, missing or invalid default region. Input that is not a number at all gives error code 1, with the message that the string supplied did not seem to be a phone number.

The practical consequence is that a validation endpoint should catch the exception, then check `is_possible_number`, then check `is_valid_number`, and report which stage failed. Skipping straight to `is_valid_number` on an unparsed string is the mistake that produces unpredictable results.

There is also a subtlety with international dialling. Parsing `00 1 650 253 2222` with a GB default region does not produce a GB number; the `00` prefix means the number is being dialled from the UK to somewhere else, so the result has country code 1.

Formatting for storage, display and as-you-type entry

Once you have a `PhoneNumber` object, output formatting is a matter of picking a member of `PhoneNumberFormat`. The README shows the same number in all three common forms:

pycon
>>> phonenumbers.format_number(x, phonenumbers.PhoneNumberFormat.NATIONAL)
'020 8366 1177'

The international form adds the plus sign and spaces, and the E164 form strips everything, which is what you should store. E164 is the safe internal representation because it is globally unique, which is the same property that let `parse` accept a plus-prefixed number with no region at all.

For user input there is a separate class, `AsYouTypeFormatter`, which formats progressively as digits arrive:

pycon
>>> formatter = phonenumbers.AsYouTypeFormatter("US")
>>> formatter.input_digit("6")
'6'

Feeding the remaining digits progressively inserts the space, then the hyphen, then the parentheses, because the formatter is following the numbering plan for the region rather than applying a fixed pattern. That is a real difference from a regex mask, and it is why this class exists at all.

The formatter needs the region up front, which is the case your UI usually cannot satisfy. If you do not know the country before the user starts typing, either default to one country or fall back to asking for it, because a wrong region produces confidently wrong formatting.

Geocoding, carrier lookup and time zones from a number

Three separate modules answer questions that go beyond validity, and they are the feature that distinguishes this library from a plain validator.

The geocoder maps a number to a place name, and takes a language code:

pycon
>>> from phonenumbers import geocoder
>>> ch_number = phonenumbers.parse("0431234567", "CH")
>>> geocoder.description_for_number(ch_number, "de")
'Zürich'

The carrier module answers which mobile carrier originally owned a number, which is how you get a provider name from a mobile number in countries where ranges were assigned to operators:

pycon
>>> from phonenumbers import carrier
>>> ro_number = phonenumbers.parse("+40721234567", "RO")

The timezone module is the one most people do not know exists. It returns the set of time zones a number could plausibly belong to, which is what you need to render a timestamp correctly for a user you have never identified:

pycon
>>> from phonenumbers import timezone
>>> gb_number = phonenumbers.parse("+447986123456", "GB")

Note that it returns a tuple, not a single value. A mobile number can roam, so a set is the honest answer, and code that indexes `[0]` is assuming the number never moves.

The README itself points at the unit tests and the original libphonenumber project for the rest of the API surface, which is an admission that these three modules do not cover everything.

Extracting numbers from free text

When the input is a block of prose rather than a single field, `PhoneNumberMatcher` finds the numbers and tells you where they were:

pycon
>>> for match in phonenumbers.PhoneNumberMatcher(text, "US"):

Iterating it yields `PhoneNumberMatch` objects, each holding a `PhoneNumber` plus the start and end offsets of the match in the original string. The README's example text contains two US numbers written differently, one with hyphens and one with a space, and both come back in E164 form.

The offsets matter more than they look. If you are redacting phone numbers from text you are about to store, or highlighting them in a search result, you need to know precisely which characters the match covered, and `PhoneNumberMatch` gives you that rather than making you search for the formatted string again.

The default region argument applies here too, and it matters more: a matcher over international text with a narrow default region will find fewer numbers, because numbers without a country code have to be interpreted against that region. Scanning a document with mixed international content means matching more than once, once per plausible region, and deduplicating on the offsets.

Two and a half megabytes of metadata, and how to avoid it

The README's memory section is the part to read before deploying this into anything with a footprint limit. The library carries just over 2 MiB of generated Python code as metadata covering the numbering plans of every country, and that load is deferred on demand, region by region, so you do not pay for countries you never touch. There is a second, lighter mechanism for the cases where even deferred loading is too much.

That design has a consequence worth planning for. A process that parses numbers from a single country pays for one region's data; a service that handles numbers from anywhere accumulates the lot. On a server with generous memory that is a non-issue. In a Lambda cold start, a container with a tight limit, or a client side application, the choice between full metadata and the reduced set is an architectural decision rather than a preference.

The repository layout reflects the upstream relationship rather than a normal Python package layout. `python/` holds the library itself and the `HISTORY.md` release notes, `resources/` holds the numbering plan data the port is generated from, `tools/` holds the tooling that regenerates the Python code from those resources, and `debian/` holds packaging for Debian. `docs/` holds the documentation site, published at daviddrysdale.github.io.

The default branch is `dev` rather than `master`, the repository is not archived, and the last push was 2026-09-21 with 11 open issues. There are no tagged releases in this repository view, so pinning by version comes from PyPI rather than from GitHub releases.

Editorial conclusion

The single most useful thing this library teaches is that parse, validate and format are three separate steps, and conflating them is the usual source of bad phone data. `parse` produces a `PhoneNumber` object that may still be nonsense, `is_possible_number` checks whether it could exist at all, and `is_valid_number` checks whether the exchange is actually assigned. Most projects need all three and use only the first. For anything beyond parsing, the geocoder, carrier and timezone modules turn a number into a city, a mobile carrier or a set of time zones, which is more than most alternatives attempt. Start with `pip install phonenumbers`, parse a handful of real numbers from your own database with `is_valid_number` before believing any of them, and if memory matters in your context read the metadata section carefully before enabling the lighter option.

Frequently asked questions

How do I install the phonenumbers module in Python?

Use pip: `pip install phonenumbers`. The package is a port of Google's libphonenumber and is published on PyPI as `phonenumbers`. Documentation is hosted separately at daviddrysdale.github.io/python-phonenumbers/, and release history derived from upstream notes lives at python/HISTORY.md in the repository.

What is the difference between is_possible_number and is_valid_number?

Possible means the number has the right shape, that is enough digits for its country. Valid means it is actually in service, that is the exchange has been assigned. A number can be possible and not valid, which the README demonstrates with a US number whose area code is unused, so both checks are needed if you care about real reachability.

Why does phonenumbers.parse raise NumberParseException?

It raises when the input cannot be uniquely parsed. Error code 0 is a missing or invalid default region, which happens when you pass a national format number without a region. Error code 1 is input that could not be a phone number at all, such as arbitrary text. Passing a number in E164 form with a leading plus and a None region avoids both.

Can python-phonenumbers find phone numbers inside a block of text?

Yes, `PhoneNumberMatcher` iterates over matches in a string and yields `PhoneNumberMatch` objects that hold a parsed number plus the start and end offsets of the match. That offset information is what makes it usable for redaction or highlighting, since you know exactly which characters to replace.

Official sources

  1. daviddrysdale/python-phonenumbers on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
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/daviddrysdale-python-phonenumbers.svg)](https://hysenlabs.com/projects/daviddrysdale-python-phonenumbers)