phonenumbers review
phonenumbers 9.0.37 is the Python port of Google's international phone-number library and generated numbering data. `parse()` turns national input plus a region, or an international `+` number, into a `PhoneNumber`. Separate calls check possible length, current assigned patterns, number type, and region, then format national, international, RFC3966, or E.164 output. Stateful formatting and text matching cover interactive fields and extraction. Optional modules map a number to a plan-based place description, original carrier, or possible time zones. Version 9.0.37 syncs upstream metadata changes for 17 regions plus alternate-format, short-number, geocoding, carrier, and time-zone data.
phonenumbers 9.0.37 installed in 0.6 seconds as 1 pure-Python package using 23 MB, imported in 0.10 seconds, and produced 0 audit findings in our sandbox. Install it for international parsing and plan checks; keep ownership verification and live carrier claims outside this dependency.
We installed it
| Install | ✓ · 0.6s | 1 package on disk · 23 MB |
| Import | ✓ | import phonenumbers in 0.10s · pure Python · py.typed · requires Python >=2.5 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does phonenumbers install cleanly?
Yes. In a fresh container with an empty cache, pip install phonenumbers finished in 0.6s, leaving 1 package and 23 MB on disk. pip-audit reported no known vulnerabilities.
What does phonenumbers need to run?
Python >=2.5, and nothing compiled: it is pure Python. In our run import phonenumbers succeeded in 0.10s, and the package ships py.typed for type checkers.
phonenumbers or phonenumberslite: which should you use?
phonenumberslite: Choose it for the same parsing and validation API without geocoder, carrier, or time-zone data. phonenumbers 9.0.37 installed in 0.6 seconds as 1 pure-Python package using 23 MB, imported in 0.10 seconds, and produced 0 audit findings in our sandbox.
When should you not use phonenumbers?
You need proof that a person controls the line. A valid result matches numbering metadata; only a verification call or message tests possession.
Use it if
- A backend accepts national phone input from multiple countries and stores a normalized E.164 value.
- Validation must distinguish an impossible length from digits that do not match the current numbering plan.
- Documents need phone matches with exact source offsets for linking, redaction, or review.
- The product needs as-you-type display, short-number checks, or plan-based region and time-zone hints.
- You need proof that a person controls the line. A valid result matches numbering metadata; only a verification call or message tests possession.
- Current carrier identity drives routing. The carrier module reports original allocation and cannot account reliably for number portability.
- A bare national number arrives without a trusted dialing region. `parse()` cannot infer its country and raises `NumberParseException`.
- The process cannot spare 23 MB on disk for data it will not use. `phonenumberslite` keeps core parsing while omitting geocoder, carrier, and time-zone packages.
- Validation outcomes must never change after dependency updates. Numbering plans are revised, and 9.0.37 itself changes the accepted patterns for 17 regions.
Setup reality
We installed phonenumbers 9.0.37 in a fresh Python 3.12 Bookworm sandbox in 0.6 seconds. It left 1 package and 23 MB on disk, and pip-audit found 0 known vulnerabilities. The distribution has 0 direct dependencies, is pure Python, declares Python 2.5 or newer, and ships py.typed. import phonenumbers worked in 0.10 seconds. PyPI did not return a license value, while the GitHub repository identifies Apache-2.0.
International input beginning with + needs no region. National text does: the same digits can mean something different under GB and US. Parsing only constructs a candidate and may succeed for an impossible or unassigned number. Run is_possible_number() for the length and prefix shape, then is_valid_number() when current allocation rules are part of the decision. Catch NumberParseException for missing regions and malformed input.
Core region metadata loads when a region is first used. Geocoder, carrier, and time-zone data live behind separate imports, so a worker's first lookup can pay an extra loading cost. PhoneMetadata.load_all() moves core loading into startup. Import the extended modules there as well when predictable request latency matters. Install phonenumberslite if none of those extended lookups are needed.
Store E.164 separately from the user's display value, and model an extension as its own field when downstream systems require it. AsYouTypeFormatter keeps state; deletion or a country change means clearing it and replaying the remaining digits. A FIXED_LINE_OR_MOBILE result is expected for plans that do not encode service type in the number. Location, carrier, and time-zone results describe numbering metadata, not the subscriber's verified situation.
Patterns
Parse national input in context parse-regional-number
import phonenumbers
phone = phonenumbers.parse('020 8366 1177', 'GB')
print(phone.country_code, phone.national_number)A national string needs a valid default region; the same 020 prefix has no global meaning by itself.
Validate and store E.164 normalize-to-e164
import phonenumbers
from phonenumbers import PhoneNumberFormat
phone = phonenumbers.parse(raw_value, region)
if not phonenumbers.is_valid_number(phone):
raise ValueError('number does not match the current plan')
e164 = phonenumbers.format_number(phone, PhoneNumberFormat.E164)A valid plan match does not prove the line exists or that the user owns it; verify possession separately.
Separate shape from allocation distinguish-possible-valid
possible = phonenumbers.is_possible_number(phone)
valid = phonenumbers.is_valid_number(phone)
if not possible:
reason = phonenumbers.is_possible_number_with_reason(phone)Possibility checks length and structural rules. Validity also uses the assigned digit patterns embedded in version 9.0.37.
Catch unusable input handle-parse-exception
from phonenumbers import NumberParseException, parse
try:
phone = parse(raw_value, default_region)
except NumberParseException as exc:
return {'error_type': exc.error_type, 'message': str(exc)}parse raises for malformed text and for a national number supplied without a valid region; `error_type` lets a form classify the cause.
Produce local and international labels format-for-display
from phonenumbers import PhoneNumberFormat, format_number
national = format_number(phone, PhoneNumberFormat.NATIONAL)
international = format_number(phone, PhoneNumberFormat.INTERNATIONAL)
rfc3966 = format_number(phone, PhoneNumberFormat.RFC3966)Formatting uses the parsed plan metadata. Store E.164 as the normalized identity and derive display strings when needed.
Handle ambiguous service types classify-line-type
from phonenumbers import PhoneNumberType, number_type
kind = number_type(phone)
sms_candidate = kind in (
PhoneNumberType.MOBILE,
PhoneNumberType.FIXED_LINE_OR_MOBILE,
)Some numbering plans cannot distinguish mobile from fixed service by digits, so `FIXED_LINE_OR_MOBILE` needs an explicit product rule.
Apply as-you-type formatting format-typed-digits
from phonenumbers import AsYouTypeFormatter
formatter = AsYouTypeFormatter('US')
rendered = ''
for digit in '6502532222':
rendered = formatter.input_digit(digit)
print(rendered)AsYouTypeFormatter is stateful. Call `clear()` and replay the remaining digits after deletion or a region switch.
Find phone spans in prose match-phone-text
from phonenumbers import PhoneNumberMatcher, PhoneNumberFormat, format_number
for match in PhoneNumberMatcher(text, 'US'):
e164 = format_number(match.number, PhoneNumberFormat.E164)
print(match.start, match.end, match.raw_string, e164)Each match includes source offsets. Matching can still confuse number-shaped identifiers, so review the surrounding text for high-risk automation.
Map a parsed number to a region lookup-region-code
from phonenumbers import region_code_for_number
region = region_code_for_number(phone)
if region != allowed_region:
raise ValueError('number belongs to another numbering plan')Region is derived from numbering metadata and may be absent for non-geographic services or ambiguous shared calling codes.
Test an emergency short number check-emergency-number
from phonenumbers import shortnumberinfo
is_emergency = shortnumberinfo.is_emergency_number('911', 'US')Short-number rules require a region and can differ from ordinary PhoneNumber validation; never use this check to route an actual emergency call.
Read allocation-based hints read-carrier-location
from phonenumbers import carrier, geocoder, timezone
original_carrier = carrier.name_for_number(phone, 'en')
place = geocoder.description_for_number(phone, 'en')
zones = timezone.time_zones_for_number(phone)Carrier data can be stale after portability, and the place or time-zone result does not prove where the subscriber is now.
Pay metadata cost during startup preload-metadata
from phonenumbers import PhoneMetadata
import phonenumbers.carrier
import phonenumbers.geocoder
import phonenumbers.timezone
PhoneMetadata.load_all()Core metadata otherwise loads by region on first use; importing extended modules also loads their separate data sets.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| phonenumberslite | PyPI | Choose it for the same parsing and validation API without geocoder, carrier, or time-zone data. |
| django-phonenumber-field | PyPI | Choose it when Django model fields, forms, and serializers should wrap phonenumbers behavior. |
| python-stdnum | PyPI | Choose it when phone-related checks sit inside a wider job covering tax, bank, and business identifiers. |
More utils guides
lru-cache · type-fest · ajv · zod · p-limit · find-up · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

