zipcoder-laravel maintained by pralhadstha
Zipcoder Laravel
A Laravel package to resolve postal codes and zip codes to addresses. Supports multiple geocoding providers with automatic failover, built-in caching, Facade support, and publishable config.
Why Zipcoder Laravel?
- Multiple providers — GeoNames, Zippopotamus, Zipcodestack, Zipcodebase, and JP Postal Code out of the box
- Automatic failover — Chain of Responsibility pattern tries providers in order until one succeeds
- Built-in caching — PSR-16 cache integration with configurable TTL and store, reducing API calls
- Zero config for free providers — Zippopotamus and JP Postal Code work without API keys
- Custom providers — Register your own provider via config, no package forking needed
- Laravel-native — Auto-discovery, Facade, publishable config, env-driven settings
Requirements
- PHP 8.2 or higher
- Laravel 11, 12, or 13
Installation
composer require pralhadstha/zipcoder-laravel
The package auto-discovers the service provider and facade. No manual registration needed.
Publish the configuration file:
php artisan vendor:publish --tag=zipcoder-config
Configuration
The published config file (config/zipcoder.php) has four sections:
HTTP Client
'http' => [
'timeout' => env('ZIPCODER_HTTP_TIMEOUT', 10),
'connect_timeout' => env('ZIPCODER_HTTP_CONNECT_TIMEOUT', 5),
],
Uses Guzzle 7+ (ships with Laravel) implementing PSR-18. You can override by binding your own ClientInterface in the container.
Providers
Configure credentials for each provider:
'providers' => [
'geonames' => [
'username' => env('GEONAMES_USERNAME'),
],
'zippopotamus' => [
'enabled' => true,
],
'zipcodestack' => [
'api_key' => env('ZIPCODESTACK_API_KEY'),
],
'zipcodebase' => [
'api_key' => env('ZIPCODEBASE_API_KEY'),
],
'jp-postal-code' => [
'enabled' => true,
'locale' => env('JP_POSTAL_CODE_LOCALE', 'en'),
],
],
Providers with missing credentials are automatically skipped.
Chain Order
Controls the order in which providers are attempted. The first provider to return a result wins:
'chain' => [
'jp-postal-code',
'zippopotamus',
'geonames',
'zipcodebase',
'zipcodestack',
],
Cache
'cache' => [
'enabled' => env('ZIPCODER_CACHE_ENABLED', true),
'ttl' => env('ZIPCODER_CACHE_TTL', 86400), // 24 hours
'store' => env('ZIPCODER_CACHE_STORE'), // null = default store
],
Usage
Basic Lookup
use Pralhad\Zipcoder\Laravel\Facades\Zipcoder;
use Pralhad\Zipcoder\Query;
$results = Zipcoder::lookup(Query::create('10001', 'US'));
$address = $results->first();
echo $address->city; // "New York"
echo $address->state; // "New York"
echo $address->stateCode; // "NY"
echo $address->countryCode; // "US"
echo $address->latitude; // 40.7484
echo $address->longitude; // -73.9967
Iterating Results
$results = Zipcoder::lookup(Query::create('100-0001', 'JP'));
foreach ($results as $address) {
echo "{$address->city}, {$address->state}" . PHP_EOL;
}
Checking for Results
$results = Zipcoder::lookup(Query::create('10001', 'US'));
if ($results->isEmpty()) {
// No addresses found
}
echo $results->count(); // Number of addresses returned
Converting to Array
$array = $results->first()->toArray();
// or all results
$allArrays = $results->toArray();
Available Providers
| Provider | Credentials | Countries | Notes |
|---|---|---|---|
| Zippopotamus | None | Multiple | Free, no API key required. Default fallback. |
| GeoNames | username |
Multiple | Free account at geonames.org |
| Zipcodebase | api_key |
Multiple | API key from zipcodebase.com |
| Zipcodestack | api_key |
Multiple | API key from zipcodestack.com |
| JP Postal Code | None | Japan only | Supports en and ja locales |
Custom Providers
Create a class implementing Pralhad\Zipcoder\Contract\Provider:
namespace App\Zipcoder;
use Pralhad\Zipcoder\Contract\Provider;
use Pralhad\Zipcoder\Query;
use Pralhad\Zipcoder\Result\AddressCollection;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
class MyProvider implements Provider
{
public function __construct(
private ClientInterface $client,
private RequestFactoryInterface $requestFactory,
private string $api_url,
) {}
public function lookup(Query $query): AddressCollection
{
// Your implementation
}
public function getName(): string
{
return 'my-provider';
}
}
Register it in the config:
'providers' => [
// ... built-in providers
'my-provider' => [
'class' => \App\Zipcoder\MyProvider::class,
'enabled' => true,
'api_url' => 'https://api.example.com',
],
],
'chain' => [
'my-provider', // Add to chain order
'zippopotamus',
// ...
],
The $client and $requestFactory are injected automatically. Extra config keys (like api_url) are passed as constructor parameters.
Caching
Caching is enabled by default with a 24-hour TTL. Results are cached per postal code and country code using Laravel's cache store.
To disable caching:
ZIPCODER_CACHE_ENABLED=false
To change TTL (in seconds):
ZIPCODER_CACHE_TTL=3600
To use a specific cache store:
ZIPCODER_CACHE_STORE=redis
Facade API Reference
// Look up addresses for a postal code
Zipcoder::lookup(Query $query): AddressCollection
// Use a specific provider (bypasses chain)
Zipcoder::using(string $providerName): Provider
// Register an additional provider at runtime
Zipcoder::registerProvider(Provider $provider): ZipcoderLookup
// List all registered provider names
Zipcoder::getRegisteredProviders(): array
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Contributions are welcome! Please see CONTRIBUTING for details.
Security
If you discover a security vulnerability, please see our Security Policy. Do not open a public issue for security vulnerabilities.
License
MIT. See LICENSE for details.