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 World Laravel Package

altwaireb/laravel-world

Laravel World provides a complete, ready-to-use database of countries, states, cities, currencies, languages, timezones, and more. It ships with migrations, seeders, models, caching, and multilingual support, so you can add geo data to any Laravel app in minutes.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal path from install to first useful query:

composer require altwaireb/laravel-world

# Publish config, migrations, seeders (and optionally lang files)
php artisan vendor:publish --tag=world

# Create tables
php artisan migrate

# Load the dataset (large — see Gotchas)
php artisan world:seed
# If your version has no world:seed command, fall back to:
# php artisan db:seed --class=WorldSeeder

Pin the right major for your framework. v2.0.0 is the first release with Laravel 13 support (CI starts at 13.38.0) and still supports Laravel 10, 11 and 12. On Laravel 13 you need ^2.0; on 10–12 either major works, but prefer v2 for the streaming seeder:

composer require altwaireb/laravel-world:^2.0
composer show altwaireb/laravel-world   # confirm the major you actually got

Check what your installed version actually exposes before scripting anything:

php artisan list | grep -i world

Where to look first (in this order):

  1. config/world.php — the single most important file. It controls caching, route/API registration, table naming, custom fields, and which resources get seeded.
  2. Altwaireb\World\Models\Country, State, City — read the $fillable, $casts, relationships, and scopes. This tells you the real column names (iso2, iso3, phone_code, currency, state_code, country_code, etc.) faster than any README.
  3. The seeder/commands in src/Commands and database/seeders — confirms seed order, whether re-runs are idempotent, and (v2+) that the JSON source is read via a stream rather than json_decode(file_get_contents(...)).

First use case — an address form or a country lookup:

use Altwaireb\World\Models\Country;

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

// Resolve by ISO code for phone/currency defaults
$country = Country::where('iso2', 'US')->firstOrFail();
$currency = $country->currency;

// Cascading selects
$states = $country->states()->orderBy('name')->get(['id', 'name']);
$cities = \Altwaireb\World\Models\City::where('state_id', $stateId)
    ->orderBy('name')
    ->get(['id', 'name']);

Implementation Patterns

Store foreign keys, not strings. The highest-value pattern is to keep country_id, state_id, city_id on your addresses/users tables and let the package own the reference data. Add the FKs with nullOnDelete or no cascade — you do not want a reseed wiping user rows.

Schema::table('addresses', function (Blueprint $table) {
    $table->foreignId('country_id')->nullable()->constrained('countries');
    $table->foreignId('state_id')->nullable()->constrained('states');
    $table->foreignId('city_id')->nullable()->constrained('cities');
    $table->string('postal_code')->nullable();
});

Cascading form endpoint. Keep the controller thin and return only what the UI needs:

public function states(Country $country)
{
    return $country->states()->orderBy('name')->get(['id', 'name', 'state_code']);
}

public function cities(State $state)
{
    return $state->cities()->orderBy('name')->get(['id', 'name']);
}

Bind by iso2 in URLs if you want readable routes (/geo/US/states), but keep numeric IDs in storage.

Validation via Rule::exists. Never trust a client-submitted state_id independent of country_id; validate the pairing:

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

Cache the dropdowns, not the world. Countries and states are small and stable; cities are the expensive part. Cache per-country or per-state slices rather than one giant array:

$states = Cache::remember("world.states.{$countryId}", now()->addDay(), fn () =>
    State::where('country_id', $countryId)->orderBy('name')->get(['id', 'name'])->toArray()
);

If the package's built-in cache is enabled in config/world.php, verify its store and TTL and decide whether you want two cache layers. For most apps, disabling the package cache and caching at the query level you control is easier to reason about.

Custom fields. If you need population, flag, is_active, etc., add them through the config's custom-field mechanism (and publish/re-run the migration) rather than editing vendor migrations. Re-seeding then populates them. Re-publish with --force only on a fresh DB — otherwise you will silently overwrite your own edits to published files.

Don't fight the stream in v2. The seeder now reads the JSON incrementally (halaxa/json-machine), so any local override that assumed a fully-hydrated in-memory dataset — collecting all rows, sorting them in PHP, cross-referencing countries against cities before insert — is the wrong shape now. Do lookups and ordering in SQL/Eloquent after seeding, or keep your own importer separate from the package seeder.

Tests. Don't seed the full dataset in CI. Seed a trimmed subset if the config/seeder supports it, or insert three countries and a handful of states/cities in a factory. Use RefreshDatabase and assert the relationship chain, not the row counts. Lower seeder memory (v2) makes full seeding less dangerous, but it does not make it fast — keep CI trimmed.

API routes. If config/world.php has API registration, enable it behind auth/throttle middleware and confirm pagination is on. Exposing a 150k-row cities index unauthenticated and unpaginated is a foot-gun. For most apps, writing two or three of your own controllers is cheaper than configuring the bundled routes.

Upgrades. v2.0.0 is a major bump, though the release notes describe only additive changes (streaming reader, Laravel 13 support). Still treat the dataset as mutable reference data, not immutable constants: re-publish config/migrations, diff them, re-run the seeder on a staging copy, and check that the IDs your users reference still exist. Keeping iso2/state_code alongside the FK makes later reconciliation possible. If you are moving 1.x → 2.x, pin, seed a staging copy, and compare row counts plus a few spot-checked iso2/state_code pairs before touching production. On Laravel 13, verify your other packages support it — the world package shipping 13 support does not mean your auth or admin panel does.

Gotchas and Tips

  • The seed is big and slow — but no longer memory-hungry. v2 streams the JSON source, so peak PHP memory during import drops sharply versus v1's full-file decode. Wall-clock time and final DB size are unchanged: expect minutes and a large database. Keep running it via CLI outside a web request and never inside a deployment step with a request timeout. If you still OOM on v2, check composer show (you may be on 1.x) and remember Eloquent model hydration during inserts can dominate memory even with a streamed reader.
  • Streaming changes failure semantics. A streamed read means rows are committed incrementally; if the process dies mid-run you get a partially seeded country, not an empty table. Always plan the recovery path (truncate + re-run) up front. Same as before: countries → states → cities, truncate in reverse with FK checks disabled.
  • A new transitive dependency. v2 pulls in halaxa/json-machine. If your org runs license/security scanning or vendors a fixed dependency allowlist, whitelist it before upgrading; it is a pure-PHP reader, so no new extension requirements beyond ext-json.
  • Seeding order matters. Countries → states → cities. If you truncate, truncate in reverse and disable FK checks, or you will hit constraint errors mid-seed.
  • Re-runs can duplicate. Check whether the seeder uses insert or updateOrCreate. On a non-fresh DB, prefer truncating the three tables first (after confirming nothing references them) or running against a clean schema.
  • config:cache bites env-driven config. Values read from env() inside config/world.php are frozen after php artisan config:cache. Change the env var, then re-cache, or the seeder/cache settings appear to be ignored.
  • Table name collisions. If you already have countries/states/cities tables, publishing will collide. Check the config for table-name overrides, or rename yours before migrating.
  • Don't eager-load the world. Country::with('states.cities')->get() will attempt to hydrate every city. Always scope to one country/state, select explicit columns, and paginate anything city-level.
  • City names are not unique. Never key on name. Always join/store IDs, and expect duplicate city names across states and countries.
  • Locales are limited. Default names are generally English; native is often a better display value for non-English audiences. Don't assume translations exist for every row — fall back explicitly.
  • Vendor publishing is one-way-ish. --force overwrites your published copies. If you customize migrations or config, note it in the repo so the next developer doesn't blindly re-publish. Re-check published config after a major bump — new keys (cache/streaming related) may have appeared and silently default to off.
  • Debugging lookups. When a state/city "isn't there," check country_id/state_id first, then confirm the seeder actually ran and completed for that country. With v2's incremental writes, a killed seed is the likeliest cause — not missing source data.
  • Cache invalidation. If you cache dropdowns, key them by the parent ID (world.states.{countryId}) so a reseed of one country doesn't require flushing everything. Tag-based flushing only works with Redis/Memcached, not the file or database store.
  • Extension points. Subclass the package models in your app (class Country extends \Altwaireb\World\Models\Country) and point your relationships at the subclass if you need extra behavior, fillable fields, or scopes — that keeps composer update from stomping your logic. If you fork the seeder to add custom fields, prefer a separate command you own over editing the vendor one.
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