Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cidadesbr Laravel Package

artesaos/cidadesbr

Laravel package that ships a ready-to-use database of Brazilian states and cities, complete with migrations, seeders, Eloquent models and relationships, plus query helpers for filtering cities by state—ideal for address forms and select inputs.

View on GitHub
Deep Wiki
Context7

Getting Started

Verify compatibility before anything else. This package's last release was in 2015, which means it targets the Laravel 4.x/5.0–5.1 era (service providers registered in config/app.php, no package auto-discovery, illuminate/support pinned to old versions). Open vendor/artesaos/cidadesbr/composer.json right after install and read the require block — that tells you more about whether it will run on your Laravel version than any README will.

composer require artesaos/cidadesbr

If you're on Laravel 5.5+, check whether the provider is auto-discovered. If not, register it manually in config/app.php:

'providers' => [
    // ...
    Artesaos\CidadesBr\CidadesBrServiceProvider::class,
],

Publish and inspect. Rather than guessing table names and seeder class names, publish the package assets and read them:

php artisan vendor:publish --provider="Artesaos\CidadesBr\CidadesBrServiceProvider"

Then look at database/migrations/ and database/seeds/ inside the published/vendor path. You want to answer three questions before writing any code:

  1. What are the table names? (Almost certainly estados and cidades.)
  2. What are the model class names and namespace? (Likely Artesaos\CidadesBr\Models\Estado and ...\Cidade — confirm in src/.)
  3. What is the seeder class name for db:seed --class=...?

Run the migration and seed once. The municipality table is large (~5,570 rows in the 2015 IBGE snapshot), so seed as a deliberate, one-off step, not as part of every migrate:

php artisan migrate
php artisan db:seed --class=CidadesBrSeeder   # confirm exact class name

First use case: a two-level address form. This is what 90% of consumers of this package use it for — a <select> of states that drives a <select> of cities. Get the state list once, then fetch cities per state:

use Artesaos\CidadesBr\Models\Estado;

// In a controller / view composer
$estados = Estado::orderBy('sigla')->get(['id', 'sigla', 'nome']);

That's the whole getting-started path: publish, migrate, seed, then Estado::... and Cidade::... in your form controller.

Implementation Patterns

Cascading state → city select

Don't render all 5,570 cities into the page. Ship states server-side and load cities over a small JSON endpoint:

// routes/web.php
Route::get('/api/estados/{estado}/cidades', function (Estado $estado) {
    return $estado->cidades()->orderBy('nome')->get(['id', 'nome']);
});
document.querySelector('#estado').addEventListener('change', async (e) => {
    const res = await fetch(`/api/estados/${e.target.value}/cidades`);
    const cidades = await res.json();
    const select = document.querySelector('#cidade');
    select.innerHTML = cidades
        .map(c => `<option value="${c.id}">${c.nome}</option>`)
        .join('');
});

Validation against the real data

Because the tables exist in your own database, you get free referential validation. Always validate the pair together — a valid cidade_id from the wrong state is a data-integrity bug:

$request->validate([
    'estado_id' => ['required', 'integer', 'exists:estados,id'],
    'cidade_id' => [
        'required', 'integer',
        Rule::exists('cidades', 'id')->where('estado_id', $request->estado_id),
    ],
]);

Store the city, denormalize the UF

Store cidade_id as the canonical key. But also persist the estado_id/sigla on the address record — it makes filtering, shipping-zone rules, and reporting far cheaper than joining back through cidades → estados on every query:

$address->fill([
    'cidade_id' => $request->cidade_id,
    'estado_id' => $request->estado_id,
    'uf'        => Estado::find($request->estado_id)->sigla,
]);

Eager loading to avoid N+1

The package ships the relationships, so use them rather than re-querying:

$orders = Order::with('cidade.estado')->paginate(50);

If the relation names differ from cidade/estado, map them once in your own models and move on.

Wrap the package in your own models

The cleanest long-term pattern: never reference Artesaos\CidadesBr\Models\* directly in application code. Subclass them (or alias them) so you can add scopes, casts, and swap the data source later without touching call sites:

namespace App\Models;

use Artesaos\CidadesBr\Models\Cidade as BaseCidade;

class Cidade extends BaseCidade
{
    public function scopeSearch($query, string $term)
    {
        return $query->where('nome', 'like', "%{$term}%");
    }
}

Then configure the package (if it exposes a config for model classes) or simply use App\Models\Cidade everywhere. This is your migration path off the package when it becomes a liability.

Caching the state list

States are static; query them once, not per request:

$estados = Cache::rememberForever('estados', fn () =>
    Estado::orderBy('sigla')->get(['id', 'sigla', 'nome'])
);

Cache cities per state ("cidades.{$estadoId}") if your form endpoint is hot.

Seeding in tests and CI

Seeding ~5,570 rows on every RefreshDatabase test run is wasteful. Seeder classes are just classes — call the specific one only in the test suite that needs it, or seed a small fixture subset in setUp():

protected function setUp(): void
{
    parent::setUp();
    $this->seed(\Artesaos\CidadesBr\Seeders\CidadesSeeder::class); // exact class from the package
}

Extending the schema

The package has no coordinates and no CEP ranges. If you need geo or postal features, publish the migration and add columns in a new migration (ALTER TABLE cidades ADD lat ...), then backfill from the IBGE API. Don't edit the published migration in place — you'll lose the change on the next publish.

Gotchas and Tips

It is effectively unmaintained. One release in 2015 means no Laravel 8/9/10/11 support, no auto-discovery, and potential deprecation warnings on PHP 8.x. Treat adoption as "vendor this code" rather than "install and forget." A realistic fork plan: copy the migration + the raw IBGE CSV/seed data into your own database/ folder and delete the dependency. You keep the data, you own the upgrade path.

The data is a 2015 IBGE snapshot. Municipalities created after that date are missing, and any IBGE corrections since then aren't there. For most address forms this is acceptable, but if you need completeness, re-seed from the current IBGE API (servicodados.ibge.gov.br/api/v1/localidades/municipios) instead of relying on the bundled seed.

Seeding is slow and not obviously idempotent. Re-running db:seed --class=... may duplicate rows unless the seeder uses updateOrCreate or truncates first. Check the seeder source; if it does plain inserts, guard with if (Estado::count() === 0) in your own wrapper command.

Migration collisions. Published migrations get the timestamp of the publish moment, which can place them out of order relative to your existing tables. If cidades has a foreign key to estados, ensure the estados migration sorts first — otherwise rename the published files.

Accents and collation. City names like "São Paulo" and "Araçatuba" depend on utf8mb4 and a case/accent-insensitive collation for LIKE searches. If your cidades.nome column uses utf8mb4_bin, searching "sao paulo" won't match "São Paulo" — normalize on write (strip diacritics into a nome_normalizado column) if you need forgiving search.

Don't dump the whole table to the frontend. No Cidade::all() in a Blade loop or a JSON payload. Use per-state endpoints and paginate any administrative screens.

Foreign-key deletion order. On teardown/truncation (tests using RefreshDatabase, migrate:fresh), drop cidades before estados if FKs are enforced; otherwise disable FK checks around bulk seed/truncate operations.

Model conventions may surprise you. Old packages often omit $fillable/$guarded, use nonstandard $table/$primaryKey, or don't use timestamps. Read the two model files — it's five minutes and saves a debugging session.

Extension points to rely on: the published migrations (add your own columns via a follow-up migration), the seeder classes (call them selectively from your own seeder/command), and subclassing the models (add scopes, casts, accessors). Config, if present, is thin — don't expect a pluggable data source.

Have an exit strategy. The value here is the data, not the code. Keep city/state references behind your own App\Models wrappers and a repository/cache layer so that swapping in a maintained dataset — or the live IBGE API — is a contained change rather than a hunt through every form and query.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin