Building a map with multiple locations usually means choosing between a clunky iframe embed with zero styling control, or hand-rolling the Google Maps JS API yourself every time you need one. SN Google Map handles the second option for you, pulling location data straight from an ACF Google Map field and rendering it as a fully styled, interactive map, complete with clustering, custom marker colors, an optional synced location list, and popups built from your own template.
This is the right fit for something like a list of company locations, store fronts, or office addresses, basically anything where you just want all your locations plotted on a map. If you’re already running WP Grid Builder and have something more like a large set of projects or listings that need filtering, sorting, or pagination alongside the map view, WP Grid Builder is going to be the more full featured route. SN Google Map isn’t trying to replace that, it’s built for the simpler “show our locations” use case.
Requirements
Before dropping this component in, you’ll need a couple things in place:
- You need ACF installed, since we’re storing the Google Maps API key in ACF’s settings and pulling the location data from the ACF Google Maps field
- You also need a loop data source where each item has an ACF Google Map field named
location, returning the standard ACF Google Map array shape (lat,lng,address). A custom post type with a location field is a typical setup here.- If your project already has a Google Map field under a different name, you can edit the component’s template directly, swapping every instance of
location.acf.locationforlocation.acf.yourFieldNameeverywhere it appears in the component’s HTML
- If your project already has a Google Map field under a different name, you can edit the component’s template directly, swapping every instance of
- Your Maps JavaScript API key needs to be restricted in Google Cloud Console. Set the application restriction to HTTP referrers limited to your site’s domain, and the API restriction to Maps JavaScript API only. This is important because your API key is exposed on the frontend due to how Google Maps scripts work.
- Last but not least, set a default loop on the
locationsprop inside the component editor. Without one, the Etch editor has no real page context to pull from when you’re editing this component on its own, so it shows a loop init error instead of rendering anything. Pick any post type with thelocationfield as the prop’s default source so the component editor always has something valid to preview against.
PHP Setup
Add this to your theme or a snippet plugin. It saves your Google Maps API key to ACF’s settings, then exposes it to the frontend as window.snGoogleMaps.apiKey:
<?php
if (function_exists('get_field')) {
// Google Maps API Key (saved to ACF)
add_action('acf/init', function () {
acf_update_setting('google_api_key', 'YOUR_KEY_GOES_HERE');
});
// Exposes the saved ACF Google Maps API key on the frontend for SN Google Map component
add_action('wp_enqueue_scripts', function () {
wp_register_script('sn-maps-key-frontend', false, [], null, true);
wp_enqueue_script('sn-maps-key-frontend');
wp_add_inline_script('sn-maps-key-frontend', 'window.snGoogleMaps = { apiKey: ' . wp_json_encode(acf_get_setting('google_api_key')) . ' };');
});
}
Component Props
| Prop | Type | Description |
|---|---|---|
| Locations Loop | Loop | The data source to loop over. Each item must expose a location ACF Google Map field, or your project’s equivalent. |
| Show Location List | Boolean | Shows a visible list of locations alongside the map, a title and address per item by default. Hovering a list item highlights its corresponding marker on the map. Defaults to Visible (true). |
| Enable Clustering | Boolean | Groups nearby markers into clusters at lower zoom levels. Only takes effect with more than one location. |
| Marker Color | Text | CSS color for the map pins (hex, named color, rgb(), or a custom property like var(--primary)). Border and glyph contrast are calculated automatically. Leave blank for Google’s default red pin. |
| Marker Highlight Color | Text | CSS color used when a marker is highlighted, whether from list hover/focus or hovering the marker itself. Leave blank to derive the highlight automatically from markerColor. |
| Aria Label | Text | Accessible label read by screen readers for the map region. Should describe what the map shows, e.g. “Map of store locations.” Defaults to “Interactive Google map.” |
| Map Height | Text | CSS height value for the map (400px, 60vh, etc). Defaults to max(400px, 30vh) |
| Single Location Zoom | Text (number) | Zoom level for single locations, or when multiple locations share the same coordinates. Ignored when locations span different coordinates, since zoom is calculated automatically in that case. Defaults to 16. |
| Wrapper Class | Class | Optional additional classes applied to the component’s root element, for styling from outside the component. |
How It Works
Marker position, title, and popup content all come from .sn-google-map__info-window-list, covered above. The visible list (.sn-google-map__list, shown via showList) is a separate, independently styled loop over the same locations, and its items are matched to their corresponding marker by position, since both loops render the same locations in the same order.
Hovering a list item highlights its corresponding marker on the map, and hovering a marker directly highlights it as well. In both cases the marker swaps to highlightColor (or an automatic shade of markerColor if highlightColor is left blank). The color change is instant rather than animated, since Google’s marker rendering doesn’t support a smooth color transition.
Editing the Marker Template
The component has two separate loops over the same locations prop, and they serve different purposes.
Marker Data / Info Window Template
.sn-google-map__info-window-list is the data source for the entire map. Every marker’s position, title, and popup content comes from here, and every marker always opens a popup when clicked, there’s no way to disable that per-marker.
<div data-title={location.title} data-lat={location.acf.location.lat} data-lng={location.acf.location.lng} class="sn-google-map__info-window">
<h3 class="sn-google-map__info-title h4"><a href={location.permalink.full} class="sn-google-map__link">{location.title}</a></h3>
<p class="sn-google-map__info-address">{location.acf.location.address}</p>
</div>
Everything inside the outer <div>, the heading, link, address paragraph, is freely editable. Add fields, remove them, rearrange them, change the heading tag, whatever the page needs. This content becomes the popup shown when that marker is clicked.
A couple things not to touch here: don’t remove or rename the sn-google-map__info-window class, and don’t remove the data-title, data-lat, or data-lng attributes on the outer <div>. These are how the JavaScript finds markers, positions them, and labels them. Removing the class breaks the whole map. Removing one of those data attributes just causes that specific location to be silently skipped (it logs a warning to the console, but the page keeps working).
Location List
.sn-google-map__list is a separate, optional, visible list shown when showList is enabled. It can look completely different than the Info Window
<li class="sn-google-map__location-item">
<h3 class="sn-google-map__location-item-title h4"><a href={location.permalink.full} class="sn-google-map__link">{location.title}</a></h3>
<p class="sn-google-map__location-item-address">{location.acf.location.address}</p>
</li>
With the default template, that’s the bold red title and address you’d see in a sidebar list next to the map. Since this loop is completely separate from the popup loop, you can take it much further, swap in a thumbnail image, a “Get Directions” link, a category label, whatever fits the layout. Nothing about the list’s markup affects what shows up in the map popups.
This loop has no required classes or attributes beyond whatever you choose to style it with, it doesn’t drive marker placement or popup content. If you want a list item’s link to support keyboard-triggered highlighting, make sure it contains a focusable element such as a link.
Both loops are shared across every instance of the component, on every page it’s used. Neither is a per-instance setting, so editing either one inside the component editor changes that layout everywhere this component appears, not just the map you’re currently looking at. If a specific page needs different layouts, that page needs its own copy of the component, or a separate component entirely, rather than an edit made here.
Also worth noting, the heading tag used in either loop’s title is set directly in the template rather than as a prop, so adjust it per page to match that page’s actual heading hierarchy.
Editor Behavior
Inside the Etch editor and Gutenberg, you’ll see a static placeholder in place of a live map, along with a generated example of what a location’s popup will look like. This example updates automatically as you edit the marker template. The real, interactive map only renders on the live frontend due to both Etch and Gutenberg rendering inside of an iframe.