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

Countries Laravel Package

mledoze/countries

PHP library of world country data: names, ISO codes, capitals, regions, currencies, languages, borders, area, population, and translations. Ships as JSON, CSV, XML, YAML, and PHP arrays for easy import.

Deep Wiki
Context7

Getting Started

mledoze/countries is a data package, not a Laravel package. It ships country records as JSON/YAML/CSV/XML; there is no service provider, model, or validation rule. Your first job is to load vendor/mledoze/countries/dist/countries.json and decide whether to read it at runtime or seed it into a table.

Install and confirm where the data landed:

composer require mledoze/countries
ls vendor/mledoze/countries/dist
# countries.json, countries.yaml, countries.csv, countries.xml

Inspect one record before writing any schema — every field you rely on should be verified against a real row:

$countries = json_decode(
    file_get_contents(base_path('vendor/mledoze/countries/dist/countries.json')),
    true,
    flags: JSON_THROW_ON_ERROR
);

dump(count($countries));       // ~250 entries
dump(array_keys($countries[0])); // cca2, cca3, name, currencies, idd, ...

A good first use case is a country select on an address form: cca2 as the option value, name.common as the label, and flag/currencies/idd as adjacent form defaults. Build that end-to-end before seeding a database; it forces you to confront translation fallbacks, calling codes, and territories early.

Implementation Patterns

Wrap loading in a cached singleton. Never decode the ~2.5 MB JSON on each request.

final class CountryRepository
{
    public function all(): array
    {
        return Cache::rememberForever('countries.data', function () {
            return json_decode(
                file_get_contents(base_path('vendor/mledoze/countries/dist/countries.json')),
                true, 512, JSON_THROW_ON_ERROR
            );
        });
    }

    public function find(string $cca2): ?array
    {
        return collect($this->all())->firstWhere('cca2', strtoupper($cca2));
    }
}

Bind it as a singleton in a service provider and key a keyBy('cca2') index inside it to avoid repeated firstWhere scans in loops.

Seed to Eloquent when you need joins, filtering, or admin editing. Create a countries table with cca2 (unique), cca3 (unique), name_common, name_official, region, subregion, currency_code, calling_code, flag_emoji, latitude, longitude, un_member, and a raw JSON column for the untouched record. Use upsert on cca2 so reruns are idempotent:

Country::upsert($rows, ['cca2'], ['name_common', 'region', 'raw']);

Keep the raw column — you will want fields later without re-migrating.

Validation rule. Wrap the data set in a custom rule rather than Rule::in(...) with a hardcoded array:

final class CountryCode implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! app(CountryRepository::class)->find((string) $value)) {
            $fail("The {$attribute} is not a valid country code.");
        }
    }
}

Use cca2 for forms (ISO 3166-1 alpha-2, matches most payment/shipping APIs); store cca3 alongside it for internal joins and analytics.

Localization. Use translations[$locale] with a fallback to eng:

$label = $country['translations'][app()->getLocale()]['common']
    ?? $country['translations']['eng']['common']
    ?? $country['name']['common'];

Note the keys are ISO 639-3 (fra, deu, zho), not Laravel's usual fr/de/zh — maintain an explicit map.

Domain helpers. Derive defaults from the record: idd.root . $idd.suffixes[0] for phone fields (with caveats below), array_key_first($currencies) for billing currency, $timezones[0] for user timezone, region/subregion for shipping zones and EU/EEA checks. For forms that only need a list, expose a small API endpoint (GET /api/countries?region=Europe) backed by the cached repository so the frontend doesn't ship the full JSON.

Flags. flags.svg/flags.png are remote URLs; flag is an emoji (regional indicator pair). For production UI, self-host SVGs or use the emoji and accept OS font differences. Don't hotlink in high-traffic pages.

Gotchas and Tips

  • License is ODbL-1.0, not MIT. Attribution is required, and derivative databases carry share-alike obligations. If you redistribute a seeded/enriched country table, review ODbL with legal; keep the attribution in an about page or LICENSE file.
  • No autoloading, no facade. The composer package is essentially a data drop. Everything — loader, cache, validation, seeding — is your code.
  • Territories and non-UN entries are in the list. unMember is false for many entries, independent can be null, and Kosovo uses cca2: "XK" with status: "user-assigned". Filter explicitly if your product only serves sovereign states.
  • Calling codes are not a single string. idd.root + idd.suffixes can contain multiple suffixes (NANP territories share +1), and idd may be empty. Don't blindly concatenate; build a small resolver and test against US, GB, PR, and AQ.
  • Currency is an object, not a code. currencies is { "USD": { "name": "...", "symbol": "$" } }, can contain multiple currencies, and is empty for Antarctica. Treat "no currency" as valid.
  • capital can be [] and capitalInfo.latlng may be missing; borders is [] for islands. Guard before indexing.
  • Region/subregion are display strings ("Europe", "Northern Europe"), not codes. Normalize them yourself if you need stable identifiers.
  • Use name.common for UI, name.official for legal documents. Some official names are long and include "Republic of…"; don't use them in dropdowns.
  • UTF-8 everywhere. Country names and demonyms contain accents and non-Latin scripts; ensure utf8mb4 columns and JSON_UNESCAPED_UNICODE if writing files.
  • CSV loses structure. Nested fields (languages, currencies, translations) are serialized strings in the CSV; the JSON is the right source for PHP. YAML via Illuminate\Support\Facades\File + Symfony\Component\Yaml\Yaml::parseFile() is fine but slower than JSON.
  • Pin the version and test the data. Releases (latest 2025-02-26) change names, borders, and membership. Add a test asserting the entry count and spot-checking a handful of cca2 rows so an update fails loudly instead of silently changing dropdowns.
  • Invalidate the cache on deploy. Cache::forget('countries.data') in a deploy hook, otherwise a package bump won't reach production.
  • Extension points: keep the raw record in your DB so new fields (startOfWeek, car.side, postalCode.regex) are available without re-importing; add a config array listing which fields your API exposes so you can trim payloads later.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo