laravel-maps
July 11, 2026 · View on GitHub
Version Support
| Laravel | PHP |
|---|---|
| 11.x | 8.2, 8.3, 8.4, 8.5 |
| 12.x | 8.2, 8.3, 8.4, 8.5 |
| 13.x | 8.3, 8.4, 8.5 |
Prerequisites
- PHP >= 8.2
- Laravel >= 11.0
- A Google Maps API key
Installation
composer require genealabs/laravel-maps
Add your Google Maps API key to .env:
GOOGLE_MAPS_API_KEY=your-api-key-here
Add the following entry to your config/services.php file:
'google' => [
'maps' => [
'api-key' => env('GOOGLE_MAPS_API_KEY'),
],
],
Usage
The package provides a Map facade and an app('map') helper. Both
give you the same API — use whichever you prefer.
Every map follows the same pattern:
- Initialize the map with a config array.
- Add overlays (markers, polylines, polygons, etc.).
- Create the map to get the HTML and JavaScript output.
- Render the output in your Blade view.
Basic Map with Geolocation
This prompts the user for their location and centers the map on it:
use GeneaLabs\LaravelMaps\Facades\Map;
Route::get('/map', function () {
Map::initialize([
'center' => 'auto',
'onboundschanged' => 'if (!centreGot) {
var mapCentre = map.getCenter();
marker_0.setOptions({
position: new google.maps.LatLng(mapCentre.lat(), mapCentre.lng())
});
}
centreGot = true;',
]);
Map::add_marker([]);
return view('map', ['map' => Map::create_map()]);
});
Single Marker
Map::initialize([
'center' => '37.4419, -122.1419',
'draggableCursor' => 'default',
]);
Map::add_marker([
'position' => '37.4419, -122.1419',
]);
$map = Map::create_map();
Multiple Markers
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 'auto',
'draggableCursor' => 'default',
]);
Map::add_marker([
'position' => '37.429, -122.1519',
'infowindow_content' => 'Hello World!',
'icon' => 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=A|9999FF|000000',
]);
Map::add_marker([
'position' => '37.409, -122.1319',
'draggable' => true,
'animation' => 'DROP',
]);
Map::add_marker([
'position' => '37.449, -122.1419',
'onclick' => 'alert("You clicked the marker!")',
]);
$map = Map::create_map();
Polyline
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 'auto',
]);
Map::add_polyline([
'points' => [
'37.429, -122.1319',
'37.429, -122.1419',
'37.4419, -122.1219',
],
]);
$map = Map::create_map();
Polygon
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 'auto',
]);
Map::add_polygon([
'points' => [
'37.425, -122.1321',
'37.4422, -122.1622',
'37.4412, -122.1322',
'37.425, -122.1021',
],
'strokeColor' => '#000099',
'fillColor' => '#000099',
]);
$map = Map::create_map();
Drawing Tools
Map::initialize([
'drawing' => true,
'drawingDefaultMode' => 'circle',
'drawingModes' => ['circle', 'rectangle', 'polygon'],
]);
$map = Map::create_map();
Directions
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 'auto',
'directions' => true,
'directionsStart' => 'Empire State Building',
'directionsEnd' => 'Statue of Liberty',
'directionsDivID' => 'directionsDiv',
]);
$map = Map::create_map();
When rendering, include a <div id="directionsDiv"></div> in your view
to display the turn-by-turn directions.
Street View
Map::initialize([
'center' => '37.4419, -122.1419',
'map_type' => 'STREET',
'streetViewPovHeading' => 90,
]);
$map = Map::create_map();
Marker Clustering
Map::initialize([
'center' => '37.409, -122.1319',
'zoom' => '13',
'cluster' => true,
'clusterStyles' => [
[
'url' => 'https://raw.githubusercontent.com/googlemaps/js-marker-clusterer/gh-pages/images/m1.png',
'width' => '53',
'height' => '53',
],
],
]);
Map::add_marker(['position' => '37.409, -122.1319']);
Map::add_marker(['position' => '37.409, -122.1419']);
Map::add_marker(['position' => '37.409, -122.1219']);
Map::add_marker(['position' => '37.409, -122.1519']);
$map = Map::create_map();
KML Layer
Map::initialize([
'zoom' => 'auto',
'kmlLayerURL' => 'https://www.google.com/maps/d/kml?mid=your-kml-id',
]);
$map = Map::create_map();
Circles
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 14,
]);
Map::add_circle([
'center' => '37.4419, -122.1419',
'radius' => 500,
'strokeColor' => '#FF0000',
'fillColor' => '#FF0000',
'fillOpacity' => 0.35,
]);
$map = Map::create_map();
Rectangles
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 14,
]);
Map::add_rectangle([
'bounds' => [
'37.435, -122.155',
'37.449, -122.129',
],
'strokeColor' => '#FF0000',
'fillColor' => '#FF0000',
'fillOpacity' => 0.35,
]);
$map = Map::create_map();
Ground Overlay
Map::initialize([
'center' => '40.7128, -74.0060',
'zoom' => 13,
]);
Map::add_ground_overlay([
'url' => 'https://example.com/overlay-image.png',
'bounds' => [
'40.700, -74.020',
'40.730, -73.990',
],
'opacity' => 0.5,
]);
$map = Map::create_map();
Rendering in Blade
Pass the map data to your view and render the JavaScript in your
<head> and the HTML in your <body>:
<!DOCTYPE html>
<html>
<head>
{!! $map['js'] !!}
</head>
<body>
{!! $map['html'] !!}
</body>
</html>
With a layout:
@section('scripts')
{!! $map['js'] !!}
@endsection
@section('content')
{!! $map['html'] !!}
<div id="directionsDiv"></div>
@endsection
Controller Example
For more complex setups, use a dedicated controller:
namespace App\Http\Controllers;
use GeneaLabs\LaravelMaps\Facades\Map;
class MapController extends Controller
{
public function markers(): \Illuminate\View\View
{
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 'auto',
]);
Map::add_marker([
'position' => '37.429, -122.1519',
'infowindow_content' => 'Location A',
]);
Map::add_marker([
'position' => '37.449, -122.1419',
'infowindow_content' => 'Location B',
]);
return view('maps.show', ['map' => Map::create_map()]);
}
public function directions(): \Illuminate\View\View
{
Map::initialize([
'center' => '37.4419, -122.1419',
'zoom' => 'auto',
'directions' => true,
'directionsStart' => 'San Francisco, CA',
'directionsEnd' => 'San Jose, CA',
'directionsDivID' => 'directionsDiv',
]);
return view('maps.directions', ['map' => Map::create_map()]);
}
}
API Reference
The Map facade (or app('map')) exposes the following methods:
| Method | Description |
|---|---|
initialize(array $config) | Configure map center, zoom, type, and behavior |
add_marker(array $params) | Add a marker with position, info window, icon, etc. |
add_polyline(array $params) | Draw a polyline from an array of points |
add_polygon(array $params) | Draw a filled polygon from an array of points |
add_circle(array $params) | Draw a circle with center and radius |
add_rectangle(array $params) | Draw a rectangle from bounds |
add_ground_overlay(array $params) | Overlay an image on the map |
create_map() | Generate the map and return ['js' => ..., 'html' => ...] |
get_lat_long_from_address(string $address) | Geocode an address to lat/lng |
MultiPolygon from GeoJSON
To render a GeoJSON MultiPolygon, decode the JSON and call add_polygon() once per polygon in the collection:
Route::get('/multipolygon', function () {
$geojson = json_decode($geojsonString); // your GeoJSON MultiPolygon
$config = ['center' => '-6.2, 106.8', 'zoom' => 10];
app('map')->initialize($config);
foreach ($geojson->coordinates as $polygon) {
$points = [];
// Each polygon may have an outer ring and optional holes — use the outer ring (index 0)
foreach ($polygon[0] as $coord) {
$points[] = $coord[1] . ', ' . $coord[0]; // GeoJSON is [lng, lat]; the library expects "lat, lng"
}
app('map')->add_polygon(['points' => $points]);
}
$map = app('map')->create_map();
return '<html><head>' . $map['js'] . '</head><body>' . $map['html'] . '</body></html>';
});
Note: GeoJSON coordinates use
[longitude, latitude]order, but this library expects"latitude, longitude"strings. The example above swaps them accordingly.
Custom Overlay Popup
You can create a custom overlay popup that appears automatically (without requiring a click or hover) by using the onload configuration option together with the Google Maps JavaScript API custom popup example.
The approach has two parts:
- Define a custom
Popupoverlay class in a separate<script>tag. - Use the
onloadoption to instantiate the popup once the map finishes loading.
Route::get('/popup', function () {
$lat = 37.4419;
$lng = -122.1419;
$config = [
'center' => "{$lat}, {$lng}",
'zoom' => 13,
'onload' => "
var position = new google.maps.LatLng({$lat}, {$lng});
var popup = new Popup(position, document.getElementById('popup-content'));
popup.setMap(map);
",
];
app('map')->initialize($config);
$map = app('map')->create_map();
$popupScript = <<<'JS'
<script>
class Popup extends google.maps.OverlayView {
constructor(position, content) {
super();
this.position = position;
content.classList.add("popup-bubble");
var container = document.createElement("div");
container.classList.add("popup-container");
container.appendChild(content);
this.anchor = document.createElement("div");
this.anchor.classList.add("popup-anchor");
this.anchor.appendChild(container);
this.stopEventPropagation();
}
onAdd() {
this.getPanes().floatPane.appendChild(this.anchor);
}
onRemove() {
if (this.anchor.parentElement) {
this.anchor.parentElement.removeChild(this.anchor);
}
}
draw() {
var divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
var display = Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
? "block"
: "none";
if (display === "block") {
this.anchor.style.left = divPosition.x + "px";
this.anchor.style.top = divPosition.y + "px";
}
if (this.anchor.style.display !== display) {
this.anchor.style.display = display;
}
}
stopEventPropagation() {
var anchor = this.anchor;
anchor.style.cursor = "auto";
["click", "dblclick", "contextmenu", "wheel", "mousedown",
"mouseup", "mouseover", "mouseout", "touchstart", "touchend",
"touchmove"].forEach(function (event) {
anchor.addEventListener(event, function (e) {
e.stopPropagation();
});
});
}
}
</script>
JS;
$popupStyles = <<<'CSS'
<style>
.popup-container {
cursor: auto;
position: absolute;
width: 200px;
transform: translate(-50%, -100%);
}
.popup-bubble {
background-color: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
font-family: sans-serif;
font-size: 14px;
overflow-y: auto;
max-height: 200px;
}
.popup-anchor {
position: absolute;
width: 100%;
}
</style>
CSS;
return "<html><head>"
. $popupStyles
. $map['js']
. $popupScript
. "</head><body>"
. '<div id="popup-content" style="display:none;">Hello from a custom popup!</div>'
. $map['html']
. "</body></html>";
});
The onload JavaScript runs after the map initializes, so the map variable is available. The Popup class extends google.maps.OverlayView and positions custom HTML at a given lat/lng coordinate. The popup appears immediately without any user interaction.
You can also use the infowindow_content marker option for simpler popups that open on click:
$marker = [
'position' => '37.4419, -122.1419',
'infowindow_content' => '<strong>Hello!</strong><br>This popup opens on click.',
];
app('map')->add_marker($marker);
To auto-open a standard info window without a click, use the onload option:
$config = [
'center' => '37.4419, -122.1419',
'zoom' => 13,
'onload' => 'google.maps.event.trigger(marker_0, "click");',
];
More Examples
BIOINSTALL has a great website showing how to do all the things with the class. No reason to reinvent the wheel, so here it is. The only thing to note is that $this->googlemaps is now either the facade Map:: or the app variable app('map').
License
This package is open-sourced software licensed under the MIT license.