Skip to main content

Zipcodes

PyPI - Python Version PyPI crates.io Downloads Contributors

Zipcodes is a simple library for querying U.S. zipcodes. No SQLite, no network, no runtime data files — the full dataset is embedded in the package.

Since 2.0, the library is implemented in Rust and published from a single codebase as both the zipcodes Python package and the zipcodes Rust crate. The Python API is a drop-in replacement for 1.x — same functions, same dicts, same exceptions — just faster:

Operation 1.x (pure Python) 2.0 (Rust)
import zipcodes ~330 ms (loads dataset) ~5 ms (dataset loads lazily on first query, ~200 ms)
is_real("06903") ~4.2 ms ~0.03 ms
matching("77429") ~4.2 ms ~0.3 ms
similar_to("1018") ~7.2 ms ~0.3 ms
filter_by(state="TX") ~9.7 ms ~3.7 ms
>>> import zipcodes
>>> assert zipcodes.is_real('77429')
>>> assert len(zipcodes.similar_to('7742')) != 0
>>> exact_zip = zipcodes.matching('77429')[0]
>>> filtered_zips = zipcodes.filter_by(city="Cypress", state="TX")
>>> assert exact_zip in filtered_zips
>>> pprint.pprint(exact_zip)
{'acceptable_cities': [],
 'active': True,
 'area_codes': ['281', '346', '713', '832'],
 'city': 'Cypress',
 'country': 'US',
 'county': 'Harris County',
 'lat': '29.9766',
 'long': '-95.6358',
 'state': 'TX',
 'timezone': 'America/Chicago',
 'unacceptable_cities': [],
 'world_region': 'NA',
 'zip_code': '77429',
 'zip_code_type': 'STANDARD'}

The zipcode data is refreshed automatically every month — see Zipcode Data.

Installation

Python

$ python -m pip install zipcodes

Zipcodes 2.x supports Python 3.9+ and ships prebuilt wheels for Linux (x86_64, aarch64, musl), macOS, and Windows. Installing from the source distribution requires a Rust toolchain. Python 2.6+/3.2+ users are automatically served the pure-Python 1.3.0 release by pip.

Rust

$ cargo add zipcodes

New in 2.0

  • Implemented in Rust; the dataset is compiled into the extension module and decompressed lazily on first query, so import zipcodes is effectively free.
  • New functions: contains, filter_by_state, filter_by_city, filter_by_county, filter_by_timezone, filter_by_zip_code_type, filter_by_coordinates, and haversine.
  • is_valid now actually emits its DeprecationWarning (in 1.x it raised AttributeError); use is_real.
  • PyInstaller users no longer need --add-data for zips.json.bz2 — there is no data file anymore.
  • Behavioral notes for upgraders: query results are fresh dicts (mutating a result no longer mutates the shared database list), and filter_by(active=1) no longer matches active=True (pass a bool).
  • Unified validation. Input validation now lives once, in the Rust core, instead of a separate Python validator. This changes a few edge cases:
    • Breaking: is_real/matching now raise ValueError for input shorter than five characters (after trimming), instead of returning False/[].
    • Relaxations: surrounding whitespace (" 06903 ") and space-separated Zip+4 ("12345 6789") are now accepted; any input of five or more characters is normalized to its first five digits, generalizing the existing #####-#### handling.
    • Private API removal: the undocumented helpers zipcodes._clean and zipcodes._contains_nondigits (and the _digits/_valid_zipcode_length module attributes) no longer exist.

Zipcode Data

The embedded dataset (crates/zipcodes/src/zips.json.bz2) is assembled from three sources:

  • unitedstateszipcodes.org — the rich base data (city aliases, zip type, area codes, county, timezone), committed in-repo as scripts/data/zip_code_database.csv. Its bot protection prevents automated downloads, so this file is refreshed manually on occasion.
  • GeoNames (download.geonames.org/export/zip/US.zip) — GPS coordinates, fetched fresh on every update. Licensed CC BY 4.0.
  • USPS ZIP Locale Detail (postalpro.usps.com) — the authoritative list of active delivery ZIPs, fetched fresh on every update and used to add zipcodes missing from the base CSV (#23). Public domain.

A GitHub Actions workflow (update-data.yml) rebuilds the dataset on the 1st of every month (#7). If anything changed, it opens a pull request with a summary of added/removed/modified records; merging that PR tags a patch release, which publishes to PyPI and crates.io automatically.

To rebuild the dataset manually:

$ pip install xlrd
$ curl -fsSL https://download.geonames.org/export/zip/US.zip -o /tmp/geonames_us.zip
$ # download the .xls linked from https://postalpro.usps.com/ZIP_Locale_Detail
$ python scripts/update_zipcode_dataset.py \
    --base scripts/data/zip_code_database.csv \
    --gps scripts/data/zip-codes-database-FREE.csv \
    --geonames-zip /tmp/geonames_us.zip \
    --usps-xls /tmp/usps_zip_locale.xls \
    --output-bz2 crates/zipcodes/src/zips.json.bz2 \
    --summary-output /tmp/change_summary.json

Tests

The tests are defined in a declarative, table-based format that generates test cases.

$ cargo test                    # Rust unit tests
$ python tests/__init__.py      # Python suite (or: pytest tests/)

Examples

>>> from pprint import pprint
>>> import zipcodes

>>> # Simple zip-code matching.
>>> pprint(zipcodes.matching('77429'))
[{'acceptable_cities': [],
  'active': True,
  'area_codes': ['281', '346', '713', '832'],
  'city': 'Cypress',
  'country': 'US',
  'county': 'Harris County',
  'lat': '29.9766',
  'long': '-95.6358',
  'state': 'TX',
  'timezone': 'America/Chicago',
  'unacceptable_cities': [],
  'world_region': 'NA',
  'zip_code': '77429',
  'zip_code_type': 'STANDARD'}]


>>> # Handles Zip+4 zip-codes nicely. :)
>>> pprint(zipcodes.matching('77429-1145'))
[{'acceptable_cities': [],
  'active': True,
  'area_codes': ['281', '346', '713', '832'],
  'city': 'Cypress',
  'country': 'US',
  'county': 'Harris County',
  'lat': '29.9766',
  'long': '-95.6358',
  'state': 'TX',
  'timezone': 'America/Chicago',
  'unacceptable_cities': [],
  'world_region': 'NA',
  'zip_code': '77429',
  'zip_code_type': 'STANDARD'}]

>>> # Will try to handle invalid zip-codes gracefully...
>>> print(zipcodes.matching('06463'))
[]

>>> # Until it cannot.
>>> zipcodes.matching('0646a')
Traceback (most recent call last):
  ...
ValueError: Invalid characters, zipcode may only contain digits and "-".

>>> zipcodes.matching('0646')
Traceback (most recent call last):
  ...
ValueError: Invalid format, zipcode must be of the format: "#####" or "#####-####"

>>> zipcodes.matching(None)
Traceback (most recent call last):
  ...
TypeError: Invalid type, zipcode must be a string.

>>> # Whether the zip-code exists within the database.
>>> print(zipcodes.is_real('06463'))
False

>>> # How handy!
>>> print(zipcodes.is_real('06469'))
True

>>> # Search for zipcodes that begin with a pattern.
>>> pprint(zipcodes.similar_to('1018'))
[{'acceptable_cities': [],
  'active': False,
  'area_codes': ['212'],
  'city': 'New York',
  'country': 'US',
  'county': 'New York County',
  'lat': '40.71',
  'long': '-74',
  'state': 'NY',
  'timezone': 'America/New_York',
  'unacceptable_cities': ['J C Penney'],
  'world_region': 'NA',
  'zip_code': '10184',
  'zip_code_type': 'UNIQUE'},
 {'acceptable_cities': [],
  'active': True,
  'area_codes': ['212'],
  'city': 'New York',
  'country': 'US',
  'county': 'New York County',
  'lat': '40.7808',
  'long': '-73.9772',
  'state': 'NY',
  'timezone': 'America/New_York',
  'unacceptable_cities': ['Manhattan', 'New York City', 'NY', 'Ny City', 'Nyc'],
  'world_region': 'NA',
  'zip_code': '10185',
  'zip_code_type': 'PO BOX'}]

>>> # Use filter_by to filter a list of zip-codes by specific attribute->value pairs.
>>> pprint(zipcodes.filter_by(city="Old Saybrook"))
[{'acceptable_cities': [],
  'active': True,
  'area_codes': ['860', '959'],
  'city': 'Old Saybrook',
  'country': 'US',
  'county': 'Middlesex County',
  'lat': '41.2913',
  'long': '-72.385',
  'state': 'CT',
  'timezone': 'America/New_York',
  'unacceptable_cities': ['Fenwick'],
  'world_region': 'NA',
  'zip_code': '06475',
  'zip_code_type': 'STANDARD'}]

>>> # Arbitrary nesting of similar_to and filter_by calls, allowing for great precision while filtering.
>>> pprint([z['zip_code'] for z in zipcodes.similar_to('2', zips=zipcodes.filter_by(active=True, city='Windsor'))])
['23487', '27983', '29856']

>>> # Find zipcodes within a radius (miles) of a coordinate.
>>> pprint([z['zip_code'] for z in zipcodes.filter_by_coordinates(41.3015, -72.3879, 5)])
['06371', '06409', '06426', '06442', '06475', '06498']

>>> # Have any other ideas? Make a pull request and start contributing today!
>>> # Made with love by Sean Pianka

Repository layout

This repository builds both packages from one Rust core:

  • crates/zipcodes — the core library, published to crates.io.
  • crates/zipcodes-py — PyO3 bindings (not published to crates.io).
  • python/zipcodes — the thin Python compatibility layer; together with the bindings it forms the PyPI package, built with maturin.

Metadata

Release files for zipcodes 3.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zipcodes 3.0.0
File Size Uploaded
zipcodes-3.0.0.tar.gz 734.5 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for zipcodes 3.0.0
File
zipcodes-3.0.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl CPython 3.9 abi3 Linux musl 1.2+ x86-64 Details
zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details
zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl CPython 3.9 abi3 macOS 11.0+ ARM64, macOS 10.12+ x86-64, macOS 10.12+ universal2 (ARM64, x86-64) Details

Total release size: 7.0 MB

Release files / zipcodes-3.0.0.tar.gz

Download URL zipcodes-3.0.0.tar.gz
Size 734.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9a9426527f1288d334444a4de7d1960d9f6670bb26b843b274234fcf95ce4497
BLAKE2b-256 checksum
How to use checksums
fda64f0b4666c54eccffe057124b63e4385b80598dd94a967d6d5eb0a6363ae7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / zipcodes-3.0.0-cp39-abi3-win_amd64.whl

Download URL zipcodes-3.0.0-cp39-abi3-win_amd64.whl
Size 930.2 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
57e61aa5182aaeec2a1f44af3d0358c54c0495aaa0c08f8aebb022c94aed5682
BLAKE2b-256 checksum
How to use checksums
37a09ab7dc8b1884141de27cc9677b25e98061eb5b46e9a2eabc3e748765e8b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl

Download URL zipcodes-3.0.0-cp39-abi3-musllinux_1_2_x86_64.whl
Size 1.3 MB
Tags CPython 3.9 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
2f2ce64cd478f753ac9cda90095889e25b6f66cc3b1ebc0f30809d87e40d19a6
BLAKE2b-256 checksum
How to use checksums
3375225120a686fb32df12719f1cdf363b701a0471507960f2393933677c3224
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL zipcodes-3.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.0 MB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
862d8f6b6b82b50f5268ae2651cf08af1892526ea929fbe658d3872ebe395b1a
BLAKE2b-256 checksum
How to use checksums
ea0d46a183b1c500f81d3013c423557a469b1a3daf2a08627f71fc718a9b94a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL zipcodes-3.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.0 MB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
6cb450f427d5568530e4c6a6a497c0d572eaacc987c6f75f8a92b206d6056280
BLAKE2b-256 checksum
How to use checksums
8e6b4ef5c0a557c042c18d87bcb8a00708083c9ab4d3d5660bc911134a9ef6bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL zipcodes-3.0.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 2.0 MB
Tags CPython 3.9 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
70b1d5687930c186c883ef1c05cf76c3d05a68191162e2f3f47a132dc81571a7
BLAKE2b-256 checksum
How to use checksums
a00c5c001eb2ee68cd143d57e2c23f47bc6eb88ed8bb8bd73876bd0541c626d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.0.0 This release

6 release files

2.0.1

6 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page