NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Use the Geolocation facade — every method is also available on an injected GeolocationManager. The one-liner helpers cover the common cases:

use RoundlyConsulting\Geolocation\DataTransferObjects\Coordinates;
use RoundlyConsulting\Geolocation\Facades\Geolocation;

$location = Geolocation::locateIp('8.8.8.8');
$location = Geolocation::locateRequest();                              // auto-detects the client IP

$location?->city;           // "Mountain View"
$location?->region;         // "California"
$location?->postalCode;     // "94043"
$location?->countryIsoCode; // "US"
$location?->timezone;       // "America/Los_Angeles" (IP providers only)
$location?->latitude;       // 37.4056

$location = Geolocation::locateAddress('1600 Amphitheatre Pkwy, Mountain View');

$location?->city;           // "Mountain View"
$location?->timezone;       // "" (Google geocoding returns no timezone)
$location?->latitude;       // 37.4224

$location = Geolocation::locateCoordinates(new Coordinates(48.1486, 17.1077));   // reverse geocoding

locateRequest() reads the IP from the current request (or the one you pass) and returns null when there is none. Behind a load balancer, configure Laravel’s trusted proxies so the request IP is the visitor’s, not the proxy’s.

Every helper returns null when no provider answers. A Location you do get back always places something — an empty answer ($location->isEmpty(): no address part, no country, 0,0 coordinates) is treated as a miss — though a coarse IP match can still lack a city or a country.

Named queries

Every helper builds a GeolocationQuery and calls locate(). Build the query yourself when you already have one to pass around:

use RoundlyConsulting\Geolocation\DataTransferObjects\Coordinates;
use RoundlyConsulting\Geolocation\DataTransferObjects\GeolocationQuery;
use RoundlyConsulting\Geolocation\Facades\Geolocation;

Geolocation::locate(GeolocationQuery::forIp('203.0.113.7'));
Geolocation::locate(GeolocationQuery::forAddress('Main Street 1, Springfield'));
Geolocation::locate(GeolocationQuery::forCoordinates(new Coordinates(48.1486, 17.1077)));

Dependency injection

Every helper works the same on an injected GeolocationManager — type-hint it wherever Laravel resolves dependencies (see DI and actions):

use Illuminate\Http\Request;
use RoundlyConsulting\Geolocation\GeolocationManager;

final class CheckoutController
{
    public function show(Request $request, GeolocationManager $geolocation)
    {
        $country = $geolocation->locateRequest($request)?->countryIsoCode;

        // ...
    }
}

The Location object

PropertyTypeContents
humanReadablestringDisplay label, e.g. a formatted address or “City, Region Country”.
streetstringStreet — filled by Google only; empty for IP providers.
citystringCity name.
regionstringRegion / state; defaults to ''.
postalCodestringPostal code; defaults to ''.
countryIsoCodestringISO country code, e.g. US.
latitudefloatLatitude.
longitudefloatLongitude.
timezonestringIANA timezone, e.g. America/Los_Angeles — filled by the IP providers only; defaults to ''.
typeGeolocationTypeIp, Geolocation (Google) or Default (the fallback).

Location and Distance implement Arrayable and JsonSerializable, so you can return them straight from a controller:

return response()->json($location);   // Location is Arrayable + JsonSerializable

$location->toArray();
// ['humanReadable' => ..., 'street' => ..., 'city' => ..., 'region' => ..., 'postalCode' => ...,
//  'countryIsoCode' => ..., 'latitude' => ..., 'longitude' => ..., 'timezone' => ..., 'type' => 'IP']

$location->coordinates();   // a Coordinates value object

Batch lookups

Resolve many IPs at once. The result is keyed by IP, and failures are isolated per item — any exception on one IP becomes null for that IP and the batch carries on. A scope or override chained before batch() applies to every IP:

$results = Geolocation::batch(['8.8.8.8', '1.1.1.1', '203.0.113.7']);

$results['8.8.8.8']?->city;   // a Location or null per IP

foreach ($results as $ip => $location) {
    // $location is null when that one lookup failed — the batch never aborts
}

// A scope or override chained before batch() applies to every IP
Geolocation::using('maxmind_database', 'ipinfo')->batch($ips);

Each item runs through the normal pipeline, cache and events. HTTP providers pace a batch automatically under their rate-limit budget.

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 crypto

By 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.