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

Geoip Location Laravel Package

victorybiz/geoip-location

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and its config in one pass, then immediately verify the artisan surface it exposes — the command names are the thing most likely to differ from the README on a 2021-era package:

composer require victorybiz/geoip-location
php artisan vendor:publish --provider="Victorybiz\GeoIPLocation\GeoIPLocationServiceProvider" --tag=config
php artisan list | grep geoip   # confirm the install/update command names

Before writing any application code, check that the package actually resolves on your Laravel version:

composer show victorybiz/geoip-location   # inspect require constraints

The default driver is a local MaxMind GeoLite2 database, so the second step is populating it (the package ships an artisan command that downloads the .mmdb into storage/app). Set the driver and database path in config/geoip.php / .env, and make sure storage/app is writable by the web user.

Where to look first, in order:

  1. config/geoip.php — driver selection, database path, and any API-key settings for the web-service driver.
  2. The registered facade / container binding in GeoIPLocationServiceProvider — this is the object you'll actually call.
  3. The GeoIPLocation class itself — it's small, and the public getters (getCountry, getCity, getCoordinates, getTimezone, getCurrency, getCallingCode, …) are the real API. Read them once rather than guessing from the README.

First use case: don't start with redirects or blocking. Start with personalisation that degrades gracefully — auto-selecting currency, showing a "ships to your country?" banner, or pre-filling a country select on a checkout form. If the lookup returns null (localhost, private IP, missing DB), the page must still work with a sensible default. That one rule prevents most of the production incidents this package causes.

use Victorybiz\GeoIPLocation\Facades\GeoIPLocation;

$country = GeoIPLocation::getCountry(request()->ip()) ?? config('app.default_country');

Implementation Patterns

Resolve once per request, not per call. The MaxMind reader is fast, but it's still file I/O, and it's easy to accidentally call it five times in one controller. Wrap it in a small service and memoise:

class VisitorLocation
{
    private ?array $resolved = null;

    public function get(): array
    {
        return $this->resolved ??= Cache::remember(
            'geoip:'.request()->ip(),
            now()->addDay(),
            fn () => [
                'country' => GeoIPLocation::getCountry(request()->ip()),
                'city'    => GeoIPLocation::getCity(request()->ip()),
                'currency'=> GeoIPLocation::getCurrency(request()->ip()),
                'timezone'=> GeoIPLocation::getTimezone(request()->ip()),
            ]
        );
    }
}

Register it as a scoped singleton so middleware, controllers and Blade all share the same lookup. Cache with a TTL — IPs are reassigned, so an hour-to-a-day window keeps accuracy reasonable without hammering storage.

Middleware that decorates rather than blocks. The most useful pattern is a middleware that attaches location to the request/session and lets the rest of the app opt in:

public function handle($request, Closure $next)
{
    $request->attributes->set('geo', app(VisitorLocation::class)->get());
    return $next($request);
}

Then a SetLocaleFromGeo middleware can run after it and only override the locale if the user hasn't explicitly chosen one. Always give the user an override — a "country/currency" switcher that persists to session — and check that override first. Geo-detection is a default, never a decision.

Multi-currency / tax / shipping. Map the returned ISO country code through your own config table, not through whatever labels the package returns. You want a single config/regions.php that maps country_code => [currency, tax_rate, shipping_zone]. That keeps the vendor's data format out of your domain logic and makes the eventual swap to a different geo library a one-file change.

Store the country, not the IP. For analytics, cohorting or pricing audits, persist country_code (and maybe city) on the user/order/session record. Persisting raw IPs indefinitely is a GDPR/CCPA liability and you rarely need it — store a truncated or hashed IP if you need deduplication.

Testing. Never let tests hit a real lookup. Bind a fake in TestCase::setUp():

$this->app->instance(VisitorLocation::class, new FakeLocation('DE', 'Berlin'));

Reserved ranges (127.0.0.1, 10.x, 192.168.x) will reliably return null from the MaxMind reader, so guard any code path that assumes non-null and test that branch explicitly.

Gotchas and Tips

This package is stale. Last release December 2021, ~21 stars. Check composer show against your Laravel version before committing; you may hit constraint conflicts with Laravel 10/11 or with newer geoip2/geoip2. If you do, the honest move is to skip the wrapper and use geoip2/geoip2 directly — the package is a thin facade over that reader, so the migration cost is a few hours, not a rewrite.

GeoLite2 downloads now require a MaxMind account and license key. MaxMind changed their distribution model; the download URL used by older install/update commands may fail with a 401 unless a license key is configured. If the package's command predates that change, download the .mmdb manually (or via a scheduled curl) and point database_path at the file. Then schedule a monthly refresh in Kernel::schedule() — GeoLite2 ships weekly updates and stale data drifts measurably at city level.

Behind a CDN or load balancer you'll geo-locate the proxy. request()->ip() returns Cloudflare/ALB addresses unless TrustProxies is configured with the right X-Forwarded-For handling. This is the #1 false-negative: everything resolves to a datacentre in Virginia. Verify by logging the IP alongside the result for a day.

config:cache will bite you. If you edit geoip.php or change the database path, run php artisan config:clear && php artisan config:cache in the deploy pipeline — otherwise the old path persists and lookups silently return null.

Accuracy is country-level for most traffic. City, postal code and ISP fields are unreliable in many regions, and VPNs/mobile carriers will place users in the wrong country entirely. Use this for defaults and analytics, never for compliance decisions (tax jurisdiction, age gates, content-restricted regions) without a user-confirmed address.

Handle nulls everywhere. Private IPs, IPv6 quirks, missing database, corrupt downloads, and exhausted free-tier API quotas on the web-service driver all produce null. A single ?? chain into a config default is the difference between a graceful fallback and a 500 on the homepage.

Extension point worth knowing: the driver is configurable, so if you're on shared hosting where the local .mmdb can't be written, switch to the web-service driver and accept the per-request latency and quota — but put it behind the same VisitorLocation cache so you're not burning quota on every page view. If neither fits, the GeoIPLocation class is small enough to subclass or wrap with your own reader implementation, which is the cleanest exit strategy when this package finally stops working.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route