Feature Renderer
Render cards and popups yourself from your own data using featureRenderer.
Feature Renderer
featureRenderer is a JavaScript callback you pass to Mapsemble.init() that takes over rendering of every feature — both the cards in the list and the popups shown when a marker is clicked. Mapsemble still handles the map canvas, marker placement, pagination, hover sync, and positioning; your callback only decides what goes inside each card or popup container.
This is available in shadow-embed mode only (embed: 'shadow').
API signature
Mapsemble.init({
container: '#mapsemble-map',
mapId: 'YOUR_MAP_ID',
embed: 'shadow',
featureRenderer: function (container, feature, queryParams, location) {
// render content into `container`
},
featureUnmount: function (container) {
// optional: clean up before Mapsemble removes the container
},
});
| Argument | Type | Description |
|---|---|---|
container |
HTMLElement |
The <div> Mapsemble has created for this render. Write your content into it. |
feature |
object |
All properties the data source returned for this feature — __id, remoteId (if configured), and every field value. No transformation. |
queryParams |
object |
The query parameters Mapsemble last sent to the data source (filters, extent, etc.). Useful for passing through to your own fetches. |
location |
string |
Where this render is happening. One of: 'list', 'list_mobile', 'popup', 'popup_mobile'. |
Cards
When a feature appears in the card list, Mapsemble calls featureRenderer with location === 'list' (desktop) or location === 'list_mobile' (mobile bottom sheet).
featureRenderer: function (container, feature, queryParams, location) {
if (location === 'list' || location === 'list_mobile') {
container.innerHTML =
'<div class="my-card">' +
'<h3>' + feature.name + '</h3>' +
'<p>' + feature.description + '</p>' +
'</div>';
}
}
Mapsemble controls:
- Creating and removing the card container as the user paginates or filters.
- Hover sync — hovering a card highlights the corresponding marker.
- Click behavior — clicking a card pans the map to the feature.
You control everything inside the container.
Popups
When a marker is clicked, Mapsemble calls the same featureRenderer callback with location === 'popup' (or 'popup_mobile' on mobile, where the popup is shown in a bottom sheet). Branch on location to render a popup-specific layout:
featureRenderer: function (container, feature, queryParams, location) {
if (location === 'popup' || location === 'popup_mobile') {
container.innerHTML =
'<div class="my-popup">' +
'<strong>' + feature.name + '</strong>' +
'<p>' + feature.price + '</p>' +
'</div>';
return;
}
// ...card rendering for 'list' / 'list_mobile'
}
Mapsemble positions the popup relative to the marker, runs edge detection, and pans the map if there isn't room. Your callback only decides what's inside.
featureUnmount
Optional cleanup callback. Called right before Mapsemble removes a container from the DOM — after pagination, filter changes, or when a popup closes. Use it to tear down any framework state bound to the container (React roots, Vue instances, event listeners).
featureUnmount: function (container) {
// Mapsemble will remove `container` after this returns
}
If you write plain HTML into the container with innerHTML, you can omit featureUnmount — Mapsemble will just remove the element.
React example
Mapsemble.init({
container: '#mapsemble-map',
mapId: 'YOUR_MAP_ID',
embed: 'shadow',
featureRenderer: (container, feature, queryParams, location) => {
const root = ReactDOM.createRoot(container);
container.__root = root;
if (location === 'popup' || location === 'popup_mobile') {
root.render(<FeaturePopup {...feature} />);
} else {
root.render(<FeatureCard {...feature} />);
}
},
featureUnmount: (container) => {
container.__root?.unmount();
},
});
Async rendering
featureRenderer can return a Promise. When it does, Mapsemble waits for the promise to resolve before measuring the container and positioning the popup. This prevents popups from flashing at the wrong spot while async data loads.
featureRenderer: (container, feature, queryParams, location) => {
return new Promise((resolve) => {
const root = ReactDOM.createRoot(container);
container.__root = root;
root.render(<FeaturePopup {...feature} onReady={resolve} />);
});
}
// In the component:
function FeaturePopup({ id, onReady }) {
const { data } = useSWR(`/api/popup/${id}`, fetcher);
useEffect(() => {
if (data && onReady) onReady();
}, [data, onReady]);
if (!data) return <Skeleton />;
return <PopupContent data={data} />;
}
Synchronous renderers (plain HTML with no data fetching) don't need to return anything — Mapsemble proceeds immediately.
Default behavior when featureRenderer is omitted
If your map is configured for remote data but you don't pass a featureRenderer:
- Cards fall back to a built-in minimal renderer that shows each feature's title (or
__idif no title is configured). - Popups fall through to Mapsemble's default popup path — the template-driven popup rendered inside the shadow root.
This is useful for quick previews but gives up the per-feature layout control that featureRenderer provides.
See also
- Remote Datasource — the server-side contract for feeding features to Mapsemble.
- Widget.js Reference — full
Mapsemble.init()option surface.