rmsramos/postal-code
rmsramos/postal-code is a Laravel package for working with postal codes: validate, format, and look up address data through a clean, configurable API. Lightweight and easy to drop into forms, checkout, or shipping flows.
composer require rmsramos/postal-code. Use ^0.1.1 or later if you're on Laravel 11 — v0.1.1 is the first release that declares Laravel 11 support, so earlier constraints can fail dependency resolution or silently pull an incompatible illuminate/support range.bootstrap/providers.php is needed. If you're upgrading an app from 10 → 11, this is the only package-side change required.config/postal-code.php if the package ships one (php artisan vendor:publish --tag=postal-code-config), and src/ for the client class that actually performs the HTTP call.Wrap the package, don't call it from controllers. Put a thin adapter around it so a provider change (or the package's 1.0 API break) doesn't ripple through your codebase:
interface PostalCodeResolver
{
public function lookup(string $postalCode): ?Address;
}
final class PackagePostalCodeResolver implements PostalCodeResolver
{
public function lookup(string $postalCode): ?Address
{
try {
$result = PostalCode::search($this->normalize($postalCode));
} catch (Throwable $e) {
report($e);
return null;
}
return $result ? Address::fromArray((array) $result) : null;
}
private function normalize(string $value): string
{
return preg_replace('/\D/', '', $value);
}
}
Bind it in a service provider and inject the interface. Every other pattern below assumes this seam.
Cache aggressively. Postal codes map to stable geography — they change on the order of years, not requests. Wrap the resolver in Cache::remember("cep:{$code}", now()->addDays(30), ...). This is the single biggest win: it removes the third-party API from your latency and rate-limit budget for repeat lookups.
Validate before you call. Use a dedicated rule for the CEP format (8 digits, optionally 00000-000) and reject bad input at the FormRequest layer. Never spend an outbound HTTP request discovering that the user typed 5 characters.
Autofill endpoint pattern. Expose a small JSON route (GET /api/postal-codes/{code}) that returns a normalized address DTO, and have your frontend debounce on input. Return 204 for "unknown code" and 422 for "malformed code" so the client can distinguish retryable from not.
Bulk enrichment via queue. For backfilling addresses on existing records, dispatch a job per chunk rather than looping in a command. Combine with the cache layer so a large import doesn't hammer the upstream service.
Testing. Fake the HTTP layer (Http::fake([...])) rather than the facade wherever possible — that exercises your adapter's parsing and error handling. Add Http::preventStrayRequests() in your base test case so no test can accidentally hit the real API.
^0.1.1 and read the changelog before bumping. Don't treat PostalCode::search() signatures as stable.--ignore-platform-reqs or a composer.json conflict override, remove that workaround after upgrading; it will mask real conflicts later.null returns across the board, check for 429s before assuming bad data — this is why report($e) in the adapter matters. Caching plus queued backfills are the mitigation.vendor:publish won't overwrite an existing file, so stale config silently persists across upgrades. Verify the base URL and timeout keys actually exist in the published file rather than assuming.Cache::lock() guard on the bulk job if you ever run more than one worker against the same upstream.src/ — the exact response shape (array vs. object vs. DTO) is the most likely thing to shift in a minor release, and it's the thing your adapter depends on.How can I help you explore Laravel packages today?