The maxmind_database provider reads a local MaxMind .mmdb file (GeoLite2 or GeoIP2) with a native binary reader built into the package — there is no geoip2/geoip2 or maxmind-db/reader dependency. Lookups happen on your own servers: no network round-trip, no per-request cost, no rate limits, and no visitor IP leaving your infrastructure. Enable it and point it at a database you have downloaded under your own MaxMind licence:
MAXMIND_DB_ENABLED=true
MAXMIND_DB_PATH=/var/data/GeoLite2-City.mmdb # defaults to storage_path('app/geolocation/GeoLite2-City.mmdb')The reader supports 24-, 28- and 32-bit record sizes and IPv4 lookups in IPv6 databases. It maps the record’s English names: city, the first subdivision as region, country ISO code, postal code, coordinates and timezone.
Downloading the database
geolocation:db:update fetches the GeoLite2/GeoIP2 archive from MaxMind and writes the .mmdb to the configured path, unpacking the gzipped tarball natively (ext-zlib + ext-phar) and creating the directory if needed. Configure your license key and edition:
MAXMIND_LICENSE_KEY=your-license-key
MAXMIND_DB_EDITION=GeoLite2-City # GeoLite2-City | GeoLite2-Country | a GeoIP2 editionphp artisan geolocation:db:update
php artisan geolocation:db:update --edition=GeoLite2-Country --path=/var/data/geo.mmdbThe command is a thin wrapper over Geolocation::updateDatabase(), which you can call from code — both arguments default to the config above, and the written path is returned:
use RoundlyConsulting\Geolocation\Facades\Geolocation;
$path = Geolocation::updateDatabase(); // config edition and path
$path = Geolocation::updateDatabase('GeoLite2-Country', '/var/data/country.mmdb');A missing license key or path, a failed download (an HTTP error, a timeout or an unreachable host) or an archive that can’t be unpacked throws DatabaseUpdateException; the command prints the same message and exits non-zero. The license key travels in the download URL, so it is redacted from that message and the transport exception is not chained as previous.
The new file is written beside the old one and then renamed over it, so a lookup running during an update reads either the old database or the new one, never a half-written file. A failed update leaves the current database in place. MaxMind publishes database updates regularly, so schedule the refresh:
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('geolocation:db:update')->weekly();Errors
- An IP that isn’t in the database returns null, so the next provider is tried.
- When the provider is enabled but the file is missing (or no path is set), it throws DatabaseNotFoundException whose message tells you to run php artisan geolocation:db:update.
- A corrupt file throws InvalidDatabaseException.
Long-running processes
The file is read into memory once per process (a GeoLite2-City file is tens of megabytes) and shared by every lookup — no binding needed. When the file on disk changes, for example after geolocation:db:update, the next lookup reloads it, so queue workers and Octane pick up a refreshed database without a restart.
Reading records directly
The same reader is available for raw access to every field in a record:
use RoundlyConsulting\Geolocation\MaxMind\Reader;
$reader = new Reader(storage_path('app/geolocation/GeoLite2-City.mmdb'));
$record = $reader->get('203.0.113.7'); // the decoded record as an array, or null
$reader->metadata()->databaseType; // from the file's own metadata
$reader->metadata()->ipVersion;MaxMind web service
The maxmind_web provider calls MaxMind’s GeoIP2 Precision web service with HTTP Basic auth (account ID and license key). It is disabled by default; enable it and pick the service:
MAXMIND_WEB_ENABLED=true
MAXMIND_ACCOUNT_ID=123456
MAXMIND_LICENSE_KEY=your-license-key
MAXMIND_WEB_SERVICE=city # city | country | insightsIt honours the per-call overrides: withToken('maxmind_web', …) replaces the license key for one call (the account ID stays the configured one), and withTimeout() replaces geolocation.timeout. An unreachable web service is skipped like any other HTTP provider.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.