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 Laravel Package

fof/geoip

FriendsOfFlarum GeoIP brings IP-based geolocation to Flarum, adding country flags to user profiles and posts via MaxMind GeoLite2. It helps communities see where members are from with configurable, lightweight display options.

View on GitHub
Deep Wiki
Context7

Getting Started

fof/geoip is a Flarum extension (not a standalone Laravel package — Flarum runs on Illuminate components, so the service-container patterns will feel familiar) that resolves an IP address to location data. Install it like any Flarum extension:

composer require fof/geoip
php flarum migrate
php flarum cache:clear

Then enable it in Admin → Extensions → GeoIP and pick a driver. The plugin ships with multiple providers (MaxMind GeoLite2, IP-API, and similar) and the admin panel exposes provider-specific settings under the fof-geoip.* settings namespace.

Where to look first:

  • extend.php — shows which routes, settings, and bindings are registered.
  • src/GeoIPServiceProvider.php (or equivalent) — how the GeoIP contract is bound into the container.
  • src/Drivers/ — each provider (MaxMind/IP-API/etc.) implements the same contract. Reading one driver tells you the shape of the return values and error handling.
  • The admin settings component — confirms the exact setting keys you'll read in code.

First use case: resolve a visitor's country inside a controller, listener, or middleware:

use FoF\GeoIP\GeoIP;

$country = app(GeoIP::class)->getCountry($request->ip()); // e.g. "DE" or null

That single call is the 90% entry point — everything else in the extension exists to make that call work reliably (driver selection, database updates, caching).

Implementation Patterns

Inject the contract, never a concrete driver. Bind through the GeoIP interface so switching providers in admin doesn't break your code:

public function __construct(protected GeoIP $geoip) {}

Wrap lookups in a caching decorator. Every driver either does a network call (IP-API) or a database hit (MaxMind). A per-IP cache with a sane TTL is the single biggest performance win:

$country = Cache::remember("geoip:{$ip}", now()->addDays(30), fn () =>
    $this->geoip->getCountry($ip)
);

Geolocation is effectively static for a given IP, so 30–90 days is reasonable.

Enrich asynchronously when it isn't on the request path. If you're tagging users at registration or attaching country to posts, don't block the response — dispatch a queued job:

class ResolveUserCountry implements ShouldQueue
{
    public function handle(GeoIP $geoip): void
    {
        if ($country = $geoip->getCountry($this->user->last_ip)) {
            $this->user->save(['geoip_country' => $country]);
        }
    }
}

Custom drivers. If your providers aren't covered, implement the contract and register it — either by binding your class in extend.php or by extending the driver registry the extension exposes. This is the intended extension point: keep lookup logic in the driver and let the contract handle the rest.

Pair it with the ecosystem. The most common real-world use isn't calling GeoIP directly but letting it feed other FriendsOfFlarum extensions — spam blocking by country, IP-ban tooling, registration restrictions. In those cases you rarely touch the API at all; you configure the settings and let the consuming extension call the binding.

Keep a "test" driver. For feature tests, bind a fake implementation of the contract in your test case so nothing hits the network:

$this->app->instance(GeoIP::class, new FakeGeoIP());

Gotchas and Tips

  • Private and reserved IPs return nothing. 127.0.0.1, 10.0.0.0/8, 192.168.x.x, and IPv6 loopback will yield null (or worse, throw depending on the driver). Always null-check and have a fallback path.
  • Proxies and Cloudflare eat the real IP. If the forum sits behind a load balancer or Cloudflare, $request->ip() returns the proxy. Configure trusted proxies at the framework level or read CF-Connecting-IP, otherwise every "visitor country" is your CDN's datacenter.
  • MaxMind needs a license key. Since 2019, GeoLite2 requires a (free) MaxMind account and a license key to download the database. The extension can fetch/update it, but you must supply the key; there's typically a scheduled task or command for refresh — wire it into your cron or the data goes stale.
  • IP-API has aggressive rate limits (~45 req/min on the free tier). Without caching you will hit them, and the driver will silently return null. Treat external-API drivers as best-effort.
  • Never let geolocation block a request. Wrap the call in try { } catch and fail open. A third-party API outage should never 500 a login.
  • Settings live in Flarum's settings repository, not config files. Read via the settings repository / admin panel, and remember the keys are namespaced (fof-geoip.*) — grep the driver for Settings::get to find the exact names.
  • IP addresses are PII under GDPR. Don't log raw IPs at debug level, and consider hashing if you persist them long-term for geolocation only.
  • Maturity caveat: this is a small, low-star community extension. Pin the version in composer.json, review the driver code for your chosen provider, and expect to read source rather than docs. The contract is stable enough to abstract behind your own service if you later swap providers.
  • Driver quirks vary in return shape. Some return country codes, some return country names or full location arrays. Normalize into your own value object before passing data across your application boundary.
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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr