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

Google Geocoder Laravel Package

alexpechkarev/google-geocoder

PHP client for the Google Maps Geocoding API. Convert addresses into latitude/longitude coordinates and reverse geocode coordinates back into addresses with a simple, framework-agnostic interface for adding location lookup to your apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require alexpechkarev/google-geocoder
php artisan vendor:publish --provider="Alexpechkarev\GoogleGeocoder\GoogleGeocoderServiceProvider"

On Laravel 5.5+ the service provider and facade are auto-discovered. On older apps, register manually in config/app.php:

'providers' => [
    Alexpechkarev\GoogleGeocoder\GoogleGeocoderServiceProvider::class,
],
'aliases' => [
    'Geocoder' => Alexpechkarev\GoogleGeocoder\GoogleGeocoderFacade::class,
],

Configure the key

The published config/google-geocoder.php holds the API key (and typically region/language). Point it at an env var and never commit the key:

// config/google-geocoder.php
'key' => env('GOOGLE_GEOCODER_KEY'),
GOOGLE_GEOCODER_KEY=your-server-side-key

Create the key in Google Cloud Console with the Geocoding API enabled and billing active, and restrict it by server IP (not HTTP referrer — this is a server-side call).

First use case — turn a form address into coordinates

use Alexpechkarev\GoogleGeocoder\GoogleGeocoderFacade as Geocoder;

$results = Geocoder::geocode('1600 Amphitheatre Parkway, Mountain View, CA')->get();

if ($results->isEmpty()) {
    // ZERO_RESULTS — nothing matched
}

$first = $results->first();
$coords = $first->getCoordinates(); // ['lat' => 37.42..., 'lng' => -122.08...]
$formatted = $first->getFormattedAddress();
$placeId   = $first->getPlaceId();

That single call — address in, lat/lng + canonical formatted address + place_id out — is the 90% use case: geocoding user-entered addresses before saving, and reverse-geocoding a stored point back to a display string. Read the package README next for the exact Result accessor names in the version you install; the wrappers are thin around Google's results array.

Implementation Patterns

Wrap it in your own service, not the facade everywhere

The facade is convenient but leaks a third-party API surface across your controllers. Put one class between them — it gives you a single place to add caching, logging, retries, and a fake for tests.

class GeocodingService
{
    public function coordinatesFor(string $address): ?array
    {
        $key = 'geocode:'.md5(strtolower(trim($address)));

        return Cache::remember($key, now()->addDays(30), function () use ($address) {
            $results = Geocoder::geocode($address)->get();

            if ($results->isEmpty()) {
                return null;
            }

            $geo = $results->first()->getCoordinates();

            return ['lat' => $geo['lat'], 'lng' => $geo['lng']];
        });
    }
}

Geocoding results for a given address are effectively static — cache aggressively. This is the single biggest lever on cost and latency.

Reverse geocoding for display

$results = Geocoder::reverseGeocode($lat, $lng)->get();

$label = $results->first()?->getFormattedAddress();

Use this when you have coordinates (from a map picker, a mobile device, a stored point) and need a human-readable address. Reverse geocoding is much less precise than forward — expect a street or neighborhood, not a rooftop.

Persist place_id, not just lat/lng

Store all three: latitude, longitude, and place_id. Coordinates of the same address can drift as Google updates its data; place_id is the stable canonical identifier and is what you should use if you later adopt Places API or need to re-resolve. Also store location_type if you care about precision (see Gotchas).

Address component filters for structured lookups

When you have a partial address and want it scoped (e.g. postal code within a country), pass components rather than string-concatenating:

Geocoder::geocode('Main Street', 'country:US|postal_code:94105')->get();

This is more reliable than building "Main Street, 94105, US" and avoids ambiguous cross-border matches.

Batch geocoding belongs in a queue

Geocoding is network I/O with a hard rate limit. Never loop over hundreds of records synchronously in a request. Push a job per record (or per chunk) and let workers throttle:

class GeocodeAddress implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public int $tries = 3;
    public int $backoff = 30;

    public function __construct(public int $addressId) {}

    public function handle(GeocodingService $geocoder): void
    {
        // resolve + persist; release() on rate-limit errors
    }
}

Redis::throttle() or a small usleep between calls in a chunked job keeps you under Google's per-second cap and avoids OVER_QUERY_LIMIT.

Validate on the edges, geocode in a service

Validate the raw input in a FormRequest (non-empty, sane length), then do the geocode in a service or observer — never in a controller and never in a model accessor that fires on every read.

Testing

Because everything funnels through your service, you can bind a fake in tests:

$this->instance(GeocodingService::class, new class extends GeocodingService {
    public function coordinatesFor(string $address): ?array
    {
        return ['lat' => 1.0, 'lng' => 2.0];
    }
});

Alternatively, Geocoder::shouldReceive(...) on the facade, but the service swap is cleaner and keeps tests offline.

Gotchas and Tips

The package is unmaintained (last release 2017). Treat this as a risk decision, not a detail. It targets Laravel 5.x and Guzzle 6-era dependencies. On Laravel 9/10/11 and PHP 8.x you may hit:

  • Facade/service-provider signatures that no longer satisfy current interfaces,
  • Composer dependency conflicts with modern guzzlehttp/guzzle,
  • Deprecation notices from old code paths.

Before committing, run composer require on your actual Laravel version in a scratch branch. If it installs, great — but consider forking and pinning, or wrapping the raw Google REST API in ~50 lines yourself (it is a GET to /maps/api/geocode/json). The abstraction this package gives is genuinely thin.

ZERO_RESULTS is not an exception — it's an empty collection. ->get() returns an empty Collection when nothing matched. Always check isEmpty() before ->first(). Distinguish "no match" from "API error" in your UI.

Google error statuses need handling. OVER_QUERY_LIMIT, REQUEST_DENIED, INVALID_REQUEST, and UNKNOWN_ERROR come back in the response body with an HTTP 200. Inspect the response status (the README documents the accessor, commonly isSuccess() or a status accessor) and translate:

  • REQUEST_DENIED → almost always a bad/restricted key or missing billing. Check this first when "nothing works."
  • OVER_QUERY_LIMIT → back off and retry the job; don't hammer.
  • INVALID_REQUEST → your input or component string is malformed.

Coordinates are approximate — record location_type. Google returns ROOFTOP, RANGE_INTERPOLATED, GEOMETRIC_CENTER, or APPROXIMATE. An APPROXIMATE result can be a block or a suburb off. If precision matters (delivery radius, distance calculations), branch on this and flag low-confidence results for manual review. Also check partial_match — Google will happily return a "best effort" for an address it couldn't fully resolve.

Don't trust the returned formatted address as canonical input. getFormattedAddress() is Google's normalized string; storing it is fine for display, but re-geocoding it can return subtly different results. Always key your cache on the user's original input (normalized case/whitespace), not the formatted output.

Config caching bites here. php artisan config:cache bakes env() calls in. If you add the key to .env after caching, it won't take effect. Re-run config:cache after changing the key, and always reference the key via config(), not env(), inside runtime code.

Language and region are per-call, not global. Even if the config sets defaults, pass $language/$region explicitly when the request context differs (e.g. a UK user's address). Results genuinely differ by region bias — a query for "Springfield" is ambiguous across countries.

Rate limits are per-project, shared across every caller. Geocoding caps around 50 requests/second (varies). If you also call Google APIs elsewhere in the app, all traffic shares the quota. Centralize geocoding through your service so you have one throttle point.

HTTPS/cert issues on old stacks. On legacy PHP with outdated CA bundles, Guzzle can throw cURL SSL errors. If you see this, update ca-certificates on the host rather than disabling verification in the package config.

Extension point: the package constructs its own HTTP client internally. If you need retries, logging, or a custom transport, the practical extension point is your wrapper service — intercept before calling the facade, catch its exceptions, and handle backoff there. Forking to inject a Guzzle client is possible but rarely worth it for a package this thin; wrapping is the lower-maintenance path.

Have an exit plan. Given the staleness, keep geocoding behind your own interface (GeocodingService) so swapping to the direct Maps API, geocoder-php/google-maps-provider, or a different vendor is a one-class change rather than a grep across the codebase.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package