---
title: "How it works — Geolocation for Laravel | Roundly"
description: "An ordered pipeline of named providers — the first real answer wins, unreachable APIs are skipped and an optional static default is the last resort."
url: https://roundly-consulting.com/open-source/docs/geolocation-for-laravel/how-it-works
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Geolocation for Laravel](https://roundly-consulting.com/open-source/docs/geolocation-for-laravel.md)

# How it works

Every lookup walks an ordered pipeline of named providers and returns the first non-null answer. A GeolocationProvider turns a GeolocationQuery (IP, coordinates or address) into a Location; a DistanceProvider turns a DistanceQuery (two coordinate pairs plus a travel mode) into a Distance. Providers that don’t implement the contract a call needs are skipped:

```php
use RoundlyConsulting\Geolocation\DataTransferObjects\Distance;
use RoundlyConsulting\Geolocation\DataTransferObjects\DistanceQuery;
use RoundlyConsulting\Geolocation\DataTransferObjects\GeolocationQuery;
use RoundlyConsulting\Geolocation\DataTransferObjects\Location;

// RoundlyConsulting\Geolocation\GeolocationProvider
public function locate(GeolocationQuery $query): ?Location;

// RoundlyConsulting\Geolocation\DistanceProvider
public function distance(DistanceQuery $query): ?Distance;
```

## Bundled providers

| Name | Class | Location | Distance | Source |
| --- | --- | --- | --- | --- |
| `maxmind_database` | `MaxMindDatabaseProvider` | by IP | no | Local .mmdb file (native reader) |
| `maxmind_web` | `MaxMindWebServiceProvider` | by IP | no | MaxMind GeoIP2 Precision web service |
| `ip2location` | `IP2LocationProvider` | by IP | no | ip2location.io HTTP API |
| `ipinfo` | `IpInfoProvider` | by IP | no | ipinfo.io HTTP API |
| `google` | `GoogleProvider` | by coordinates or address | yes | Google Geocoding API + Routes API |
| `default` | `DefaultLocationProvider` | static fallback, once configured | no | Config values |

Both MaxMind providers are disabled by default and return null immediately until you enable them and supply credentials or a database path.

## The pipeline

Providers are consulted in pipeline order; reorder them in config to change precedence:

```php
// config/geolocation.php — consulted top to bottom, the first non-null answer wins
'pipeline' => ['maxmind_database', 'maxmind_web', 'ip2location', 'ipinfo', 'google', 'default'],
```

A bundled name you removed from the providers map is skipped silently; any other pipeline name that is neither in the map nor registered with extend() throws UnknownProviderException. A provider that can’t serve a query — for example Google for an IP, or an IP provider for an address — returns null and the next one is tried. Error responses degrade to null too, so a failing provider hands over to the next one.

A provider whose API can’t be reached at all — a timeout, a DNS failure, a refused connection — counts as a miss as well, and so does a request Google refuses as a whole (a key without the API enabled, an exhausted quota): the pipeline moves on instead of throwing, and the error is reported, with credentials redacted, on the LocationResolutionFailed event (DistanceResolutionFailed for a distance). A few errors still abort the lookup on purpose: a fail-fast RateLimitExceededException, a missing or corrupt MaxMind database, and any exception your own provider throws (see Exceptions).

## Empty answers

An empty answer counts as no answer. A Location with no address part, no country and 0,0 coordinates ($location->isEmpty()) — such as IP2Location’s reply for a private IP — is skipped and the next provider is asked. A Location you get back therefore always places something, though a coarse IP match can still lack a city or a country.

## What calls the network by default

With the shipped config an IP lookup sends the IP to api.ip2location.io and then ipinfo.io, even without IP2LOCATION\_API\_KEY or IPINFO\_TOKEN (the request is then unauthenticated). An address or coordinate lookup, and every distance, goes to Google — the Geocoding API for lookups, the Routes API for distances — which needs GOOGLE\_MAPS\_API\_KEY with both APIs enabled to succeed. maxmind\_database and default never touch the network, and in tests Geolocation::fake() never calls a provider.

Trim the pipeline to the providers you have credentials for — every provider left in it is consulted in turn until one answers, and each HTTP provider is a real outbound request:

```php
// Only the providers you have credentials for: offline first, fallback last
'pipeline' => ['maxmind_database', 'ipinfo', 'google', 'default'],
```

To keep visitor IPs on your own servers, leave only the offline providers:

```php
// Keep visitor IPs on your own servers — no provider here calls the network
'pipeline' => ['maxmind_database', 'default'],
```

## The default fallback

The default provider answers only once you configure a default location (any default.\* value). With the shipped empty values it answers null as well, so an unresolved lookup stays null. When it does answer, the Location has type GeolocationType::Default, so you can tell a fallback from a real lookup:

```php
use RoundlyConsulting\Geolocation\Enum\GeolocationType;
use RoundlyConsulting\Geolocation\Facades\Geolocation;

$location = Geolocation::locateRequest();

if ($location?->type === GeolocationType::Default) {
    // No real provider answered — this is the configured fallback location.
}
```

The fallback is never cached — it only answered because the real providers did not, so the next call asks them again. Leave default.\* empty, or remove default from the pipeline, if you would rather receive null (and a LocationResolutionFailed event) when nothing resolves.

## Named providers

The providers key is a named map — the pipeline, provider() and using() all refer to providers by name. When none of the pipeline names match the map, the map’s own order is used. A flat list of class-strings is no longer accepted:

```php
use RoundlyConsulting\Geolocation\Providers\DefaultLocationProvider;
use RoundlyConsulting\Geolocation\Providers\IpInfoProvider;

// Every entry needs a name — the pipeline and provider() address providers by it
'providers' => [
    'ipinfo' => IpInfoProvider::class,
    'default' => DefaultLocationProvider::class,
],

// An unnamed entry throws UnknownProviderException on the first lookup
'providers' => [IpInfoProvider::class],
```

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/geolocation-for-laravel.md)
