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.json project 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 containing id and name properties.
  • 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 like selectionChanged or viewChanged.
  • 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:

These files provide reference implementations for connection handling, protocol compliance, and event management.

Summary

  • Enable the embed API by setting the GEOLIBRE_EMBED_ORIGINS environment variable to allow-list your domain.
  • Install @geolibre/embed to get type-safe access via the connect() function with automatic handshake management.
  • Control the map using methods like setView(), setLayerVisibility(), and highlightFeature() that mirror the v2 postMessage protocol.
  • Handle events by subscribing to selectionChanged and viewChanged through the on() method.
  • Reference tests in tests/embed-client.test.ts for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →