maggomann/addressable
Maggomann Addressable is a Laravel package that adds polymorphic address support to your Eloquent models, letting any model store one or more addresses through a simple trait and relation.
composer require maggomann/addressable
php artisan vendor:publish --tag="addressable-config"
php artisan vendor:publish --tag="addressable-migrations"
php artisan migrate
Then load the reference data with the package's install/seed command (run php artisan list | grep addressable to see the exact name in your version — it ships an install-style command that seeds countries, subdivisions, and cities). Seeding is the slow part; do it once locally and snapshot the DB for teammates rather than re-seeding on every migrate:fresh.
Look first at:
config/addressable.php — table names, model bindings, locale/translation settings.Maggomann\Addressable\Models\{Country,Subdivision,City} — relations and scopes.iso_2, iso_3, name) and FK shape, before you write your own addresses table against them.First use case: a cascading country → subdivision → city select. Country list is small; subdivisions and cities should be fetched per-parent over AJAX, not embedded in the initial page.
$countries = Country::query()->orderBy('name')->get(['id', 'iso_2', 'name']);
Store codes, not IDs, at the edges. Persist iso_2 on your own addresses table (plus a nullable subdivision/city FK). ISO codes survive a re-seed; auto-increment IDs do not. Keep the FK only where you need referential integrity and accept that a re-seed may require re-linking.
Cascading selects. One endpoint per level, keyed by parent code:
Route::get('/geo/subdivisions/{country:iso_2}', fn (Country $country) =>
$country->subdivisions()->orderBy('name')->get(['id', 'name'])
);
Use route-model binding on iso_2 so the controller stays thin, and return only the columns the <select> needs.
Validation. Validate against the package tables directly rather than duplicating lists:
'country' => ['required', Rule::exists('addressable_countries', 'iso_2')],
'subdivision' => ['nullable', Rule::exists('addressable_subdivisions', 'id')
->where('country_id', $countryId)],
The where clause matters — a subdivision ID from the wrong country is a real bug class in address forms.
Cache the country list. It changes only when you re-seed:
$countries = Cache::rememberForever('geo.countries', fn () =>
Country::orderBy('name')->get(['iso_2', 'name'])
);
Bust the key in your seeder/import command, not on a timer.
Translations. If you expose localized names, eager-load the translation relation and always fall back to the base name; translated rows can be missing for a locale. Don't call a translation accessor inside a loop over 250 countries without with().
Your own address model. Extend or reference the package models but keep your Address model separate — it will grow fields (line1, postcode, recipient) the package should not own. Add a small HasAddress/Addressable trait on your side for formatting.
Testing. Seed reference data once per test suite, not per test. A base TestCase that migrates + seeds and wraps each test in a transaction is far faster than RefreshDatabase plus a full city import. For unit tests that only need a country, insert the handful of rows you need by hand.
"maggomann/addressable": "0.2.2") and diff the published migrations on every bump — 0.x minors can rename columns without a deprecation cycle.SET FOREIGN_KEY_CHECKS=0 on MySQL). SQLite will crawl or hit variable limits; use MySQL/Postgres for the initial import and copy the resulting DB.addresses FK migration. If you publish them after your own migrations were timestamped, rename or re-order, otherwise migrate:fresh fails on a clean machine.config/addressable.php. Use (new Country)->getTable() in raw queries, or you will break for anyone who changes the prefix.Country::with('subdivisions.cities') across a full country list is enormous. Paginate or fetch per-parent.name.migrate:fresh --seed becomes unusable once the geo seed is in DatabaseSeeder. Keep reference-data seeding in a separate command so app seeders stay fast.How can I help you explore Laravel packages today?