Getting started

This page takes you from a published map to a page that drives it. It assumes a map has already been published and that you have its publish id (it looks like pub_... and is the last part of the embed URL).

1. Allow your site

A map only accepts commands from sites its author has named. Ask the author to open the map in Icon Map, choose Publish, and:

  1. Add your site to Allowed origins - the scheme and host your page is served from, for example https://portal.contoso.com. A wildcard sub-domain is allowed (https://*.contoso.com); a path is not.
  2. Leave Let the hosting page control this map from script (Embed API) switched on, and leave on the command groups you need.
  3. Publish. Allowed origins and the group switches are part of the signed publish, so a change needs a re-publish.

During development add your local origin too, for example http://localhost:5173. An origin is scheme + host + port, so http://localhost:5173 and http://localhost:3000 are different sites.

If your origin is missing the map still displays in your page - it simply cannot be driven. map.ready rejects with the code denied_origin so you find out immediately rather than wondering why nothing happens.

2. Install the SDK

Load the SDK with a script tag. It defines one global, IconMapEmbed:

<script src="https://www.icon-map.com/js/iconmap-embed/2/iconmap-embed.umd.min.js"></script>
<script>
  const { embed } = IconMapEmbed;
</script>

The SDK is about 17 KB and has no dependencies. It works anywhere a script tag does, including SharePoint, Confluence and CMS pages.

An npm package (@iconmap/embed, with TypeScript types, for use with a bundler) is on its way. The API is the same; the snippets below that use import apply once it is published - until then use the IconMapEmbed global.

3. Mount the map

Give the map an element with a size. The frame fills it.

<div id="map" style="height: 600px"></div>
const map = embed(document.getElementById("map"), {
  publishId: "pub_...",
  onError: (e) => console.error(e.code, e.message),
});

const capabilities = await map.ready;
console.log(capabilities.commands);   // exactly what THIS map answers

ready resolves with the capability descriptor. Use it to decide what your page offers:

const can = (name) => capabilities.commands.includes(name);
if (!can("setFilters")) hideMyFilterPanel();

Branch on capabilities, not on version numbers - the list already accounts for the viewer build, the embed type and the author's switches.

4. Send commands

Every command is a method that returns a promise.

await map.flyTo({ center: [-3.18, 51.48], zoom: 12 });

const layers = await map.getLayers();
await map.setLayerVisibility({ layerId: layers[0].id, visible: false });

await map.setSlicerState({ slicerId: "slicer-region", state: { values: ["Wales"] } });

You can call commands as soon as you have the handle. Anything sent before the map has finished starting is held and answered once the part of the map it needs exists, so this is fine:

const map = embed(el, { publishId });
await map.fitLayer({ layerId: "depots" });   // no need to wait for anything first

5. Listen for events

Nothing is sent until you ask for it.

const stop = map.on("featureClick", (feature) => {
  if (!feature) return;                       // a click on empty map
  showDetails(feature.layerName, feature.fields, feature.featureKey);
});

map.on("viewChanged", (view) => {
  if (view.reason === "user") saveLastView(view);   // ignore moves your own code caused
});

stop();   // unsubscribe

Every state event carries a reason - see Events for why that matters.

6. Handle errors

A rejected command is an EmbedError with a stable code:

const { EmbedError } = IconMapEmbed;

try {
  await map.setFilters({ region: ["EMEA"], colour: "red" });
} catch (e) {
  if (e instanceof EmbedError && e.code === "undeclared_filter") {
    console.warn("not a filter on this map:", e.detail.undeclared);
  } else {
    throw e;
  }
}

The codes are listed in the reference and explained in Errors and versioning.

Start in the right place

Everything you can change at runtime you can also set when the map mounts, so the reader never sees the author's default view flash first:

embed(el, {
  publishId: "pub_...",
  filters: { region: ["EMEA"] },
  view: { center: [2.35, 48.85], zoom: 9 },
  visibility: { layers: { depots: false } },
  basemapId: "slate",
  theme: { mode: "dark" },
  language: "fr",
  chrome: { legend: false, layerControl: false },
  background: "transparent",
  hyperlinkBehavior: "raiseEvent",
  events: ["featureClick", "linkClicked"],
});

bookmarkId applies one of the author's bookmarks, and scene restores a view you captured earlier.

Token-gated maps

If the author published the map with signed tokens required, your server mints a short-lived token with the publish's embed secret and your page hands it over. The secret must never reach the browser.

const map = embed(el, {
  publishId: "pub_...",
  getToken: () => fetch("/api/iconmap-token").then((r) => r.text()),
});

map.on("tokenExpiring", async () => {
  await map.setToken({ token: await fetch("/api/iconmap-token").then((r) => r.text()) });
});

getToken is called on mount and whenever the map asks for a new one. tokenExpiring fires a minute before expiry so you can push a fresh token ahead of time.

If minting is slow, start the download first and supply the token when you have it:

const map = embed(el, { publishId: "pub_...", deferRender: true });   // frame starts loading now
const token = await mintToken();
map.render({ getToken: () => token });

Embedding for your organization

To embed a live map for signed-in colleagues, swap the entry point. The handle is the same.

const { embedForOrganization } = IconMapEmbed;

const map = embedForOrganization(el, {
  mapItem: { workspaceId: "...", itemId: "..." },
  getToken: (scopes) => myMsal.acquireTokenSilent({ scopes }).then((r) => r.accessToken),
});

getToken receives the scopes the map needs at that moment (OneLake, Fabric, and others depending on the map's data) and returns a delegated token for the signed-in user. Leave it out and the map runs its own sign-in inside the frame. See Embedding for your organization for the full setup.

The sites allowed to drive an organizational embed are set on the map item, in Publish > Embedding for your organization. By default they are the same sites as the publish's Allowed origins ("Use the allowed origins above"), saved with the map when you publish - or with Save origins if you never publish. Untick the box to keep a separate list, for example when the published copy may be embedded by any site but only your intranet may script the live map.

On an organizational embed, declared filters and refreshData do not exist (they are absent from capabilities.commands), highlight answers unsupported_command (use setSelection), and exportImage / print are refused with denied_capability when the map or any of its data sources carries a sensitivity label.

TypeScript

The npm package (coming - see the note above) ships its types, so commands, their arguments and results, and event payloads are all typed:

import { embed, type EmbedHandle, type EmbedScene, type EmbedFeatureClick } from "@iconmap/embed";

const map: EmbedHandle = embed(el, { publishId });
const scene = (await map.captureScene()) as EmbedScene;
map.on("featureClick", (f: EmbedFeatureClick | null) => { /* ... */ });

React

There is no wrapper to install - the handle fits a ref and an effect:

function IconMap({ publishId, filters, onFeatureClick }) {
  const el = useRef(null);
  const map = useRef(null);

  useEffect(() => {
    map.current = embed(el.current, { publishId, filters });
    const stop = map.current.on("featureClick", onFeatureClick);
    return () => { stop(); map.current.destroy(); };
  }, [publishId]);

  useEffect(() => { map.current?.setFilters(filters).catch(console.error); }, [filters]);

  return <div ref={el} style={{ height: 600 }} />;
}

Live examples

The examples are live maps you can use, each followed by the complete page that produces it: camera, layers, slicers, filters, selection and highlight, scenes and tours, restyling, popup commands and events.

Troubleshooting

Symptom Cause
ready rejects with denied_origin Your page's origin is not on the map's allowed origins, or the map was not re-published after it was added. Check the exact scheme, host and port.
The frame is blank and the console reports a frame-ancestors violation The same allowed origins list also controls which sites may frame the map. Add your origin and re-publish.
ready resolves, but commands is nearly empty The author switched the Embed API (or most groups) off for this publish.
A command rejects with denied_capability That command's group is switched off for this map.
A command rejects with unsupported_command The map, or this kind of embed, does not have it. Check capabilities.commands before offering the feature.
Commands time out The frame failed to load - look at onError and the network tab.