lwwcas/laravel-countries
Eloquent-first country database for Laravel. Every ISO 3166 Alpha-2, Alpha-3 and numeric code, international phone code, currency, SVG and emoji flag, GeoJSON border and translation is a native Eloquent relationship—one Country:: call away, with no external API needed.
Install and seed in one shot:
composer require lwwcas/laravel-countries
php artisan w-countries:install
Before running the installer, decide your language set — it's the one irreversible-ish choice you make on day one. Seeding all nine languages when the app only serves English and Portuguese just makes every translation join heavier:
php artisan w-countries:install --languages=en,pt
Where to look first, in this order:
Lwwcas\LaravelCountries\Models\Country — the model is the API. Read its scopes and accessors before writing any query.iso, iso_alpha3, iso_numeric are indexed in your DB before you build FKs on them.config/ after publishing — translation/table settings and any model binding hooks.First real use case: a countries dropdown on a registration or address form that persists a country_id, not a raw two-letter string. Do the lookup by ISO only on the boundary (Country::whereIso('BR')->firstOrFail()->id); store the FK, join later.
Production/CI:
php artisan w-countries:install --force
# or, when migrations already ran during deploy:
php artisan w-countries:seed --languages=en,pt
Run w-countries:seed --show-commands once to see the underlying db:seed invocations — you'll want those in your deploy script rather than shelling out to an interactive command.
Foreign keys over string codes. The package is valuable precisely because countries is a real table you can relate to. Add country_id to users, addresses, orders, pricing_tiers, and relate normally:
Schema::table('addresses', fn (Blueprint $t) =>
$t->foreignId('country_id')->constrained('countries'));
class Address extends Model {
public function country(): BelongsTo { return $this->belongsTo(Country::class); }
}
Keep a country_code string column only if you need historical audit data that must survive a reseed.
Dropdowns and list endpoints. Pull only what you render, and order by the translated name, not the raw column:
Country::query()
->with('translation') // avoid N+1 on names
->get()
->map(fn ($c) => ['id' => $c->id, 'name' => $c->name, 'iso' => $c->iso]);
If your list is hot, cache it keyed on locale + language set:
Cache::rememberForever("countries.options.{$locale}", fn () => ...);
Validation rules. Phone number validation is the highest-value integration: prefix checking against wherePhoneCode('+55') catches obvious mismatches before you reach a real phone library. Remember dialing codes are shared (+1 covers US, CA, and more) — use it as a soft check, not an identity check.
Currency and region segmentation. whereCurrency, GDP, and region relationships are good for feature gating, tax/price tables, and analytics grouping. Seed a regions lookup and relate your own models to it if you segment by continent.
Flags in Blade. flag_emoji for lists and toggles, flag_svg only where you need crisp rendering. Don't loop flag_svg over 250 rows — that's megabytes of markup in the response.
GeoJSON. Treat geojson as a server-side column. Use it for generating map tiles, choropleths, or spatial joins; ship latitude/longitude to the frontend and fetch borders per-country on demand.
Localization. Wrap country lookups in a small service that resolves the current locale against your installed language set and falls back explicitly. This is the seam where you'd swap the package later.
release/4.13.1.md before upgrading — migration path casing and dependency bumps have broken things before.country_id, verify the package migration ran first in a fresh migrate:fresh — otherwise FK creation fails intermittently depending on discovery order.--force skips prompts; it does not make an unguarded db:seed safe. Never run the package seeder implicitly in a db:seed chain in production without the language flag, or you'll reintroduce languages you deliberately dropped.w-countries:seed --languages=... run. Removing a language deletes translation rows — audit anything that referenced translated names before dropping.SELECT * is expensive here. GeoJSON, SVG flags, and translation joins make toArray() on a full collection surprisingly large. Scope columns for lists and API resources.select() your way out of accessors. Translatable/accessor-backed attributes (name, flag_colors) may depend on columns or relations you excluded. Verify before trimming.brasil vs brazil). Write these assumptions into tests, not comments.w-countries:seed. A versioned cache key (countries.v{schema}.{locale}) is cheaper than remembering.phpunit.xml, seed once in a base TestCase where the DB survives, or seed only the handful of countries a test needs via direct model creation.countries.id rather than widening theirs.AN), duplicate fixes (CY), and added entries (PH). Pin, watch releases, and plan reseeds — a "static" seed is only static until the next tag.How can I help you explore Laravel packages today?