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.comand, for the SDK element, to load scripts fromhttps://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
- Open the map in Studio and choose Publish. The Publish & embed page opens.
- 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 Studio | What you get | Choose it when |
|---|---|---|
| iFrame | A 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. |
| Developer | The 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
widthandheighton the iframe, as any iframe. -
SDK element: give the element a height.
<kaleidr-map>isdisplay:blockby default, so the height applies. The snippets writestyle="display:block; height:480px"anyway: it is harmless, and it covers Safari before 16.4, where the element stays inline and ignoresheight. 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.mountreturns, not on the element.document.querySelector('kaleidr-map').setCameraisundefined. To move an element-mounted map, change itscenter,zoom,pitchorbearingattribute. The map jumps to the new camera with no animation. - You can call it at any time after
Kaleidr.mountreturns. 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 thekaleidr-embed:readymessage 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:
-
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.
-
Open the Embed tab.
-
Change the access dropdown from Any domain to Specific domains.
-
Enter one domain per line, for example:
example.comblog.example.org -
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.comallowsexample.com,www.example.comandshop.eu.example.com. - To test on your own machine while the list is active, add
localhost.
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-referrerorsame-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:
| Message | When | Fields |
|---|---|---|
kaleidr-embed:ready | The map has drawn for the first time. | type, id (the map id), source ("publish" or "share") |
kaleidr-embed:error | The 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:
code | Meaning | What the visitor sees in the frame |
|---|---|---|
no_id | The iframe URL has no map id. | "Couldn't find a map id in the URL." |
not_found | No 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_allowed | The 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_exhausted | The 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_failed | The map could not be fetched, for example during a network problem. | "Could not load the map. Please try again in a moment." |
maplibre_error | The 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 andreadynever arrives. maplibre_errorcan arrive before or afterready, 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
| Topic | Behavior |
|---|---|
| Opening view | The author's saved view, unless you pass a camera. A camera your page passes wins over the saved view, field by field. |
| Scroll | Scrolling 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. |
| Updates | Embeds 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 data | The datasets and image overlays the map draws are drawn for visitors. See Uploaded data on a published map. |
| Unpublishing | An 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. |
| Popups | Clicking the map opens place popups only if the author turned popups on. |
| Height | Follows the element when the element has a height at mount time, otherwise a fixed 500 pixels. |
| Loading | The SDK iframe loads lazily, when it comes near the viewport. |
| Key | None. 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 Studio | Effect on the published map | Effect on a share link |
|---|---|---|
| Delete the dataset in the Data panel | The 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 map | Nothing 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.
Share links
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_IDwith status 200. With the SDK element you also seekaleidr.jsandviewer.jsfromhttps://cdn.kaleidr.com. - In your listener: one
kaleidr-embed:readymessage.
If it does not work
| Symptom | Cause | Fix |
|---|---|---|
The map is 500 pixels tall whatever height you set | The 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: #map | Kaleidr.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 defined | Your 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-src | Your 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-src | Your policy does not allow the loader. | Add https://cdn.kaleidr.com to script-src, or use the plain iframe. |
setCamera does nothing | The 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 function | setCamera 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 map | Expected. 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 embed | The 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 public | A 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 missing | The 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 late | Each 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:readyandkaleidr-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-idafter it mounted does nothing. Remove the element and add a new one, or callhandle.destroy()and mount again. - Allowed domains depend on the browser's referrer and are not a secret.
Next steps
- Viewer reference for every option, attribute and message.
- Kaleidr AI on a published map to let visitors ask questions about the map.
- Datasets on a map for the files you can add to a map, their limits and their column roles.
- Framework integration to mount and destroy the map from React, Next.js or Vue.
- Content Security Policy if your site sends one.