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 Country State City Data Laravel Package

dipeshsukhia/laravel-country-state-city-data

Laravel package that ships ready-to-use country, state, and city data with migrations, Eloquent models, and seeders. Add searchable location dropdowns and relational geography to your app quickly, without manually importing or maintaining large datasets.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and pull in its data layer:

composer require dipeshsukhia/laravel-country-state-city-data

Publish the package assets (config, migrations, and optionally models/seeders). Use the vendor publish tag documented in the README, e.g.:

php artisan vendor:publish --tag=country-state-city

Then migrate and seed the world data:

php artisan migrate
php artisan db:seed --class="DipeshSukhia\\CountryStateCity\\Seeders\\CountryStateCitySeeder"

If the package provides a dedicated command, prefer that—check php artisan list | grep country after install.

Where to look first

  1. config/country-state-city.php — table names, model bindings, and any toggles.
  2. database/migrations/ — table structure, indexes, and foreign-key behavior.
  3. src/Models/ — Country, State, City relationships and fillable fields.
  4. The README’s “Usage” section for the canonical query examples.

First use case

A dependent country → state → city dropdown is the canonical day-one use case:

use DipeshSukhia\CountryStateCity\Models\Country;
use DipeshSukhia\CountryStateCity\Models\State;

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

$states = State::where('country_id', $countryId)
    ->orderBy('name')
    ->pluck('name', 'id');

Use those collections directly in Blade select elements, or return them as JSON for AJAX-driven forms.

Implementation Patterns

Use the Eloquent models directly for reads. The package is primarily a data provider, so most integration is query-level. Eager-load relationships when you need nested data:

$country = Country::with(['states' => fn ($q) => $q->orderBy('name')])
    ->findOrFail($countryId);

Build dependent dropdown endpoints. For address forms, expose lightweight JSON endpoints instead of rendering thousands of options server-side:

Route::get('/states', function (Request $request) {
    return State::where('country_id', $request->integer('country_id'))
        ->orderBy('name')
        ->get(['id', 'name']);
});

Pair this with a small Alpine/vanilla JS handler that clears the city select when the state changes.

Cache reference data aggressively. Country/state/city lists change rarely, so cache them with a versioned key:

$states = Cache::remember("states:country:{$countryId}", now()->addDay(), function () use ($countryId) {
    return State::where('country_id', $countryId)->orderBy('name')->pluck('name', 'id');
});

Clear these keys after any re-seed or data sync.

Store foreign keys, not names. In your own addresses table, keep country_id, state_id, and city_id columns with constrained() foreign keys. This makes filtering and reporting reliable and avoids name-matching bugs (e.g., “Georgia” the country vs. the U.S. state).

Validate against the package tables. Use Laravel’s exists rule with the configured table names:

$request->validate([
    'country_id' => ['required', 'exists:countries,id'],
    'state_id'   => ['required', 'exists:states,id'],
    'city_id'    => ['nullable', 'exists:cities,id'],
]);

For stricter integrity, add a custom rule that verifies the selected state belongs to the selected country and the city belongs to the state.

Seed only what you need in tests. The full dataset is large. In test suites, either use a small fixture set or seed specific countries:

Country::factory()->create(['name' => 'Testland']);
State::factory()->create(['country_id' => $country->id]);

If factories aren’t shipped, insert minimal rows directly in a test seeder.

Integrate with form components. For Blade, Livewire, Filament, or Nova forms, wrap the queries in small services or view composers. Keep the package models behind your own repository if you expect to swap data sources later:

class LocationRepository
{
    public function countries(): Collection
    {
        return Cache::rememberForever('countries', fn () => Country::orderBy('name')->pluck('name', 'id'));
    }
}

Use AJAX search for cities. Never render all cities for a country. Search by name with a limit:

City::where('state_id', $stateId)
    ->when($term, fn ($q) => $q->where('name', 'like', "{$term}%"))
    ->orderBy('name')
    ->limit(50)
    ->get(['id', 'name']);

Gotchas and Tips

  • Seeding is heavy. The full country/state/city dataset can contain hundreds of thousands of rows. Run seeders from the CLI with a raised memory limit (php -d memory_limit=-1 artisan db:seed ...) and expect several minutes. Avoid running the full seeder during every test run.
  • Check the indexes before shipping. Confirm that states.country_id and cities.state_id are indexed. If you publish and modify migrations, add indexes on any columns you filter by (name, foreign keys).
  • Do not call City::all() in production. It will bloat memory and slow responses. Always constrain by state/country and select only id and name.
  • Watch for missing hierarchy data. Some territories have no states, and some cities may be listed directly under a country. Handle null state/city selections gracefully in forms and validation.
  • Table-name conflicts. If your app already has countries, states, or cities tables, change the package table names in config before migrating, or you’ll collide.
  • Foreign-key behavior matters. Inspect the published migrations. If they use cascadeOnDelete, deleting a country will wipe its states and cities. For address records, prefer restrictOnDelete or nullOnDelete in your own tables.
  • Cache invalidation after re-seeding. If you cache country/state/city lists, clear those keys after updating package data. Use a version prefix or Cache::tags if your store supports it.
  • Data freshness. The package ships static data; it will not auto-update when countries or cities change. Plan a periodic re-seed or a sync job if accuracy is critical.
  • AJAX endpoints need validation. Validate country_id/state_id as integers and verify parent-child relationships server-side. Don’t trust the client to send consistent IDs.
  • Debugging slow forms. Enable query logging (DB::enableQueryLog()) or Laravel Debugbar and look for accidental full-table scans. The usual culprit is loading all cities or missing indexes.
  • Extension points. If the package doesn’t bind models in the container, extend its models and set $table to the published table names, or wrap queries in your own repository. Check config/country-state-city.php for overridable model classes before duplicating logic.
  • License and data source. The package is MIT, but the underlying dataset may have its own attribution requirements. Confirm before redistributing the data as part of an API.
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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log