genealabs/laravel-maps
GeneaLabs Laravel Maps lets you embed interactive maps in Blade views using simple components and directives. Supports Google Maps and Leaflet/OpenStreetMap, with configurable markers, zoom, controls, and styling—no hand-written JavaScript required.
Verify two things before you write any code: your Laravel version and what the package actually wraps. The description says Leaflet.js + OpenStreetMap (no API key), but the surrounding docs and release notes have drifted over time and some material describes a Google Maps wrapper with API-key configuration. Read the published config and published views — they are the source of truth. Do not wire an API key into .env because a blog post told you to.
Release 0.9.0 is the Laravel 9/10 compatibility bump; there is no stated support for 11+. That is a go/no-go decision, not a detail.
composer require genealabs/laravel-maps on Laravel 9/10. On 11+ either fork it or skip to plain Leaflet (a ~30-line Blade partial plus the CDN/npm Leaflet bundle).composer require genealabs/laravel-maps, then php artisan vendor:publish and use the interactive list — tag names drift between releases, so don't copy them from the README.<script> tags the component emits and the option serialization. Read the JS before you trust the docs.Where to look first, in order: published config → published views → the actual component namespace/signature in the package source (casing and namespacing have shifted across versions; confirm from source).
First use case — one static map, one marker, in a GET route:
<x-maps::map :center="['lat' => 40.7128, 'lng' => -74.0060]" :zoom="13">
<x-maps::marker :position="['lat' => 40.7128, 'lng' => -74.0060]" />
</x-maps::map>
If nothing renders, view-source and read the emitted <script>. You will see the serialized center, zoom, and marker coordinates verbatim. That HTML is your primary debugging surface for the rest of the integration — build the habit immediately.
Day-one decision: publish the views even if you never plan to override them. Once they live under resources/views/vendor/, every later fix is a patch instead of a fork.
The package's value is repeatability, not capability. Structure usage so the genealabs namespace appears in three or fewer files repo-wide and everything else talks to your own wrapper.
Data-driven markers from a collection. Resolve lat/lng and formatting in the controller or a presenter — pass arrays and scalars to Blade, never models or formatted strings:
<x-maps::map :center="$center" :zoom="$zoom">
@foreach ($locations as $location)
<x-maps::marker :position="['lat' => $location['lat'], 'lng' => $location['lng']]">
<x-maps::info-window>
<strong>{{ $location['name'] }}</strong><br>
{{ $location['address'] }}
</x-maps::info-window>
</x-maps::marker>
@endforeach
</x-maps::map>
Keep coordinates as decimals on the model (decimal:7 cast) and cast to float only at the boundary. The moment a view does floatval() or string interpolation on a coordinate, you have created a class of silent failures that only reproduces in production.
Wrap it, once. Create resources/views/components/app-map.blade.php that owns every package call. Feature code uses <x-app-map :markers="$markers" :center="$center" /> and nothing else. When you fork for Laravel 11, swap Leaflet versions, or add clustering, you edit one file. This is the highest-leverage decision in the integration.
Geocode server-side, persist, cache. The package renders pins; it does not geocode. Call your geocoder from a queued job, persist lat/lng plus geocoded_at on the record, and pass plain numbers to the marker. Doing it in Blade means every page render is a billed call plus a multi-hundred-millisecond block. For store locators, seed coordinates at import time and geocode only records missing them — with a rate limit and a failure state so a bad address doesn't loop forever.
Multiple maps per page. Each map needs a unique DOM container ID. If the component signature doesn't expose an ID prop, publish the view and add one. Reusing the default ID renders the first map and silently ignores the rest — a classic "why is the second map blank" afternoon.
Livewire. The package emits inline JS at render time, which fights Livewire's DOM morphing. Wrap maps in wire:ignore and re-initialize manually (Livewire.hook('morph.updated')) when the marker set changes. With wire:navigate, expect the map not to reinit on navigation unless you hook livewire:navigated and force a redraw.
Marker customization. Custom icons, anchor offsets, and popup options go through the marker/options array. Read the published view to see exactly which keys are forwarded to the underlying Leaflet MarkerOptions — that forwarding path is your extension point, and unforwarded keys fail silently.
Testing. There is no suite in the package, so build your own crude baseline: a feature test that renders the Blade view and asserts against emitted HTML — marker count, serialized "lat", expected component markup. It's brittle, but it's your only regression signal and it is exactly what catches behavior changes when you fork.
The docs and the code disagree. Some published material describes a Google Maps wrapper with an API key; the package description says Leaflet/OSM with no key. Trust the config file and the emitted script. If your map is blank and you've been chasing InvalidKeyMapError, you picked up the wrong documentation.
Laravel 11/12 is unsupported territory. 0.9.0 targets 9 and 10. If the upgrade is on your roadmap, plan the fork before the spike so the spike tests the artifact you'll actually ship.
OSM tile policy is a real constraint. OpenStreetMap's tile servers require visible attribution (Leaflet's default control provides it — don't strip it) and prohibit heavy or systematic use. Prototypes and internal dashboards are fine; anything with real traffic needs a commercial tile provider (MapTiler, Stadia, Thunderforest) swapped into the config. Discover this before launch, not after an IP block.
Asset publishing is per-environment. The classic failure is "works locally, 404 in staging" because public/vendor/... was never deployed. Add the publish step to the deploy script and validate in staging.
CSP will bite you. The package emits inline <script> tags. Under a strict script-src, they break. Thread a nonce through the emitted tags (publish the views and edit them) or take an 'unsafe-inline' exception you should not want. Test against production CSP headers in staging.
Marker position format is exact. ['lat' => ..., 'lng' => ...]. Use latitude/longitude/x/y and you get a silent no-marker with no PHP error — the failure is inside Leaflet. Check the serialized object in the emitted script first.
Silent-failure triage order. (1) Browser console — JS errors, missing tile requests, 404s on the Leaflet bundle. (2) The emitted <script> for center/zoom/marker arrays. (3) DOM readiness — the container must exist with a nonzero height before Leaflet initializes; a hidden tab or display:none parent yields a zero-size map. Walk the list; don't guess.
Zero-height containers are the most common "it's broken" report. Leaflet can't size what isn't laid out yet. Give the container an explicit height in CSS and call invalidateSize() on tab-reveal or resize if you're toggling visibility.
No CI, no tests, no support policy. "Supports Laravel 10" means "installs against Laravel 10" until you prove otherwise. Pin the exact version (0.9.0, or 0.9.* at most) and commit the lock file. Run composer update on an isolated branch and diff the lock before merging.
Extension points, in preference order: (1) props/options arrays passed to components; (2) published Blade views overridden under resources/views/vendor/; (3) a thin internal wrapper that replaces the package. Reach for (3) sooner than feels natural — a 30-line Leaflet init you own often beats a wrapper you don't, especially since clustering, routing, heatmaps, and real-time updates are outside this package's scope no matter how you configure it.
When to stop using it. The moment a map becomes a primary product surface — clustering at volume, drawing tools, live tracking, accessible keyboard interaction — go straight to Leaflet directly or Mapbox GL. The package saves the bootstrapping boilerplate and then gets in the way. Use it for secondary UI and prototypes, wrap it, and don't let it become the map layer of record.
How can I help you explore Laravel packages today?