How to Build Embedded iframe Integrations with @geolibre/embed
GeoLibre provides a type-safe embed client that connects to an iframe-hosted map viewer via postMessage, enabling programmatic control over map views, layers, and user selections after allow-listing your origin.
The GeoLibre mapping platform supports deep integration through browser-based iframe embedding. By leveraging the @geolibre/embed npm package, developers can establish bidirectional communication with a static GeoLibre viewer to build embedded iframe integrations that load projects, manipulate the camera, and respond to user interactions in real time.
Security Configuration and Origin Allow-Listing
Before establishing communication, you must enable cross-origin messaging. According to the embedding guide in docs/user-guide/embedding.md, the embed API is disabled by default for security reasons.
Set the GEOLIBRE_EMBED_ORIGINS environment variable on the server to permit specific domains:
GEOLIBRE_EMBED_ORIGINS="https://yourdomain.com,https://app.yourdomain.com"
Alternatively, configure the build-time variable VITE_GEOLIBRE_EMBED_ORIGINS when building the viewer. This allow-list ensures only trusted origins can send commands and receive events from the embedded map.
Setting Up the iframe Container
The static GeoLibre viewer serves from https://web.geolibre.app/ and accepts project configurations via the url query parameter. You can embed the viewer using standard HTML with optional chrome removal using the maponly flag.
<iframe
src="https://web.geolibre.app/?url=https://share.geolibre.app/sample.geolibre.json&maponly"
title="GeoLibre map"
width="100%"
height="600"
style="border:0;"
loading="lazy"
allow="fullscreen; geolocation"
></iframe>
The viewer loads the specified .geolibre.json project and displays the map with optional UI elements controlled by URL parameters.
Installing the Typed Client
The @geolibre/embed package provides a zero-dependency ESM client that normalizes the postMessage handshake. Install it via npm, then use the connect function to establish a type-safe connection to the iframe element.
import { connect } from "@geolibre/embed";
const iframe = document.querySelector<HTMLIFrameElement>("iframe");
const map = await connect(iframe, {
origin: "https://web.geolibre.app",
timeoutMs: 15_000,
requestTimeoutMs: 15_000,
});
The client automatically manages the ready handshake, attaches unique requestId values to commands, and resolves promises when corresponding ack messages return from the viewer.
Core API Methods for iframe Control
Once connected, the client exposes methods that mirror the v2 postMessage protocol commands. These enable complete programmatic control over the embedded map:
loadProject(url)– Loads a new.geolibre.jsonproject without reloading the iframe.setView(target)– Flies the camera to coordinates or fits a bounding box.highlightFeature({layerId, featureId, fit?})– Selects and optionally zooms to specific features.setLayerVisibility(layerId, visible)– Toggles layer visibility dynamically.listLayers()– Returns an array of layer summaries containingidandnameproperties.addLayer(spec)– Adds runtime layers with valid sources (GeoJSON, vector tiles, etc.).exportImage()– Returns a PNG data URL of the current map canvas.on(event, listener)– Subscribes to viewer events likeselectionChangedorviewChanged.disconnect()– Cleans up message listeners and rejects pending promises.
All methods return promises that resolve when the viewer acknowledges command completion.
Practical Implementation Examples
Basic Static Embed
For simple use cases requiring only display, include the iframe with a hosted project URL:
<iframe
src="https://web.geolibre.app/?url=https://share.geolibre.app/sample.geolibre.json&maponly"
width="100%"
height="600"
style="border:0;"
allow="fullscreen"
></iframe>
Programmatic Map Control
Drive the map from your host application using the typed client to change views and respond to selections:
import { connect } from "@geolibre/embed";
(async () => {
const iframe = document.querySelector<HTMLIFrameElement>("iframe");
const map = await connect(iframe, {
origin: "https://web.geolibre.app",
timeoutMs: 15_000,
});
// Fly to specific coordinates
await map.setView({ center: [-95.7, 37.1], zoom: 5 });
// Hide the roads layer
await map.setLayerVisibility("roads", false);
// Listen for user feature selections
map.on("selectionChanged", ({ featureIds }) => {
console.log("Selected features:", featureIds);
});
})();
Dynamic Layer Addition and Image Export
Add data layers at runtime and capture the current map view as an image:
import { connect } from "@geolibre/embed";
(async () => {
const iframe = document.getElementById("map") as HTMLIFrameElement;
const map = await connect(iframe, { origin: "https://web.geolibre.app" });
// Add a GeoJSON circle layer
const layerId = await map.addLayer({
id: "my-points",
type: "circle",
source: {
type: "geojson",
data: { type: "FeatureCollection", features: [] },
},
paint: {
"circle-color": "#ff6600",
"circle-radius": 6,
},
});
// Export current view as PNG
const pngDataUrl = await map.exportImage();
const img = document.createElement("img");
img.src = pngDataUrl;
document.body.append(img);
})();
Understanding the Message Protocol
All communication uses a versioned postMessage protocol (currently v2) with a standard envelope shape:
{ v: 2, type: "...", payload: {...} }
Host to GeoLibre commands include loadProject, setView, highlightFeature, and setLayerVisibility. GeoLibre to Host events include ready, ack, selectionChanged, and viewChanged. The client library abstracts this complexity, but understanding the protocol helps when debugging or implementing custom clients as shown in tests/embed-api.test.ts.
Key Source Files and Testing
The embed functionality is implemented and tested across these locations:
docs/user-guide/embedding.md– Authoritative documentation for configuration and API reference.tests/embed-client.test.ts– Unit tests validating the@geolibre/embedclient connection flow.tests/embed-api.test.ts– Integration tests for message shapes, acknowledgements, and events.apps/geolibre-desktop/src/lib/embed-api.ts– Runtime bridge between web view and embed API.apps/geolibre-desktop/src/hooks/embedHost.ts– React hook managing iframe connections in the desktop app.
These files provide reference implementations for connection handling, protocol compliance, and event management.
Summary
- Enable the embed API by setting the
GEOLIBRE_EMBED_ORIGINSenvironment variable to allow-list your domain. - Install
@geolibre/embedto get type-safe access via theconnect()function with automatic handshake management. - Control the map using methods like
setView(),setLayerVisibility(), andhighlightFeature()that mirror the v2 postMessage protocol. - Handle events by subscribing to
selectionChangedandviewChangedthrough theon()method. - Reference tests in
tests/embed-client.test.tsfor production-ready connection patterns.
Frequently Asked Questions
How do I enable the embed API in my GeoLibre instance?
Set the GEOLIBRE_EMBED_ORIGINS environment variable to a comma-separated list of permitted origins, or use the VITE_GEOLIBRE_EMBED_ORIGINS build-time variable when compiling the viewer. Without this configuration, the viewer rejects all postMessage commands for security.
Can I use the embed client with a self-hosted GeoLibre deployment?
Yes. When calling connect(), provide your self-hosted origin (e.g., https://maps.yourcompany.com) in the options object. Ensure your domain is included in the server's allow-list and that the iframe src points to your deployment URL.
How do I capture when a user selects features on the embedded map?
Subscribe to the selectionChanged event using map.on("selectionChanged", callback). The callback receives an object with featureIds and layer information, allowing your host application to synchronize selection state with the embedded viewer.
What happens if the iframe fails to respond to a command?
The connect() function accepts timeoutMs and requestTimeoutMs options that control handshake and command timeout durations. If timeouts occur, pending promises reject and you can retry operations or call disconnect() to clean up the connection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →