farzai/geonames
Laravel package for working with GeoNames data such as countries, cities, timezones and currencies. Provides a simple, expressive API backed by a local database, so you can query geographic data offline without relying on external services or API limits. Easy to install and integrate.
Requires PHP 8.3+. If you're on 2.x, read UPGRADING.md before touching composer.json — this is a rewrite, not a bump.
composer require farzai/geonames:^3.0
php artisan vendor:publish --tag=geonames-config
php artisan vendor:publish --tag=geonames-migrations
php artisan migrate
Data no longer ships with the package or is fetched at runtime. You explicitly pull the snapshots you need:
php artisan geonames:fetch # interactive / default set
geonames fetch country # or target a specific snapshot from the CLI
Both commands download from https://data.farzai.com/geonames/, verify the pinned sha256, and persist into your configured storage. Confirm the snapshots landed where you expect (check config/geonames.php) before writing queries — the most common "nothing works" report in v3 is a fetch that targeted a different directory than the app reads from.
First use case: seed your app's own lookup tables or validate user input against real geography.
use Farzai\Geonames\Facades\Geonames;
$fr = Geonames::country('FR'); // read-only model
$cities = Geonames::cities('FR'); // collection of read-only models
$paris = Geonames::city('2988507'); // by geoname id
$near = Geonames::nearest(48.8566, 2.3522, limit: 10);
Seeders shipped with the package help you stand up a full dataset for tests or CI:
php artisan db:seed --class="Farzai\\Geonames\\Database\\Seeders\\GeonamesSeeder"
Treat snapshots as build artifacts, not app data. Fetch in a deployment step or a scheduled job, not on every request. Because the runtime is pure-read (no HTTP, no zip), you can safely call Geonames::* from anywhere — controllers, jobs, model accessors — without worrying about outbound latency or third-party outages.
Autocomplete / search. Geonames::search() is the workhorse for user-facing type-ahead. Scope it by country or admin level when you can, and debounce on the client. Combine with cities() when you need structured results rather than a flat list.
Validation rules. Use the bundled rules for address forms and onboarding flows instead of a brittle regex or a hardcoded list of countries:
$request->validate([
'country' => ['required', new CountryRule()],
'admin1' => ['nullable', new Admin1Rule($request->country)],
'postal' => ['nullable', new PostalCodeRule($request->country)],
]);
Check the rule classes shipped in src/Rules/ — they accept scoping arguments so dependent fields stay consistent.
Eloquent casts for normalized storage. The package's casts let you store a geoname id on your own model and hydrate the full read-only record on access. This keeps your schema lean while giving you $user->city->name, ->admin1, ->country without extra joins. The models are intentionally read-only — persist your own foreign keys, not the GeoNames rows themselves.
Proximity features. nearest() is fine for store locators, "events near me", and shipping-zone checks at low volume. For high-cardinality lookups, pre-compute distances into your own table; the package does not (and should not) own your spatial index.
Seeding strategy. For local dev and tests, run the seeder once and commit the resulting SQLite file, or run it in a CI setup step. For production, fetch + seed a single canonical dataset, and refresh on a schedule that matches the upstream snapshot cadence rather than re-downloading per deploy.
Integration with models. Add a country_code / geoname_id column to your domain tables and use the casts. Keep the package's read-only models behind a small application-level service if you want to swap providers later — the public API surface is stable but the underlying source (GeoNames CCN BY 4.0) may occasionally disagree with your business rules.
--feature-class, farzai/transport, symfony/http-client, symfony/console, psr/log, and ext-zip are all gone. If you depended on any of those — especially the MongoDB converter or the old downloader API — you must build an adapter yourself. UPGRADING.md is the only migration guide; there's no compatibility shim.config/geonames.php controls both where fetches write and where queries read. If you move storage (e.g. to storage/app/geonames on a read-only filesystem), update both the config and your deploy step's write permissions. Don't assume storage_path() is writable on Lambda-style containers.max_allowed_packet or parameter limits.save() them; changes will either be ignored or throw. Persist references in your own tables and join on geoname_id or country codes.Geonames::country($code) returns non-null before chaining).How can I help you explore Laravel packages today?