Attach the map editor
At the end of this guide your page shows Kaleidr's on-map editing tools over
a Kaleidr basemap, themed to your brand. This works on every plan, Free
included. You need a publishable key that carries the design scope, which
new keys carry by default.
The embedded editor keeps its work in the page's memory. Nothing is saved,
exported or published from the embed, and your page gets no callback when
something changes. A reload, or handle.destroy(), discards the edits. If
you need maps that persist, create them in
Kaleidr Studio and embed them with
Viewer.
Before you start
- A publishable key (
kld_pk_live_…) from kaleidr.com/api-keys. See Get an API key. - The origin of the page you will test on, for example
http://localhost:3000, in the key's allowed origins. A key with no allowed origins works on any site. - A way to serve the page over HTTP, for example
npx serve .. - If your site sends a Content Security Policy, the editor directives
from Content Security Policy.
The editor runs in your page, so it needs
connect-src,img-src,worker-src,font-srcandstyle-srcentries that the iframe products do not. - No other Kaleidr editor on the same page.
Step 1: Add a sized container and mount
<div id="editor" style="height:640px"></div>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const handle = Kaleidr.mount('#editor', {
product: 'editor',
publishableKey: 'kld_pk_live_…',
});
</script>
What you should see: a Kaleidr basemap at world zoom filling the
container (kaleidr-morning, unless your organization has set another
default), with a Create button on the map. Choose Create to open the
editor's sidebar with the Text, Image, Marker and Popup tools.
The container must have a height. The editor is 100% of the container's width and height, and its panels stay inside the container.
You can also use the element. It is display:block by default, so the height
applies; the snippet writes display:block as well, for Safari before 16.4:
<kaleidr-map product="editor" publishable-key="kld_pk_live_…"
style="display:block; height:640px"></kaleidr-map>
Step 2: Choose the basemap and the opening camera
const handle = Kaleidr.mount('#editor', {
product: 'editor',
publishableKey: 'kld_pk_live_…',
styleId: 'kaleidr-night',
center: [-0.1276, 51.5072], // [longitude, latitude]
zoom: 12,
pitch: 45,
bearing: -20,
});
What you should see: the dark basemap over central London, tilted and rotated.
How the basemap is chosen:
styleId, if you pass one. Use a style id such askaleidr-morning,kaleidr-afternoon,kaleidr-evening,kaleidr-night,kaleidr-plainorkaleidr-editorial. The list with descriptions is in the Tile reference.- Otherwise your organization's default basemap, if one is set.
- Otherwise
kaleidr-morning.
Camera defaults are center [0, 0], zoom 2, pitch 0 and bearing 0. Unlike
Viewer and Tile, the editor accepts zoom without center.
A camera option passed to Kaleidr.mount that is not a finite number is
ignored, and the console says so. On the element, a camera attribute that is
not a number is dropped with no message.
[kaleidr:editor] ignored zoom: abc is not a finite value — using 2 instead.
Step 3: Theme the panels (optional)
Pass theme to match the editor's panels to your product. It is an object
with up to 17 named keys. Keys outside that list are ignored.
const handle = Kaleidr.mount('#editor', {
product: 'editor',
publishableKey: 'kld_pk_live_…',
theme: {
brandColor: '#0b5fff',
brandColorHover: '#0847c2',
brandColorOn: '#ffffff',
fontPrimary: '"Inter", system-ui, sans-serif',
cardRadius: '12px',
buttonRadius: '8px',
},
});
What you should see: the active tool, focus rings and primary buttons in your brand color, and the panels in your font.
All 17 keys are listed in the Editor reference.
Three things to know:
- The values are CSS values.
brandColortakes any CSS color,cardRadiusany CSS length,fontPrimaryanyfont-familyvalue. Load the font yourself. - The theme is applied to the whole document while the editor is mounted and is restored when the editor is destroyed. It does not change your own styles unless they use the same custom properties.
- The theme is read at mount only.
handle.setThemeis not supported on the editor. To change the theme, destroy the editor and mount it again, which discards the edits.
On the element, theme is a JSON string:
<kaleidr-map product="editor" publishable-key="kld_pk_live_…"
theme='{"brandColor":"#0b5fff","buttonRadius":"8px"}'
style="display:block; height:640px"></kaleidr-map>
Step 4: Move the camera from your page (optional)
document.getElementById('paris').addEventListener('click', () => {
// With a duration the map animates. Without one it jumps.
handle.setCamera({ center: [2.3522, 48.8566], zoom: 12, duration: 1000 });
});
What you should see: the map flies to Paris. Anything the person placed on the map stays where it was placed.
setCamera is the only way your page can act on the editor's map. The handle
does not expose the map object.
Step 5: Tear down
handle.destroy();
destroy() removes the editor, its map and its panels from the container
and restores the theme values. The edits are discarded.
In a single-page app, call destroy() when the route or component that owns
the editor goes away. If you use the element, removing it from the document
does the same. See Framework integration.
Because only one editor can exist on a page at a time, always destroy the current editor before you mount another.
A complete page
Save this as index.html, put your key in, and serve the folder over HTTP
from an origin the key allows.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kaleidr editor</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
header { padding: 12px 16px; display: flex; gap: 8px; align-items: center; }
#editor { height: calc(100vh - 56px); }
</style>
</head>
<body>
<header>
<strong>Sketch on the map</strong>
<button id="london">London</button>
<button id="paris">Paris</button>
<button id="reset">Start over</button>
<span>Your work is not saved.</span>
</header>
<div id="editor"></div>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const options = {
product: 'editor',
publishableKey: 'kld_pk_live_…',
styleId: 'kaleidr-morning',
center: [-0.1276, 51.5072],
zoom: 11,
theme: { brandColor: '#0b5fff', brandColorOn: '#ffffff' },
};
let handle = Kaleidr.mount('#editor', options);
document.getElementById('london').addEventListener('click', () => {
handle.setCamera({ center: [-0.1276, 51.5072], zoom: 11, duration: 1000 });
});
document.getElementById('paris').addEventListener('click', () => {
handle.setCamera({ center: [2.3522, 48.8566], zoom: 11, duration: 1000 });
});
// One editor per page: destroy before mounting again.
document.getElementById('reset').addEventListener('click', () => {
handle.destroy();
handle = Kaleidr.mount('#editor', options);
});
</script>
</body>
</html>
How it behaves
| Topic | Behavior |
|---|---|
| Where it runs | In your document. The map canvas and the editor's panels are elements inside the mount target. |
| Session | The SDK exchanges your key for a short-lived session before the editor starts, and renews it while the editor is mounted. |
| Edits | Held in memory. Not saved, not exported, not published, not reported to your page. |
| Basemap | Loaded by the page from https://tile.kaleidr.com. Fixed at mount. |
| Theme | Applied document-wide at mount, restored at destroy. |
| Camera | Set at mount, moved with handle.setCamera. |
| Usage | The editor's session request does not count a map load, and the embedded editor has no AI features that spend credits. Its basemap requests that carry your key add map loads in bulk, one for every 20 requests in an hour: see Map loads. |
| Signals | None. There is no ready event, no change event and no error callback. |
Tell your users
The editor itself does not warn that work is temporary. If people will spend time in it, say so in your own interface, as the complete page above does, so nobody expects to find their map again later.
Check that it worked
- On the page: the basemap fills the container, the Create button opens the sidebar, and you can place a text or a marker on the map.
- In the console: no line that contains
[kaleidr. - In the Network tab:
kaleidr.jsandeditor.jsfromhttps://cdn.kaleidr.com; onePOST …/sdk/sessionswith status 200; requests tohttps://tile.kaleidr.comfor the style and the map tiles.
If it does not work
The SDK logs a failed mount to the console as [kaleidr:editor] …. For a
refused session it logs only the HTTP status. The reason is in the
response, which you can read in the Network tab by selecting the sessions
request. A 403 carries its code in the detail field. A 429 carries its
code in the error field, with a sentence in detail.
| Symptom | Cause | Fix |
|---|---|---|
Nothing renders. Console: [kaleidr:editor] [editor-embed] attach() requires an { apiKey } | No key was passed. | Pass publishableKey. |
Nothing renders. The console shows a CORS error for …/sdk/sessions and a [kaleidr:editor] fetch error | The key is unknown or revoked, or the page's origin is not in the key's allowed origins. | Check the key. Add the page's exact origin (scheme, host and port) at kaleidr.com/api-keys and allow a few minutes for the change to apply. |
Console: [kaleidr:editor] kaleidr session exchange failed (403) | The key does not carry the design scope (insufficient_scope), is limited to other products (product_not_allowed), or the plan does not admit the request (tier_capability_not_allowed). | Read detail in the Network tab. Create a key with the default scopes. |
Console: [kaleidr:editor] Kaleidr: a server key (kld_sk_…) must never be used in the browser… | You passed a server key. | Use a publishable key. Rotate the server key if the page was ever public. |
| The container stays empty and has no height | The mount target has no height. | Give it one. On the element in Safari before 16.4, add display:block too. |
The panels render but the map is blank. Console: [kaleidr:editor] … errors. The Network tab shows 429 responses from https://tile.kaleidr.com with "error":"tile_access_restricted" | The basemap host refused the page's requests. | Confirm the key is a live publishable key and the page's origin is in its allowed origins, wait a few minutes after creating or editing the key, and reload. If it continues, contact support. |
| The panels render but the map is blank, and the console reports a Content Security Policy violation | Your policy blocks the basemap, the map's worker, or its images. | Add the editor directives from Content Security Policy. |
| The editor has no styling | Your policy blocks inline styles. | Allow 'unsafe-inline' in style-src. |
| A second editor misbehaves, or the first stops working | Two editors are mounted. | Destroy the first before mounting the second. |
[kaleidr] setTheme() is not supported on product "editor" — the call is a no-op. | The theme is mount-time only. | Pass theme at mount. |
[kaleidr] theme string must be JSON… | The theme attribute is not a JSON object. | Use single quotes around the attribute and double quotes inside the JSON. |
| A theme key has no effect | The key is not one of the 17, or is misspelled. Unknown keys are ignored without a message. | Check the key against the list. |
| The edits disappeared | The page reloaded, or the editor was destroyed or its element was moved or removed. | Expected. See the note at the top of this page. |
Limits
- Nothing is saved, exported or published from the embed.
- No change callback, no ready signal, no error callback.
- No access to the map object, so no custom layers and no way to read what was drawn.
- No Data tab, design catalog, AI panel, 3D model library, sharing or projects. The rail has Text, Image, Marker, Popup, Background, Layer, Design and Legend; see Scope.
- One editor per page.
- The basemap and theme cannot change after mount.
Next steps
- Editor reference for every option and theme key.
- Framework integration to mount and destroy the editor from React, Next.js or Vue.
- Content Security Policy for the editor's directives.
- Embed a published map when you need maps that persist.