Skip to main content

Embed a published map

At the end of this guide a map you made in Kaleidr Studio is live on your own page, limited to the sites you choose, and your page knows whether it loaded. This works on every plan, Free included. You need a Kaleidr account to publish. You do not need an API key.

Before you start​

  • A map in Kaleidr Studio that you are ready to show publicly.
  • A page you can edit. The SDK element needs a <script> tag. The plain iframe needs only HTML.
  • If your site sends a Content Security Policy, permission to frame https://map-embed.kaleidr.com and, for the SDK element, to load scripts from https://cdn.kaleidr.com. See Content Security Policy.
  • A way to serve the page over HTTP while you test, for example npx serve . in the folder that holds it.

Step 1: Publish the map​

  1. Open the map in Studio and choose Publish. The Publish & embed page opens.
  2. Under Project status, the status reads Draft. Choose Go Live.

What you should see: the status changes to Live, the button changes to Re-publish, an Unpublish button appears beside it, and an Embed code section appears with a live preview below it.

A published map shows the map as it is currently saved in Studio, not a copy taken when you chose Go Live. Studio saves your edits as you work, so every edit you make after publishing, including a dataset you add, is public the next time an embed loads. You do not choose Re-publish for that. Re-publish records the map as a new published version and keeps the same id, link and embed code; visitors see the same map before and after it.

To change a published map without visitors seeing the work in progress, choose Unpublish first and publish again when you are done. Unpublishing stops every embed of the map until you publish again. Studio has no command that copies a map, so there is no private copy to work in.

Publishing includes the data you uploaded. The datasets and image overlays you added in the map's Data panel are drawn for visitors as the layers you styled. Read Uploaded data on a published map before you publish a map whose data is not meant for everyone.

Step 2: Copy the embed code​

The Embed code section has two formats.

Format in StudioWhat you getChoose it when
iFrameA plain <iframe> that points at the published map.The page cannot run your scripts (a site builder, a CMS block, a wiki), or you only need the map to appear.
DeveloperThe SDK loader and a <kaleidr-map product="viewer"> element.You control the page and want to set or move the camera from code, place a chat panel over the map, or manage the map's lifecycle in a single-page app.

Choose a format and select Copy.

The iFrame format:

<iframe
src="https://map-embed.kaleidr.com/embed/YOUR_MAP_ID"
width="100%"
height="480"
style="border:0;"
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
allow="geolocation; microphone; fullscreen; clipboard-write"
title="Kaleidr map"
></iframe>

The Developer format:

<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js" async></script>
<kaleidr-map product="viewer" share-id="YOUR_MAP_ID"
style="display:block;width:100%;height:480px;"></kaleidr-map>

YOUR_MAP_ID stands for the id Studio puts in the snippet. It is made of letters, digits and the characters ., _, ~ and -, up to 100 characters. The same id works in both formats.

Both formats carry the map's Kaleidr AI: the Ask Kaleidr panel when its Control Tower is on, and AI chat in its popups when Popup Window is on. See Kaleidr AI on a published map.

Step 3: Add it to your page and size it​

Paste the snippet where the map should appear.

What you should see: a "Loading map…" indicator, then the map at the view you saved in Studio.

Sizing rules:

  • Plain iframe: set width and height on the iframe, as any iframe.

  • SDK element: give the element a height. <kaleidr-map> is display:block by default, so the height applies. The snippets write style="display:block; height:480px" anyway: it is harmless, and it covers Safari before 16.4, where the element stays inline and ignores height. You can also set both in a stylesheet:

    kaleidr-map { display: block; height: 480px; }
  • The map fills the width of the element. If the element has no height when it mounts, the map falls back to a fixed 500 pixels and stays that height even if you size the element later. An element inside a hidden tab or a collapsed panel has no height, so mount it when it becomes visible, or give it an explicit height.

  • For a responsive height, use any CSS that resolves to a height: height:60vh, aspect-ratio:16/9, or a flex or grid cell.

Step 4: Set the opening camera (optional)​

The map opens at the view its author saved. An author can save a separate view for desktop, tablet and phone widths, or set the map to frame its content; a map saved that way picks its view from the width of the frame and frames itself again when the frame is resized. To open somewhere else, pass a camera. center is longitude first, then latitude.

On the element:

<kaleidr-map product="viewer" share-id="YOUR_MAP_ID"
center="-0.1276,51.5072" zoom="12" pitch="45" bearing="-20"
style="display:block; height:480px"></kaleidr-map>

In the plain iframe, add the camera to the URL as camera=longitude,latitude,zoom:

<iframe
src="https://map-embed.kaleidr.com/embed/YOUR_MAP_ID?camera=-0.1276,51.5072,12&pitch=45&bearing=-20"
width="100%" height="480" style="border:0;" loading="lazy"
referrerpolicy="no-referrer-when-downgrade" allow="geolocation; microphone; fullscreen; clipboard-write"
title="Kaleidr map"></iframe>

A camera you pass wins over the author's saved view, including a view saved for the visitor's screen width and content framing. Each value you pass replaces only that part of the view: the parts you leave out keep the author's values. Once your page has set a camera, the map stops framing itself again when the frame is resized. One rule to remember: zoom is ignored unless you also pass center. pitch and bearing work alone.

What you should see: the map opens on the camera you gave, with the same markers, layers and uploaded data as before.

To move the camera after the map has loaded, mount from JavaScript and keep the handle:

<div id="map" style="height:480px"></div>
<button id="paris">Paris</button>

<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const handle = Kaleidr.mount('#map', {
product: 'viewer',
shareId: 'YOUR_MAP_ID',
});

document.getElementById('paris').addEventListener('click', () => {
// duration is in milliseconds. Leave it out to jump with no animation.
handle.setCamera({ center: [2.3522, 48.8566], zoom: 12, duration: 800 });
});
</script>

Two things to know about setCamera:

  • It lives on the handle that Kaleidr.mount returns, not on the element. document.querySelector('kaleidr-map').setCamera is undefined. To move an element-mounted map, change its center, zoom, pitch or bearing attribute. The map jumps to the new camera with no animation.
  • You can call it at any time after Kaleidr.mount returns. A call made before the map inside the iframe is ready is kept and applied when the map reports ready, so you do not have to wait for the kaleidr-embed:ready message described in Step 6. To choose the view the map opens on, use mount options.
  • Like a camera passed at load, it wins over the author's saved view and ends the map's automatic re-framing on resize. The fields you leave out keep their current values.

Step 5: Restrict the sites the map renders on (optional)​

By default a published map renders on any site that has its id. To limit it:

  1. In Studio, open the map and choose Share. Opening the Share dialog publishes the map if it is not published yet, and creates a new share link each time.

  2. Open the Embed tab.

  3. Change the access dropdown from Any domain to Specific domains.

  4. Enter one domain per line, for example:

    example.com
    blog.example.org
  5. Click outside the field. Studio saves the list and shows Saved.

What you should see: the map still renders on the listed sites. On any other site the frame shows "This map isn't allowed to render on this site. Please contact the map owner."

How the list is matched:

  • Enter hostnames only. A leading https:// is ignored. Do not add a path or a port.
  • A domain covers its subdomains. example.com allows example.com, www.example.com and shop.eu.example.com.
  • To test on your own machine while the list is active, add localhost.
The check depends on the referrer

The map decides whether your site is allowed by reading the referrer the browser sends when it loads the iframe. If no referrer arrives, a domain-locked map does not render. That happens when:

  • your page sends Referrer-Policy: no-referrer or same-origin, as a response header or a <meta name="referrer"> tag;
  • the iframe carries referrerpolicy="no-referrer";
  • the page is opened from disk as a file:// URL;
  • someone opens the map's iframe URL directly in a browser tab.

Browsers' default policy, strict-origin-when-cross-origin, sends your origin and works. If your site sets a stricter policy, keep the snippet's referrerpolicy="no-referrer-when-downgrade" on the plain iframe, or relax the policy for the page that holds the map.

If your page shows the map inside another frame, the referrer is that frame's address, so that frame's domain is the one that must be on the list.

Allowed domains control where the map appears. They are not a way to keep a published map's contents confidential, and that includes the datasets the map draws: see Uploaded data on a published map. Do not publish data that must stay private.

Step 6: Know when the map loaded or failed (optional)​

The SDK has no ready promise and the element fires no events. The signal comes from the iframe, which posts a message to your page in two cases:

MessageWhenFields
kaleidr-embed:readyThe map has drawn for the first time.type, id (the map id), source ("publish" or "share")
kaleidr-embed:errorThe map could not load, or part of it failed to draw.type, id, code, message

Listen on window, and check both where the message came from and which frame sent it:

const el = document.getElementById('locations'); // the <kaleidr-map> element

window.addEventListener('message', (event) => {
if (event.origin !== 'https://map-embed.kaleidr.com') return;
const frame = el.querySelector('iframe');
if (!frame || event.source !== frame.contentWindow) return;

const msg = event.data;
if (!msg || typeof msg.type !== 'string') return;

if (msg.type === 'kaleidr-embed:ready') {
console.log('map loaded', msg.id);
} else if (msg.type === 'kaleidr-embed:error') {
console.warn('map problem', msg.code, msg.message);
}
});

With the plain iframe, compare event.source with that iframe's contentWindow.

The code on an error message is one of:

codeMeaningWhat the visitor sees in the frame
no_idThe iframe URL has no map id."Couldn't find a map id in the URL."
not_foundNo published map has this id. It was never published, was unpublished, or the id is mistyped."This map isn't available — it may have been unpublished, paused, or the link is incorrect."
domain_not_allowedThe map has an allowed-domain list and your page is not on it, or sent no referrer."This map isn't allowed to render on this site. Please contact the map owner."
map_loads_exhaustedThe owner's organization has used its map loads for the period."This map has reached its map load limit. It will be available again when the owner's allowance resets."
fetch_failedThe map could not be fetched, for example during a network problem."Could not load the map. Please try again in a moment."
maplibre_errorThe map started, but something on it failed to draw. message carries the detail.The map, possibly with a part missing.

Notes on timing:

  • For every code except maplibre_error, no map is shown and ready never arrives.
  • maplibre_error can arrive before or after ready, and more than once. Treat it as a warning, not as proof that the map is unusable.
  • The SDK loads the iframe lazily. A map far below the fold does not start loading until the visitor scrolls near it, so neither message arrives until then. Do not treat a few seconds of silence as a failure.
  • The frame also posts other kaleidr-embed: messages that the SDK uses for its own work. They are not a public contract. Match on the two types above and ignore the rest.

A complete page​

Save this as index.html, replace YOUR_MAP_ID, and serve the folder over HTTP.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Our locations</title>
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; }
#map { height: 480px; }
</style>
</head>
<body>
<h1>Our locations</h1>
<p id="status">Loading the map…</p>

<div id="map"></div>
<p>
<button id="london">London</button>
<button id="paris">Paris</button>
</p>

<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const target = document.getElementById('map');
const status = document.getElementById('status');

const handle = Kaleidr.mount(target, {
product: 'viewer',
shareId: 'YOUR_MAP_ID',
});

window.addEventListener('message', (event) => {
if (event.origin !== 'https://map-embed.kaleidr.com') return;
const frame = target.querySelector('iframe');
if (!frame || event.source !== frame.contentWindow) return;
const msg = event.data;
if (!msg || typeof msg.type !== 'string') return;

if (msg.type === 'kaleidr-embed:ready') {
status.textContent = 'Map loaded.';
} else if (msg.type === 'kaleidr-embed:error' && msg.code !== 'maplibre_error') {
status.textContent = 'The map could not load (' + msg.code + ').';
}
});

document.getElementById('london').addEventListener('click', () => {
handle.setCamera({ center: [-0.1276, 51.5072], zoom: 12, duration: 800 });
});
document.getElementById('paris').addEventListener('click', () => {
handle.setCamera({ center: [2.3522, 48.8566], zoom: 12, duration: 800 });
});
</script>
</body>
</html>

How it behaves​

TopicBehavior
Opening viewThe author's saved view, unless you pass a camera. A camera your page passes wins over the saved view, field by field.
ScrollScrolling with the pointer over the map zooms the map, as in Studio. With the pointer outside the map, scrolling moves your page. On touch screens, dragging moves the map and pinching zooms it.
UpdatesEmbeds show the map as it is currently saved in Studio. A saved edit appears the next time the embed loads, with no re-publish.
Uploaded dataThe datasets and image overlays the map draws are drawn for visitors. See Uploaded data on a published map.
UnpublishingAn embed of a map that is no longer published shows the "isn't available" message. Publishing the map again restores the same embed URLs. See Unpublish a map.
PopupsClicking the map opens place popups only if the author turned popups on.
HeightFollows the element when the element has a height at mount time, otherwise a fixed 500 pixels.
LoadingThe SDK iframe loads lazily, when it comes near the viewport.
KeyNone. Do not pass a publishable key to a viewer mount. It adds a session request the map does not need, and if that request is refused the map does not render.

Uploaded data on a published map​

The datasets you add in Studio's Data panel are part of the map you publish. A visitor sees each dataset drawn as the layer you styled, and each image overlay in its place. This is the same in the plain iframe, in the SDK element and on a share link. There is nothing to switch on, and the page that embeds the map needs no key.

Because a published map shows the map as it is currently saved, a dataset you add to a map that is already published is public as soon as the map is saved, with no re-publish.

What is served, and to whom:

  • Only the data of a published map. A dataset is sent to a visitor only as part of a map that is published, or of a share link, and only when that map includes the dataset. A dataset on a map you have not published is not served.
  • A dataset cannot be fetched by itself. Nothing lists a map's datasets, and a dataset's id opens nothing without the published map it belongs to.
  • Anyone who can open the map can read the data. A published map is public. Allowed domains decide which sites the map renders on; they do not keep its data confidential.
  • Every column is sent, not only the ones you styled. A visitor's browser receives each row with all of its columns. Before you publish, remove the columns that should not be public from the file and upload it again.

How to stop serving a dataset:

You do this in StudioEffect on the published mapEffect on a share link
Delete the dataset in the Data panelThe dataset is deleted and no longer served. The rest of the map is unchanged.The link draws the map without that dataset's layer.
Unpublish the mapNothing of the map is served, its datasets included.None. The link keeps working with the data it had.

To delete a dataset, choose the trash control on its row in the Data panel and confirm: Delete or replace a dataset has the steps, and what Undo does afterward. Deleting only the layers that draw a dataset is not the same thing: the dataset stays stored and stays in the map, so it is still served.

A share link is a snapshot of the map's design. It shows the map as it was when the link was created: editing the map afterward, or unpublishing it, does not change what the link shows. The snapshot includes the map's name, its allowed-domain list and its Kaleidr AI switches as they were then, so a later change to them does not change where a link already created renders or which Kaleidr AI features it offers. Its Kaleidr AI also needs the map: it works only while the map is published, and only on a page the map's allowed domains include now, so unpublishing the map or removing a domain stops it. The map's owner pays for it; see Kaleidr AI on a published map.

The stored datasets are the exception. A share link reads each dataset from the same stored copy the map uses, so deleting a dataset removes it from every share link that drew it. Data kept inside the map itself is part of the snapshot and stays in the link: pasted JSON, and rows Studio stored in the map when an upload could not reach the server.

Opening Studio's Share dialog publishes the map, including a map you had unpublished, and creates a new share link each time. A map you have shared more than once has several links, each a snapshot from the moment it was created.

Studio has no control to list, delete or pause a share link, and no documented API call does it. A share link stops working when its map is deleted in Studio, which deletes the map's share links with it, or when Kaleidr removes the link at your request. Deleting a dataset takes that dataset out of the link; the link itself keeps working. To hand out a link to a newer state of the map, open Share again and use the new link; the earlier links keep showing what they showed.

Unpublish a map​

On the Publish & embed page, a live map has an Unpublish button next to Re-publish. Studio asks first: "Unpublish this map? Embeds of it stop working. Share links you have already handed out still show the map, but their Kaleidr AI stops." Choose Unpublish to go ahead or Cancel to leave the map live.

  • Every embed of the map stops: the frame shows the "isn't available" message and posts not_found.
  • The map keeps its id. Choosing Go Live again restores the same embed URLs, so the snippets already on your pages work again without a change.
  • Share links you have already handed out keep working with the data they had. Their Kaleidr AI stops until you publish the map again.

Stopping is not instant everywhere:

  • A browser that already loaded the data keeps it. A visitor with the map open still sees the layer, and a browser can reuse data it loaded in the last few minutes.
  • A download link that was already issued can stay readable for up to an hour. For a larger dataset the visitor's browser is given a temporary download link. One issued before you deleted the dataset or unpublished the map works until it expires, at most one hour later.

How it loads:

  • Each visitor downloads the whole dataset. The map does not load a dataset in pieces as the visitor pans. A table with many rows or many columns therefore takes longer to appear for visitors, and longer still on a slow connection. Publish the rows and columns the map needs.
  • A dataset that cannot be loaded does not take the map down. The rest of the map draws, without that layer.

The limits on what you can upload are in Dataset limits.

How views are counted​

Each time the embed loads for a visitor, it counts one map load against the organization that owns the map, not against the site that embeds it. The same visitor loading the same map again within 24 hours is not counted again. Views on Kaleidr's own pages, such as the preview in Studio, are not counted.

When the organization has used its map loads for the period, embeds show the limit message and post map_loads_exhausted until the allowance resets. Plan allowances are in Quota and rate limits.

Check that it worked​

  • On the page: the map shows your markers, your routes, and the datasets and image overlays you added in Studio's Data panel, drawn as the layers you styled. The on-map zoom buttons respond, and the credits line appears at the bottom of the map.
  • In the console: no line that contains [kaleidr.
  • In the Network tab: a document request to https://map-embed.kaleidr.com/embed/YOUR_MAP_ID with status 200. With the SDK element you also see kaleidr.js and viewer.js from https://cdn.kaleidr.com.
  • In your listener: one kaleidr-embed:ready message.

If it does not work​

SymptomCauseFix
The map is 500 pixels tall whatever height you setThe element had no height when it mounted, your CSS sets display:inline on it, or the browser is Safari before 16.4 and the element has no display:block.Give it display:block and a height, and mount it while it is visible.
Nothing renders. Console: [kaleidr] <kaleidr-map> requires product="chat|viewer|editor|tile"The product attribute is missing or misspelled.Set product="viewer".
Nothing renders. Console: [kaleidr:viewer] Kaleidr map: invalid id "" — must match ^[A-Za-z0-9._~-]{1,100}$.share-id is missing, empty, or has a character outside the allowed set.Copy the id from Studio again.
Nothing renders. Console: [kaleidr:viewer] mount target not found: #mapKaleidr.mount ran before the target element existed, or the selector is wrong.Put the script after the element, or fix the selector.
Console: Kaleidr is not definedYour script ran before kaleidr.js finished loading. Studio's snippet loads it with async.Remove async from the loader tag, or run your code in the tag's load event.
The frame says "This map isn't available…"The map is not published, was unpublished, or the id is wrong.Choose Go Live in Studio. A map that was published before keeps its id; for a newly published map, copy the id from the snippet.
The frame says "This map isn't allowed to render on this site."The map has an allowed-domain list and your page's domain is not on it, or your page sent no referrer.Add the domain in Studio, or fix the page's referrer policy. See Step 5.
The frame says "This map has reached its map load limit."The owner's organization has used its map loads for the period.Wait for the allowance to reset, or raise it. See Quota and rate limits.
The frame says "Could not load the map. Please try again in a moment."The map could not be fetched.Reload. If it persists, check your network and try the iframe URL in a new tab.
An empty box, and the console reports a Content Security Policy violation for frame-srcYour policy does not allow the frame.Add https://map-embed.kaleidr.com to frame-src.
The console reports a Content Security Policy violation for script-srcYour policy does not allow the loader.Add https://cdn.kaleidr.com to script-src, or use the plain iframe.
setCamera does nothingThe map never loaded, or the handle was destroyed.Check for a kaleidr-embed:error message and the console. A call made before the map is ready is applied when it becomes ready. If the map has not reported ready after a bounded wait, normally 10 seconds after the frame loads, the call is sent anyway and the console shows [kaleidr] the map frame has not reported ready: a frame that is showing an error has no map to move.
el.setCamera is not a functionsetCamera was called on the element.Call it on the handle from Kaleidr.mount, or change the element's camera attributes.
Scrolling the page stops at the mapExpected. With the pointer over the map, scrolling zooms the map.Scroll with the pointer beside the map. Give the map less than the screen's height, so visitors can scroll past it.
Your edits do not appear in the embedThe embed shows the map as it is saved, and reads it when it loads. The page was loaded before the edit was saved.Reload the page that embeds the map. Re-publishing is not needed.
The embed shows an edit you did not mean to make publicA published map shows the map as it is currently saved.Undo the edit. To work privately, unpublish the map while you edit and publish it again when you are done.
The map draws, but a layer made from an uploaded dataset is missingThe dataset was deleted, or it could not be loaded at that moment. The rest of the map draws without it.Check in Studio that the dataset is still in the map's Data panel and that its layer draws there, then reload the page.
A layer made from a large dataset appears lateEach visitor downloads the whole dataset.Upload fewer rows or columns. See Uploaded data on a published map.

Limits​

  • The embed is read-only. Your page cannot add markers or layers to it, read its data, or receive click events from it.
  • The only signals to your page are kaleidr-embed:ready and kaleidr-embed:error.
  • You cannot restyle the map or swap its basemap from the embed. Change the map in Studio; the embed shows the saved change the next time it loads.
  • Changing the element's share-id after it mounted does nothing. Remove the element and add a new one, or call handle.destroy() and mount again.
  • Allowed domains depend on the browser's referrer and are not a secret.

Next steps​