PHPackages                             pralhadstha/zipcoder-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. pralhadstha/zipcoder-php

ActiveLibrary[HTTP &amp; Networking](/categories/http)

pralhadstha/zipcoder-php
========================

PHP postal code to address lookup with multi-provider support and automatic fallback

v1.0.1(2mo ago)1251MITPHPPHP ^8.2CI passing

Since Apr 5Pushed 2mo agoCompare

[ Source](https://github.com/pralhadstha/Zipcoder-php)[ Packagist](https://packagist.org/packages/pralhadstha/zipcoder-php)[ Docs](https://github.com/pralhadstha/zipcoder-php)[ RSS](/packages/pralhadstha-zipcoder-php/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (2)Dependencies (9)Versions (3)Used By (1)

Zipcoder
========

[](#zipcoder)

**A PHP library to convert postal codes and zip codes into structured addresses using multiple geocoding APIs with automatic fallback.**

[![Latest Version on Packagist](https://camo.githubusercontent.com/3d670c60129ddbe0482ac09537be460b82f70d6ec47c03f886038b783213817a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7072616c686164737468612f7a6970636f6465722d7068702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pralhadstha/zipcoder-php)[![PHP Version](https://camo.githubusercontent.com/6abe06e66198e9819d7f1759a0e5cb58524c78cef7bb539fb5a397a4dd6f55cc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f7072616c686164737468612f7a6970636f6465722d7068702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pralhadstha/zipcoder-php)[![License](https://camo.githubusercontent.com/b3236702fcc5c55f7b86b6b91de52978290bb11ed0b7069a5b763ddef2f6922a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7072616c686164737468612f7a6970636f6465722d7068702e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![Total Downloads](https://camo.githubusercontent.com/f8bffb9f87e309d4ad5d127239e60fd5b8954d7689387e84eae04c4c29da6aea/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7072616c686164737468612f7a6970636f6465722d7068702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pralhadstha/zipcoder-php)[![PHPStan Level 9](https://camo.githubusercontent.com/fa7d257d0c5c1cf237ac3490ef3a5561626b17fcb0a8547c01b0bb8746554e60/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](https://phpstan.org/)[![Tests](https://camo.githubusercontent.com/f1ea837104e3a2dd7c27a24030d7301a113cc65bda7b2f0aaae8882aba438846/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f7072616c686164737468612f7a6970636f6465722d7068702f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/pralhadstha/zipcoder-php/actions)

---

Zipcoder turns any postal code into a normalized address (city, state, province, country, and coordinates) by querying postal code lookup APIs behind a unified interface. If one provider fails or has no data, the next one in the chain picks up automatically. No more vendor lock-in to a single zip code API.

**Use cases:** shipping &amp; logistics address autofill, checkout form validation, postal code to city/state resolution, international address lookup, and geocoding from zip codes.

Key Features
------------

[](#key-features)

- **5 built-in postal code providers** — GeoNames, Zippopotamus, Zipcodestack, Zipcodebase, and JpPostalCode
- **Automatic fallback** — Chain of Responsibility pattern tries providers in order until one succeeds
- **PSR-16 caching** — decorator wraps any provider to cache results and reduce API calls
- **PSR-18 HTTP** — bring your own HTTP client (Guzzle, Symfony HttpClient, etc.) or use the included zero-dependency curl client
- **Normalized results** — every provider returns the same `Address` structure regardless of the underlying API format
- **100+ countries** — covers postal codes worldwide; Japan-specific provider with English, Japanese, and Kana output
- **Zero required dependencies** — only PSR interfaces; all implementations are optional
- **Extensible** — implement the `Provider` interface to add any postal code API
- **PHP 8.2+** — readonly classes, constructor promotion, strict types throughout
- **PHPStan level 9** — fully statically analyzed

Supported Providers
-------------------

[](#supported-providers)

ProviderCountriesAuth RequiredFree TierBest For[GeoNames](https://www.geonames.org/)100+Free username10,000/dayPrimary global provider[Zippopotamus](https://zippopotam.us/)~60NoneUnlimitedQuick lookups, zero config[Zipcodestack](https://zipcodestack.com/)210+API key300/monthBroadest country coverage[Zipcodebase](https://zipcodebase.com/)100+API key10,000/monthGood middle-ground[JpPostalCode](https://github.com/ttskch/jp-postal-code-api)JapanNoneUnlimitedJapan addresses in EN/JA/KanaRequirements
------------

[](#requirements)

- PHP 8.2 or higher
- `ext-curl` (if using the built-in `CurlPsr18Client`)
- A PSR-18 HTTP client (e.g., Guzzle 7) or use the included curl client

Installation
------------

[](#installation)

```
composer require pralhadstha/zipcoder-php
```

For production, Guzzle is recommended as the HTTP client:

```
composer require pralhadstha/zipcoder-php guzzlehttp/guzzle
```

Framework Integration
---------------------

[](#framework-integration)

If you are using a framework, check out our dedicated integration package:

FrameworkPackageStats[Laravel](https://github.com/pralhadstha/Zipcoder-laravel)`pralhadstha/zipcoder-laravel`[![Latest Stable Version](https://camo.githubusercontent.com/0c40949ce781697b4a2a7e5dc7c0a9cf0a3ea56e5ed2c1cfad8df55e2234d059/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7072616c686164737468612f7a6970636f6465722d6c61726176656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pralhadstha/zipcoder-laravel) [![Total Downloads](https://camo.githubusercontent.com/071c158c5775f69f296831ec8aad58701232c4f41637b26690221cc9b59c6026/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7072616c686164737468612f7a6970636f6465722d6c61726176656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pralhadstha/zipcoder-laravel)Quick Start
-----------

[](#quick-start)

Look up a US zip code and get the city and state in 4 lines:

```
use Pralhad\Zipcoder\Http\CurlPsr18Client;
use Pralhad\Zipcoder\Provider\Zippopotamus;
use Pralhad\Zipcoder\Query;

$http = new CurlPsr18Client();
$provider = new Zippopotamus($http, $http);

$result = $provider->lookup(Query::create('90210', 'US'));

$address = $result->first();
echo $address->city;      // "Beverly Hills"
echo $address->state;     // "California"
echo $address->stateCode; // "CA"
echo $address->latitude;  // 34.0901
echo $address->longitude; // -118.4065
```

### With Guzzle

[](#with-guzzle)

```
use GuzzleHttp\Client;
use Pralhad\Zipcoder\Provider\GeoNames;
use Pralhad\Zipcoder\Query;

$guzzle = new Client(['timeout' => 10]);
$provider = new GeoNames($guzzle, $guzzle, username: 'your_geonames_username');

$result = $provider->lookup(Query::create('100-0001', 'JP'));
echo $result->first()->city; // "Chiyoda"
```

### With Automatic Fallback (Chain)

[](#with-automatic-fallback-chain)

```
use Pralhad\Zipcoder\Http\CurlPsr18Client;
use Pralhad\Zipcoder\Provider\Chain;
use Pralhad\Zipcoder\Provider\GeoNames;
use Pralhad\Zipcoder\Provider\JpPostalCode;
use Pralhad\Zipcoder\Provider\Zippopotamus;
use Pralhad\Zipcoder\Query;

$http = new CurlPsr18Client();

$provider = new Chain([
    new JpPostalCode($http, $http),                       // Japan: free, best data
    new Zippopotamus($http, $http),                        // 60 countries: free, fast
    new GeoNames($http, $http, 'your_geonames_username'),  // 100+ countries: free tier
]);

// Japan postal code uses JpPostalCode provider
$result = $provider->lookup(Query::create('100-0014', 'JP'));

// For US zip code, JpPostalCode skips (not JP), Zippopotamus handles it
$result = $provider->lookup(Query::create('90210', 'US'));
```

Usage
-----

[](#usage)

### Creating Queries

[](#creating-queries)

A `Query` represents a postal code lookup request. It validates input and normalizes the postal code:

```
use Pralhad\Zipcoder\Query;

$query = Query::create('90210', 'US');
$query->postalCode;           // "90210"
$query->countryCode;          // "US"
$query->normalizedPostalCode(); // "90210" (strips hyphens and spaces)

// Japanese postal codes with hyphens are normalized
$query = Query::create('100-0014', 'JP');
$query->normalizedPostalCode(); // "1000014"
```

### Working with Results

[](#working-with-results)

Every provider returns an `AddressCollection` containing normalized `Address` objects:

```
$result = $provider->lookup(Query::create('10005', 'US'));

// Access the first result
$address = $result->first();
$address->postalCode;   // "10005"
$address->countryCode;  // "US"
$address->countryName;  // "United States" (if available)
$address->city;         // "New York City"
$address->state;        // "New York"
$address->stateCode;    // "NY"
$address->province;     // Province (if available)
$address->district;     // District (if available)
$address->latitude;     // 40.7063
$address->longitude;    // -74.0089
$address->provider;     // "zipcodebase" (which provider returned this)

// Iterate all results
foreach ($result as $address) {
    echo "{$address->city}, {$address->state}\n";
}

// Collection helpers
$result->count();    // Number of addresses
$result->isEmpty();  // true if no results
$result->toArray();  // Convert all addresses to arrays
```

### Providers

[](#providers)

**GeoNames** — 100+ countries, free username registration```
use Pralhad\Zipcoder\Provider\GeoNames;

// Register at https://www.geonames.org/login to get a free username
$provider = new GeoNames($httpClient, $requestFactory, username: 'your_username');
$result = $provider->lookup(Query::create('100-0001', 'JP'));
```

Returns: `postalCode`, `countryCode`, `city`, `state`, `stateCode`, `province`, `latitude`, `longitude`

**Zippopotamus** — ~60 countries, no authentication needed```
use Pralhad\Zipcoder\Provider\Zippopotamus;

// No API key or username needed
$provider = new Zippopotamus($httpClient, $requestFactory);
$result = $provider->lookup(Query::create('90210', 'US'));
```

Returns: `postalCode`, `countryCode`, `countryName`, `city`, `state`, `stateCode`, `latitude`, `longitude`

**Zipcodestack** — 210+ countries, broadest coverage```
use Pralhad\Zipcoder\Provider\Zipcodestack;

// Get an API key at https://zipcodestack.com
$provider = new Zipcodestack($httpClient, $requestFactory, apiKey: 'your_api_key');
$result = $provider->lookup(Query::create('44600', 'NP'));
```

Returns: `postalCode`, `countryCode`, `city`, `state`, `province`, `latitude`, `longitude`

**Zipcodebase** — 100+ countries, 10k requests/month free```
use Pralhad\Zipcoder\Provider\Zipcodebase;

// Get an API key at https://zipcodebase.com
$provider = new Zipcodebase($httpClient, $requestFactory, apiKey: 'your_api_key');
$result = $provider->lookup(Query::create('10005', 'US'));
```

Returns: `postalCode`, `countryCode`, `city`, `state`, `stateCode`, `province`, `latitude`, `longitude`

**JpPostalCode** — Japan-only, free, supports English/Japanese/Kana```
use Pralhad\Zipcoder\Provider\JpPostalCode;

// English output (default)
$provider = new JpPostalCode($httpClient, $requestFactory, locale: 'en');
$result = $provider->lookup(Query::create('100-0014', 'JP'));
$result->first()->city; // "Chiyoda-ku"

// Japanese output
$provider = new JpPostalCode($httpClient, $requestFactory, locale: 'ja');
$result = $provider->lookup(Query::create('100-0014', 'JP'));
$result->first()->city; // "千代田区"

// Kana output
$provider = new JpPostalCode($httpClient, $requestFactory, locale: 'kana');
$result = $provider->lookup(Query::create('100-0014', 'JP'));
$result->first()->city; // "チヨダク"
```

Returns: `postalCode`, `countryCode` (JP), `countryName` (Japan), `state`, `stateCode`, `city`, `district`

> Automatically skips non-JP queries in a chain — no API call is made.

### Chain Provider (Automatic Fallback)

[](#chain-provider-automatic-fallback)

The `Chain` provider implements the Chain of Responsibility pattern. It tries each provider in order and returns the first successful result. If a provider throws an error or returns no data, it moves to the next one.

```
use Pralhad\Zipcoder\Provider\Chain;
use Psr\Log\LoggerInterface;

$chain = new Chain(
    providers: [
        new JpPostalCode($http, $http),
        new Zippopotamus($http, $http),
        new GeoNames($http, $http, 'username'),
        new Zipcodebase($http, $http, 'api_key'),
        new Zipcodestack($http, $http, 'api_key'),
    ],
    logger: $psrLogger, // Optional PSR-3 logger for debugging fallback behavior
);

$result = $chain->lookup(Query::create('44600', 'NP'));
```

**How fallback works:**

1. Each provider is tried in the order given
2. If a provider returns addresses, that result is returned immediately
3. If a provider throws `NoResult` or `HttpError`, the chain logs a warning and continues
4. If a provider returns an empty collection, the chain skips to the next
5. `InvalidArgument` exceptions (programming errors) are **not** caught — they bubble up
6. If all providers fail, a `NoResult` exception is thrown

### Cache Provider (PSR-16)

[](#cache-provider-psr-16)

The `Cache` decorator wraps any provider with PSR-16 caching to avoid redundant API calls:

```
use Pralhad\Zipcoder\Provider\Cache;
use Psr\SimpleCache\CacheInterface;

// Wrap any provider (or a chain) with caching
$cached = new Cache(
    provider: $chain,
    cache: $psrCache,    // Any PSR-16 cache (Laravel, Symfony, php-cache, etc.)
    ttl: 86400,          // Cache for 24 hours (default)
);

// First call: hits the API, stores result in cache
$result = $cached->lookup(Query::create('90210', 'US'));

// Second call: returns from cache, no API call
$result = $cached->lookup(Query::create('90210', 'US'));
```

Cache key format: `zipcoder:{COUNTRY_CODE}:{NORMALIZED_CODE}` (e.g., `zipcoder:US:90210`, `zipcoder:JP:1000014`)

### ZipcoderLookup (Provider Registry)

[](#zipcoderlookup-provider-registry)

`ZipcoderLookup` is a convenience aggregator to register and access providers by name:

```
use Pralhad\Zipcoder\ZipcoderLookup;

$zipcoder = new ZipcoderLookup();
$zipcoder->registerProvider($cachedChain);
$zipcoder->registerProvider(new Zippopotamus($http, $http));

// Use the first registered provider
$result = $zipcoder->lookup(Query::create('90210', 'US'));

// Use a specific provider by name
$result = $zipcoder->using('zippopotamus')->lookup(Query::create('90210', 'US'));

// List registered providers
$zipcoder->getRegisteredProviders(); // ['cache(chain)', 'zippopotamus']
```

### Full Production Example

[](#full-production-example)

```
use Pralhad\Zipcoder\Http\CurlPsr18Client;
use Pralhad\Zipcoder\Provider\Cache;
use Pralhad\Zipcoder\Provider\Chain;
use Pralhad\Zipcoder\Provider\GeoNames;
use Pralhad\Zipcoder\Provider\JpPostalCode;
use Pralhad\Zipcoder\Provider\Zipcodebase;
use Pralhad\Zipcoder\Provider\Zipcodestack;
use Pralhad\Zipcoder\Provider\Zippopotamus;
use Pralhad\Zipcoder\Query;
use Pralhad\Zipcoder\ZipcoderLookup;

$http = new CurlPsr18Client(timeout: 10);

$chain = new Chain([
    new JpPostalCode($http, $http),
    new Zippopotamus($http, $http),
    new GeoNames($http, $http, 'your_username'),
    new Zipcodebase($http, $http, 'your_api_key'),
    new Zipcodestack($http, $http, 'your_api_key'),
]);

$provider = new Cache($chain, $yourPsr16Cache, ttl: 3600);

$zipcoder = new ZipcoderLookup();
$zipcoder->registerProvider($provider);

$result = $zipcoder->lookup(Query::create('100-0014', 'JP'));
$address = $result->first();

echo "{$address->city}, {$address->state}, {$address->countryCode}";
// "Chiyoda-ku, Tokyo, JP"
```

Creating a Custom Provider
--------------------------

[](#creating-a-custom-provider)

Implement the `Provider` interface or extend `AbstractHttpProvider` to integrate any postal code API:

```
use Pralhad\Zipcoder\Contract\Provider;
use Pralhad\Zipcoder\Provider\AbstractHttpProvider;
use Pralhad\Zipcoder\Query;
use Pralhad\Zipcoder\Result\Address;
use Pralhad\Zipcoder\Result\AddressCollection;

final class MyApiProvider extends AbstractHttpProvider
{
    public function lookup(Query $query): AddressCollection
    {
        $url = "https://my-api.com/lookup?code={$query->postalCode}&country={$query->countryCode}";
        $data = $this->fetchJson($url);

        $addresses = array_map(
            fn (array $item) => new Address(
                postalCode: $query->postalCode,
                countryCode: $query->countryCode,
                city: $item['city'] ?? null,
                state: $item['region'] ?? null,
                latitude: isset($item['lat']) ? (float) $item['lat'] : null,
                longitude: isset($item['lng']) ? (float) $item['lng'] : null,
                provider: $this->getName(),
            ),
            $data['results'] ?? [],
        );

        return new AddressCollection($addresses);
    }

    public function getName(): string
    {
        return 'my-api';
    }
}
```

Then add it to your chain:

```
$chain = new Chain([
    new MyApiProvider($http, $http),
    new GeoNames($http, $http, 'username'),
]);
```

Error Handling
--------------

[](#error-handling)

Zipcoder uses a clear exception hierarchy:

ExceptionWhenCaught by Chain?`NoResult`No address found for the postal codeYes (falls back)`HttpError`Network failure or HTTP errorYes (falls back)`ProviderNotRegistered`Unknown provider name in `ZipcoderLookup::using()`No`InvalidArgument`Invalid input (empty postal code, bad country code)No (programming error)```
use Pralhad\Zipcoder\Exception\NoResult;
use Pralhad\Zipcoder\Exception\HttpError;
use Pralhad\Zipcoder\Exception\InvalidArgument;

try {
    $result = $provider->lookup(Query::create('99999', 'XX'));
} catch (NoResult $e) {
    // No provider could resolve this postal code
    echo "Not found: {$e->getMessage()}";
} catch (HttpError $e) {
    // All providers had network/HTTP errors
    echo "Service error: {$e->getMessage()}";
} catch (InvalidArgument $e) {
    // Bad input — fix the query
    echo "Invalid input: {$e->getMessage()}";
}
```

HTTP Client Options
-------------------

[](#http-client-options)

Zipcoder accepts any PSR-18 compatible HTTP client. The first constructor argument is the `ClientInterface`, the second is the `RequestFactoryInterface`. Many clients (Guzzle 7, the included curl client) implement both.

ClientInstallExampleBuilt-in curlIncluded`new CurlPsr18Client(timeout: 10)`Guzzle 7`composer require guzzlehttp/guzzle``new \GuzzleHttp\Client(['timeout' => 10])`Symfony HttpClient`composer require symfony/http-client`PSR-18 adapterTesting
-------

[](#testing)

```
# Run unit tests (mocked HTTP, no API calls)
vendor/bin/phpunit --testsuite Unit

# Run static analysis
vendor/bin/phpstan analyse

# Format code
vendor/bin/pint
```

Contributing
------------

[](#contributing)

Contributions are welcome! Please:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/my-provider`)
3. Write tests for any new functionality
4. Ensure all tests pass (`vendor/bin/phpunit`)
5. Run static analysis (`vendor/bin/phpstan analyse`)
6. Format your code (`vendor/bin/pint`)
7. Submit a pull request

License
-------

[](#license)

Zipcoder is open-sourced software licensed under the [MIT License](LICENSE).

Copyright (c) 2026 Pralhad Kumar Shrestha

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance84

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

83d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2d46fb8b473d604d2f5eb4e27451ef88b44e10a430c0a0ce89b4e501fcfe95b2?d=identicon)[pralhad](/maintainers/pralhad)

---

Top Contributors

[![pralhadstha](https://avatars.githubusercontent.com/u/6309194?v=4)](https://github.com/pralhadstha "pralhadstha (7 commits)")

---

Tags

address-lookup-php-libraryphpphp-geocoder-postal-codephp-geocoding-libraryphp-geolocation-postal-codephp-zip-code-lookuppostal-code-to-address-phpzip-code-api-phpzipcodezipcodeapigeocodingaddresspsr-18zip codezipcodelookuppostal-codegeonamesphp-geocoderpostal-code-lookup

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pralhadstha-zipcoder-php/health.svg)

```
[![Health](https://phpackages.com/badges/pralhadstha-zipcoder-php/health.svg)](https://phpackages.com/packages/pralhadstha-zipcoder-php)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.8k19.1M1.7k](/packages/cakephp-cakephp)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k12](/packages/tempest-framework)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

84735.1k](/packages/flow-php-flow)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.4M519](/packages/shopware-core)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

185671.3k41](/packages/laudis-neo4j-php-client)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35729.6k2](/packages/telnyx-telnyx-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
