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

Laravel Locations Laravel Package

milenmk/laravel-locations

Laravel package for adding countries, states/regions, and cities to your app. Provides migrations, Eloquent models, seeders, and lookup helpers for building address forms, location selects, and searchable location data.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets through Composer, then expose the config, migrations, and seeders:

composer require milenmk/laravel-locations
php artisan vendor:publish --provider="Milenmk\LaravelLocations\LaravelLocationsServiceProvider"
php artisan migrate

The published migrations create tables for countries, cities, areas, languages, and currencies. Before running migrations in an existing app, check the database/migrations folder (or the package's migrations/ directory) to see the exact table names and confirm they don't collide with anything you already have.

Next, seed the reference data. This is the step people forget — the tables are empty until you run the seeders, so you'll see empty dropdowns otherwise:

php artisan db:seed --class="Milenmk\LaravelLocations\Database\Seeders\LocationSeeder"

Look first at the package's src/Models directory (or the published models) to understand the relationships — Country typically has many City and Area records, and belongs to Currency/Language through codes. Read the migration files before seeding: the cities table can contain tens of thousands of rows, so you'll want to know what you're loading into your dev DB.

Your first real use case is almost always a country dropdown on a signup or address form. Once seeded, it's simply:

use Milenmk\LaravelLocations\Models\Country;

$countries = Country::orderBy('name')->pluck('name', 'id');

Return that to your view/Inertia props and you have a working selector in minutes.

Implementation Patterns

Cascading selects (country → city/area). The common pattern is an endpoint that returns cities filtered by country, then areas filtered by city:

Route::get('/countries/{country}/cities', function (Country $country) {
    return $country->cities()->orderBy('name')->get(['id', 'name']);
});

Trigger it from a change handler on the country select. If you're on Livewire, bind $countryId as a property and use updatedCountryId() to refetch cities — the reactive model maps naturally onto this data.

Eager-load to survive the N+1 trap. Any listing that shows a country alongside a city (e.g. a user directory) will explode in queries if you don't eager-load:

User::with('city.country', 'country.currency')->paginate();

Store codes, not names. Persist country_code (ISO alpha-2), currency_code, and language_code on your own tables rather than the package's numeric IDs. This keeps your data portable if you swap packages later and makes joins to the package tables by code trivial. It also avoids breaking if the package re-seeds and IDs shift.

Validate against the package tables. Rather than a long hardcoded in: list, use the exists rule:

'country_id' => ['required', 'exists:countries,id'],
'city_id'    => ['required', 'exists:cities,id'],

Cache the static lists. Countries, languages, and currencies barely change — cache them for the life of a deploy:

$countries = Cache::rememberForever('countries', fn () =>
    Country::orderBy('name')->get(['id', 'name', 'code'])
);

Invalidate the cache in a deploy hook or after re-seeding. Cities/areas are too large to cache wholesale; cache per-country instead if you need to.

Use it as a lookup, not a source of truth for business logic. Treat the package's tables as read-mostly reference data. If you need to store the user's chosen country with extra metadata (e.g. their billing region), keep your own user_addresses table and FK into countries rather than adding columns to the package's tables.

Gotchas and Tips

  • Seeding is slow and large. The cities (and possibly areas) seeders can run for minutes and add significant row counts. In CI, seed a single country for tests rather than the full dataset, or use RefreshDatabase with a trimmed seeder.
  • Don't re-run seeders blindly. Most package seeders insert rather than upsert, so re-seeding can create duplicates or throw unique-constraint errors. Guard with Country::count() === 0 in your own deploy script, or wrap the seeder call in a "run once" conditional.
  • Foreign-key order matters. If cities references countries, running them out of order fails. Use the package's aggregate seeder or run countries → currencies/languages → cities → areas explicitly.
  • Migration collisions. If you already have a countries table (common in older apps), the package migration will fail. Rename your table, or skip the package migration and map the models to your existing schema.
  • The "area" concept is ambiguous. Areas are sub-city subdivisions (neighborhoods/districts) and coverage varies wildly by country — some countries have almost none populated. Don't build a UI that requires an area selection; make it optional and gracefully handle empties.
  • Country code casing. ISO codes are typically stored lowercase alpha-2 in these packages (e.g. us, gb). Normalize with strtoupper()/strtolower() on both sides when joining to your own data or to third-party APIs.
  • Translation/locale. If the package ships localized names (many do via a name column plus translations), verify how locale is selected. If translations aren't included, override display names in your own translation files keyed by country code instead of patching the package.
  • Testing. The package often ships without factories. Use Country::factory() only if the package provides one; otherwise seed a fixture row manually in setUp() or create a tiny helper trait. Avoid depending on specific seeded IDs in assertions.
  • Deploying data updates. Since data lives in migrations/seeders, a "new country added upstream" arrives as a package update, not an auto-sync. Pin the version in composer.json and consciously re-run seeders after upgrades — a naive composer update + deploy won't refresh rows unless you re-seed.
  • Extension points. Rather than editing published models (you'll lose changes on update), extend them (class Country extends \Milenmk\LaravelLocations\Models\Country) and add scopes/accessors there; bind your subclass in a service provider if the package resolves models by class name.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource