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

Laravel Google Static Map Laravel Package

mastani/laravel-google-static-map

Generate static map images in Laravel with a fluent builder. Build signed Google Static Maps URLs or saved files with markers, paths, styles, zoom, and custom dimensions—ideal for emails, PDFs, invoices, and cached thumbnails with no JavaScript. A lightweight alternative to headless browsers.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up (5 minutes)

composer require mastani/laravel-google-static-map
php artisan vendor:publish --provider="Mastani\GoogleStaticMap\GoogleStaticMapServiceProvider"

On Laravel 5.5+ the service provider and GoogleStaticMap facade are auto-discovered. On older versions, register them manually in config/app.php (providers array + aliases key 'GoogleStaticMap' => ...).

Then put a server-side key in .env (the config file reads it, typically GOOGLE_STATIC_MAP_KEY — confirm the exact key name in the published config/google-static-map.php):

GOOGLE_STATIC_MAP_KEY=AIza...

Where to look first

  1. The published config file — it holds the API key and defaults for zoom/width/height/map type. Everything you'll want to tune day-to-day lives there.
  2. The facade class itself (Mastani\GoogleStaticMap\...) — it's a thin URL builder, so read getMap() plus the marker/format methods end to end. It's a small file; 10 minutes of reading saves a day of guessing at array shapes.
  3. Not the Google docs first — the wrapper's parameter mapping is what you actually call.

First use case

The canonical one: an order/shipping confirmation email with a pin on the delivery address.

use Mastani\GoogleStaticMap\GoogleStaticMap;

$url = GoogleStaticMap::getMap(51.5074, -0.1278, 14, 600, 300);
<img src="{{ $url }}" width="600" height="300" alt="Delivery location">

If that renders, you're done with setup. The next thing to build is almost always a cached, server-proxied version of that URL — see below.

Implementation Patterns

Keep URL generation out of Blade. Build the URL in a controller, view composer, or a small MapImage service and hand the view a string. You will inevitably need caching, fallbacks, and a second size, and none of that belongs in a template.

class MapPreview
{
    public function forCoordinates(float $lat, float $lng, int $zoom = 14, string $size = '600x300'): string
    {
        [$w, $h] = explode('x', $size);
        return GoogleStaticMap::getMap($lat, $lng, $zoom, (int) $w, (int) $h);
    }
}

Cache the URL, not just the request. Static map URLs are deterministic for the same inputs, so they're ideal cache values. Round coordinates to the zoom's precision so nearby points collapse to one entry:

$key = sprintf('map:%s:%s:%d:%dx%d', round($lat, 4), round($lng, 4), $zoom, $w, $h);

$url = Cache::remember($key, now()->addDay(), fn () => GoogleStaticMap::getMap($lat, $lng, $zoom, $w, $h));

This matters because every render is a billable Google request. Caching the URL string only helps if the browser/CDN also caches the image — which is why the proxy below is usually worth it.

Proxy through your own route. Instead of putting the Google URL (and your key) straight into src, expose /maps/preview/{lat}/{lng}.png that fetches the bytes once, stores them, and serves them from your domain:

Route::get('/maps/preview/{lat}/{lng}.png', function (float $lat, float $lng) {
    $path = "maps/{$lat}_{$lng}_600x300.png";

    if (! Storage::exists($path)) {
        $url = GoogleStaticMap::getMap($lat, $lng, 14, 600, 300);
        Storage::put($path, Http::get($url)->throw()->body());
    }

    return response(Storage::get($path), 200, [
        'Content-Type'  => 'image/png',
        'Cache-Control' => 'public, max-age=2592000',
    ]);
});

You get: no key in HTML, your own long-lived cache headers, a stable URL that survives key rotation, and images that actually load in PDF renderers and email clients.

Markers. Marker data is the part most likely to bite you on array shape — check the facade for the exact keys. The typical call pattern is one marker per location with a color and a single-character label:

GoogleStaticMap::getMap($lat, $lng, 13, 800, 400)
    ->addMarker($lat, $lng, 'red', 'A');   // verify signature against source

For multi-stop itineraries, build the marker list from your own data and loop; don't hand-assemble the URL.

Emails vs PDFs vs views — treat them differently.

Target Approach
Blade view Direct <img> with generated URL is fine; add width/height attributes to reserve layout space.
Email Host the image yourself (proxy route, absolute HTTPS URL). Many clients block remote images by default — design the email to look acceptable with the image missing, and always set alt.
PDF (dompdf, snappy) Do not rely on remote image fetching. Download the bytes at generation time and embed as base64 or a local file path.

Generate a fixed set of sizes. Don't let arbitrary dimensions creep in from user input — pick three canonical sizes (thumb/hero/print) in config and reuse them. Google bills per request and caps dimensions (see Gotchas).

Testing. The package builds a URL; it doesn't call Google. So tests are cheap and should be pure string assertions:

$url = GoogleStaticMap::getMap(51.5074, -0.1278, 14, 600, 300);

$this->assertStringContainsString('center=51.5074,-0.1278', $url);
$this->assertStringContainsString('size=600x300', $url);
$this->assertStringContainsString('zoom=14', $url);

For the proxy route, Http::fake() with a small binary body so you never touch the network in CI.

Gotchas and Tips

API key restrictions will break you, and the error is opaque. Google Static Maps keys come in two flavours and they are mutually exclusive in practice:

  • HTTP referrer restriction — works if the browser fetches Google directly. Breaks the moment you proxy server-side.
  • IP address restriction — works for server-side fetching and PDF generation. Breaks inline browser use.

If you're proxying (recommended), use an IP-restricted key. If you're embedding the Google URL directly, use referrer restriction. Getting this wrong returns a 403 or a grey "for development purposes only" tile, not a helpful message.

Your API key is in the HTML if you skip the proxy. $url contains key=.... Anyone can view-source it. Referrer restrictions mitigate this but don't eliminate it; the proxy route is the actual fix.

Size and zoom limits are real. The free Static Maps tier caps images at 640×640 (larger requires a premium/signed plan), and scale=2 doubles the pixel output — good for retina, but it still counts as the nominal size for limits. Zoom is 0–21; at high zoom over rural areas Google will just render "no imagery" tiles. Clamp both from config rather than trusting caller input.

URL length. Markers, styles, and paths are all query parameters. A handful of markers is fine; a 50-stop route will blow past the ~8 KB URL limit and you'll get a 414. For dense data, switch to encoded polylines or render server-side with a different tool.

Label and color strings need encoding. Marker labels are single characters in most tiers, and colors/labels with #, spaces, or Unicode must be URL-encoded. If the wrapper doesn't encode for you, do it yourself — an unencoded # silently truncates the URL at that point.

config:cache in production. The key is read from config, so after changing .env you must re-run php artisan config:cache (and clear it locally). A stale cached config with the old key is a classic "works locally, 403 in prod" cause.

Fail visibly, not with a broken image. Google returns a PNG error tile with a 400/403 status rather than failing loudly. When you download bytes in the proxy, check the HTTP status and validate the payload (getimagesize() returning false means you cached an error image). Better to serve a placeholder than to permanently cache a grey rectangle with a 30-day Cache-Control.

Watch the format. PNG is right for maps with markers and text (sharp edges); JPEG is smaller for photographic/dense terrain. The package supports both — pick per use case, and make sure your proxy's Content-Type matches what you actually requested.

Pin the version and wrap it. This is a 47-star, single-maintainer wrapper over a URL string builder — the surface area is small enough that the risk is low, but the escape hatch should be cheap. Put every call behind one class (MapPreview above) so swapping to hand-built URLs or another package is a one-file change. Also pin mastani/laravel-google-static-map to a caret-minor in composer.json and check Laravel version constraints on upgrade — small wrapper packages often lag on major framework releases.

The extension point is the config file, not the class. If you need different defaults per context (email vs admin dashboard vs PDF), don't subclass — set the config values at the call site or pass explicit zoom/size/dimensions per call and keep the config as baseline. The facade reads defaults; overriding per call is cleaner than fighting for a config override.

Don't forget attribution/licensing terms. Google's terms require attribution for map imagery in some contexts and prohibit caching beyond limited periods in others — worth a read before you set a 30-day cache TTL on a public-facing map.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect