# How to Build Embedded iframe Integrations with @geolibre/embed

> Learn to build embedded iframe integrations using @geolibre/embed. Control map views, layers, and selections programmatically with a type-safe client for seamless interaction.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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:

```bash
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.

```html
<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`](https://github.com/opengeos/GeoLibre/blob/main/.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.

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/.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:

```html
<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:

```typescript
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:

```typescript
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:

```typescript
{ 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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/docs/user-guide/embedding.md) – Authoritative documentation for configuration and API reference.
- [`tests/embed-client.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/embed-client.test.ts) – Unit tests validating the `@geolibre/embed` client connection flow.
- [`tests/embed-api.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/embed-api.test.ts) – Integration tests for message shapes, acknowledgements, and events.
- [`apps/geolibre-desktop/src/lib/embed-api.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/embed-api.ts) – Runtime bridge between web view and embed API.
- [`apps/geolibre-desktop/src/hooks/embedHost.ts`](https://github.com/opengeos/GeoLibre/blob/main/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_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`](https://github.com/opengeos/GeoLibre/blob/main/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.