jeffersongoncalves/laravel-cep
A simple Laravel package for querying Brazilian postal codes (CEP) through multiple API providers (BrasilAPI, ViaCEP, AwesomeAPI) with database storage, CEP validation and formatting, and full Brazilian state support.
Installation
composer require jeffersongoncalves/laravel-cep
Publish the config
php artisan vendor:publish --tag=cep-config
(Publishing by provider class also works, but tag-based publishing is what the package documents and is the only reliable way to grab individual assets.)
Publish and run the migration (only if you cache to the database)
The package ships its migration as create_cep_table.php.stub (since v2.0.1) and publishes it under a dedicated tag:
php artisan vendor:publish --tag=cep-migrations
php artisan migrate
The published file lands in database/migrations/ as a normal timestamped migration. You only need it when cep.cache.driver is database; file/redis caching works without it.
Configuration
Edit config/cep.php to set your preferred provider (e.g., viacep, republicavirtual, or correios). Example:
'provider' => 'viacep',
'timeout' => 10,
'cache' => [
'enabled' => true,
'driver' => 'file', // 'file' | 'redis' | 'database'
'minutes' => 60,
],
First Query
Fetch address data for a CEP (e.g., 01001000):
use JeffersonGoncalves\LaravelCep\Facades\Cep;
$address = Cep::find('01001000');
// Returns: [
// 'cep' => '01001000',
// 'logradouro' => 'Praça da Sé',
// 'complemento' => '',
// 'bairro' => 'Sé',
// ...
// ]
Fallback Providers
Configure fallback providers in config/cep.php:
'providers' => [
'viacep' => [
'enabled' => true,
'url' => 'https://viacep.com.br/ws/{cep}/json/',
],
'republicavirtual' => [
'enabled' => false,
'url' => 'https://www.republicavirtual.com.br/web_cep.php?cep={cep}&formato=json',
],
],
$cep = '01310100';
$data = Cep::find($cep);
// Validate response
if ($data->success()) {
$street = $data->logradouro;
$district = $data->bairro;
$city = $data->localidade;
$state = $data->uf;
}
Enable caching in config/cep.php to avoid repeated API calls:
'cache' => [
'enabled' => true,
'driver' => 'redis', // or 'file', 'database'
'minutes' => 1440, // 24 hours
],
If you switch driver to database, publish the migration first (--tag=cep-migrations) and run php artisan migrate — otherwise every lookup falls through to the provider and you'll see no rows in the cep table.
Clear cache manually if needed:
php artisan cache:clear
Check the active provider and handle provider-specific quirks:
$provider = config('cep.provider');
if ($provider === 'viacep') {
// Handle ViaCEP-specific fields (e.g., 'ibge')
}
Use Laravel's collect() for batch CEP validation:
$ceps = ['01001000', '04534020', '22011000'];
$results = collect($ceps)->map(fn($cep) => Cep::find($cep));
Add a CEP-based scope to a User or Address model:
namespace App\Models;
use JeffersonGoncalves\LaravelCep\Facades\Cep;
use Illuminate\Database\Eloquent\Builder;
class Address extends Model
{
public function scopeValidCep(Builder $query, string $cep)
{
$data = Cep::find($cep);
return $data->success() ? $query->where('cep', $cep) : $query;
}
}
Usage:
$address = Address::validCep('01001000')->first();
Validate CEP format and existence:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'cep' => [
'required',
'string',
'size:8',
function ($attribute, $value, $fail) {
$data = Cep::find($value);
if (!$data->success()) {
$fail('CEP inválido ou inexistente.');
}
},
],
]);
Implement middleware to throttle CEP requests:
namespace App\Http\Middleware;
use Closure;
use JeffersonGoncalves\LaravelCep\Facades\Cep;
use Illuminate\Http\Request;
class ThrottleCepRequests
{
public function handle(Request $request, Closure $next)
{
if ($request->is('api/cep/*')) {
$limit = config('cep.rate_limit', 10);
if (Cep::getRequestCount() >= $limit) {
return response()->json(['error' => 'Limite de consultas excedido.'], 429);
}
}
return $next($request);
}
}
Environment-Specific Providers Use environment variables to switch providers:
'provider' => env('CEP_PROVIDER', 'viacep'),
Database-Backed Cache in CI/Deploy Pipelines
Because the schema now comes from a stub, you can run the publish step non-interactively in a deploy script before migrate:
php artisan vendor:publish --tag=cep-migrations --force && php artisan migrate --force
Keep --force out of the command unless you actually intend to overwrite a migration you've previously customized.
Logging Failed Requests Extend the package to log failed CEP lookups:
Cep::extend(function ($app) {
$app->resolving('cep', function ($cep, $app) {
$cep->setLogger(app(\Illuminate\Log\Logger::class));
});
});
Fallback Logic
Implement a fallback chain in AppServiceProvider:
public function boot()
{
Cep::extend(function ($app) {
$app->afterResolving('cep', function ($cep) {
if (!$cep->success() && config('cep.fallback.enabled')) {
$fallbackProvider = config('cep.fallback.provider');
$cep->setProvider($fallbackProvider)->findAgain();
}
});
});
}
Migration Publishing Tag
create_cep_table.php.stub) and is only exposed through --tag=cep-migrations. Publishing by provider class (--provider="JeffersonGoncalves\LaravelCep\CepServiceProvider") is not guaranteed to pick it up.php artisan vendor:publish --tag=cep-migrations
Upgrading From Pre-2.0.1
vendor:publish will silently skip the existing file. Diff the shipped stub against your published migration before assuming you're up to date, or republish deliberately with --force (then review the diff before committing).Database Cache Without the Migration
cep.cache.driver to database without running the migration doesn't throw at config time — it surfaces as query errors on the first lookup. Publish + migrate as part of setup, not as an afterthought.Migration Name Collisions
create_cep_table migration. Delete the stale one rather than running both.CEP Format Validation
01001000, not 01.001-000).$cleanCep = preg_replace('/[^0-9]/', '', $request->cep);
Provider-Specific Fields
viacep returns ibge, while correios may return municipio.$data->city = $data->localidade ?? $data->municipio ?? null;
Rate Limits
Cache Invalidation
cache:forget route or manual invalidation:
Cep::forgetCache('01001000');
database driver you can also inspect and prune rows directly in the cep table during incident triage.HTTPS Requirements
republicavirtual) may fail on HTTP.https://.Timeouts
config('cep.timeout') (default: 10 seconds).Provider Unavailability
Cep::setProvider('viacep')->find('01001000')->retry(3);
Enable Debug Mode
Set config('cep.debug', true) to log raw API responses:
'debug' => env('CEP_DEBUG', false),
Check HTTP Status Codes
Inspect the response object for errors:
$data = Cep::find('01001000');
if ($data->response->status() === 404) {
// CEP not found
}
Verify the Cache Actually Persisted If lookups feel slow despite caching being enabled, confirm the driver target exists:
php artisan tinker --execute="dump(DB::table('cep')->count());"
Zero rows with driver => 'database' almost always means the migration was never published/run.
List Publishable Assets When something seems missing after install:
php artisan vendor:publish --list
Confirm both cep-config and cep-migrations appear for the package.
Mock Providers for Testing Use Laravel's HTTP mocking:
$this->mock(\JeffersonGoncalves\LaravelCep\Contracts\CepProvider::class, function ($mock) {
$mock->shouldReceive('find')
->once()
How can I help you explore Laravel packages today?