kaleidr.js, the loader
kaleidr.js is the one script every Kaleidr embed starts from. It defines
the global window.Kaleidr and the <kaleidr-map>
custom element. It is small: it contains no map library and no product code.
When you mount a product, it loads that product's bundle from the same CDN
and hands it your options.
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
There is no npm package. The script tag is the only way to load the SDK. For React, Next.js, Vue and TypeScript, see Framework integration.
Loading the script
| Question | Answer |
|---|---|
| Where does the tag go? | Anywhere. <kaleidr-map> elements mount whether they appear before or after the tag. |
When can I call Kaleidr.mount? | After the script has run. With a plain <script src> tag, any later script on the page can call it. |
Can I use async or defer? | Yes for elements. For Kaleidr.mount, wait for the tag's load event, because window.Kaleidr does not exist until then. |
| How often? | Once per page. |
Can I add integrity? | No. The file is updated in place, so a pinned hash stops matching at the next release. See CDN versioning. |
| Does it run on the server? | No. It needs window and document. In a server-rendered app, load it in the browser only. |
Loading the script with async and mounting when it arrives:
<div id="map" style="height:480px"></div>
<script>
const s = document.createElement('script');
s.src = 'https://cdn.kaleidr.com/embed/v1/kaleidr.js';
s.async = true;
s.onload = () => {
Kaleidr.mount('#map', { product: 'viewer', shareId: 'YOUR_MAP_ID' });
};
document.head.appendChild(s);
</script>
window.Kaleidr
| Member | Description |
|---|---|
Kaleidr.mount(target, options) | Mounts a product into an element and returns a handle. |
Kaleidr.init() | Upgrades <kaleidr-map> elements already in the document. Rarely needed. |
Kaleidr.enable(on) | Switches every later mount on or off. |
Kaleidr.version | The loader's version string. |
Kaleidr.mount(target, options)
const handle = Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map: myMap,
contained: true,
});
| Parameter | Type | Description |
|---|---|---|
target | CSS selector or HTMLElement | The element to mount into. A selector is resolved once, with document.querySelector. |
options | object | Required. See Options. |
Returns a handle.
How it behaves:
- It returns immediately. The session exchange and the product bundle
load in the background. Handle calls you make meanwhile are kept and
applied, in order, when the product is ready. For viewer and tile that is
the moment the map inside the frame reports ready, so a
setCamerawritten on the line aftermounttakes effect. - It does not throw for a failed mount. A missing target, a bundle that
cannot load, a refused session or an invalid id is written to the console
as an error that contains
[kaleidr:<product>]. You still get a handle, and its methods do nothing. The one call that throws is a mount with no options object:Kaleidr.mount('#map')raises aTypeError. - It reports nothing back. There is no promise to await and no callback. See Knowing whether a mount worked.
- One mount per target. Mounting into a target that already holds a
mount is not supported. Call
handle.destroy()on the first one before you mount again. Destroying and then mounting again into the same element is safe at any moment, including while the first mount is still loading: the destroyed mount adds nothing to the element and does not disturb the new one.
Options
| Option | Type | Default | Products | Description |
|---|---|---|---|---|
product | 'chat' | 'viewer' | 'editor' | 'tile' | none, required | all | Which product to mount. |
publishableKey | string | none | chat, editor, tile | Your publishable key, kld_pk_…. The SDK exchanges it for a short-lived session and gives only the session to the product. Do not pass it to a viewer. A server key (kld_sk_…) is refused before any request is made. |
shareId | string | none | viewer, chat | Viewer: the id of the published map. Chat with no key: the published map whose owner provides the chat. |
mapId | string | none | viewer | Alias of shareId, used when shareId is absent. |
styleId | string | the organization's default basemap | tile, editor | A basemap style id such as kaleidr-morning, or a full style URL. |
map | a live map instance | none | chat | The map the assistant should drive: a Mapbox GL JS, MapLibre GL JS, Google Maps or Leaflet map object. See Supported maps. Omit it for a panel with no map. |
mapTarget | CSS selector | none | chat | A <kaleidr-map> viewer or tile element for the assistant to drive, in place of map. |
contained | boolean | false | chat | Docks the panel inside the mount target, which must be positioned and sized. By default the panel is a fixed window at the top-left of the viewport. |
lang | string | the page's <html lang> when that is a well-formed language tag, otherwise English | chat | The language the panel answers in, as a BCP 47 tag such as en, ko or pt-BR. See Chat language. |
title | string | the panel's own title | chat | The title in the panel's header. On the element the attribute is chat-title. |
placeholder | string | the panel's own placeholder | chat | The placeholder text of the panel's input. |
center | [longitude, latitude] | product default | viewer, tile, editor | Opening center. |
zoom | number | product default | viewer, tile, editor | Opening zoom. On viewer and tile it is used only together with center. |
pitch | number | product default | viewer, tile, editor | Opening tilt, in degrees. |
bearing | number | product default | viewer, tile, editor | Opening rotation, in degrees. |
theme | object, or a JSON object string | none | chat, editor | Chat: CSS custom properties, for example { '--highlight-color': '#0b5fff' }. Editor: the 17 theme keys. A string that is not JSON is ignored with a warning. |
enabled | boolean | none | all | Per-mount arm of the enable gate. Read at mount only. |
base | URL | https://cdn.kaleidr.com | all | Where product bundles load from. Only the origin is used: bundles are always requested from /embed/v1/ on that origin. Honored only for the origins listed under The base override; any other value is ignored with a console warning. |
viewerBase | URL | https://map-embed.kaleidr.com | viewer, tile | The origin the iframe loads from. Only the origin is used. |
apiBase | URL | https://api.kaleidr.com/inference-api | chat, editor, tile | Where the session exchange and the product's requests are sent. Must include the /inference-api path. A bare origin returns 404. |
tileBase | URL | https://tile.kaleidr.com | editor | The host that style ids are resolved against. |
Notes:
apiKeyis a deprecated alias ofpublishableKey.- The four
…Baseoptions exist for testing against another deployment. A production page does not need them.basemoves only the bundles: the map page, the API and the basemap host stay on Kaleidr's hosts unless you move each one. lang,titleandplaceholderare read at mount. To change one, destroy the mount and mount again.- An option a product does not use is ignored without a message.
The base override
base (the option and the element attribute) and the page global
window.__KALEIDR_EMBED_BASE__ name the origin product bundles load from. An
override is honored only when its origin is one of these:
| Accepted origin | Example |
|---|---|
| The Kaleidr CDN | https://cdn.kaleidr.com |
The origin kaleidr.js itself was loaded from | You serve kaleidr.js from https://static.example.com, and base is https://static.example.com |
| The page's own origin | The page is https://www.example.com/…, and base is https://www.example.com |
Anything else is ignored. The mount still happens, the bundles load from the default CDN, and the console shows one warning for that value:
[kaleidr] base "https://other.example.net" is IGNORED — bundles load from https://cdn.kaleidr.com. A base must be the Kaleidr CDN, the origin kaleidr.js was loaded from, or this page's own origin.
The page global must be a string. Any other value is skipped without a message. Self-hosting works within this rule when the loader and the bundles are served from the same origin.
Product defaults for the camera:
| Product | Default camera |
|---|---|
| viewer | The view the map's author saved. |
| tile | The style's default view, otherwise the whole world. |
| editor | Center [0, 0], zoom 2, pitch 0, bearing 0. |
| chat | Not applicable. The chat does not create a map. |
Each product's page has the details: Chat, Viewer, Tile, Editor.
The handle
Kaleidr.mount returns the same shape for every product.
interface KaleidrHandle {
readonly product: 'chat' | 'viewer' | 'editor' | 'tile';
setCamera(camera: {
center?: [number, number]; // [longitude, latitude]
zoom?: number;
pitch?: number;
bearing?: number;
duration?: number; // milliseconds
}): void;
setTheme(theme: string | Record<string, string>): void;
setStyle(style: string): void;
destroy(): void;
}
Every method exists on every handle. Whether it does anything depends on the product:
| Method | chat | viewer | editor | tile |
|---|---|---|---|---|
setCamera(camera) | warns | yes | yes | yes |
setTheme(theme) | yes | warns | warns | warns |
setStyle(style) | warns | warns | warns | yes |
destroy() | yes | yes | yes | yes |
"warns" means the call does nothing and the console shows
[kaleidr] setCamera() is not supported on product "chat" — the call is a no-op. See the handle matrix at https://docs.kaleidr.com/sdk/kaleidr-map-element#handle-matrix.
This is part of the /embed/v1 contract: an unsupported method never throws.
Method notes:
setCamera: fields you omit keep their value.durationanimates the move; omitted or0, the map jumps. On chat,setCamerais not supported because the chat does not own a map. The assistant still moves your map when it answers. To move it yourself, use your map library's own API.setTheme: chat only. Takes an object of CSS custom properties or a JSON string of one.setStyle: tile only. Takes a style id or a style URL.destroy: removes what the mount added, stops the session renewals, and drops queued calls. Calling it twice is harmless. A destroyed handle cannot be reused: mount again.
You can call the handle straight after mount. For viewer and tile, a
setCamera (and on tile, a setStyle) made before the map inside the frame
is ready is kept and applied, in order, when the map reports ready: with
its kaleidr-embed:ready message, or with the first camera position it
reports to the SDK. The latest 256 such calls are kept. If the map has
still not reported ready after a bounded wait, normally 10 seconds from the
moment the frame loads, the kept calls are sent anyway, and the console shows
one warning:
[kaleidr] the map frame has not reported ready — calls held for it are sent now, and may reach no map. The frame may be showing an error (an unpublished map, a blocked request).
The wait starts when the frame loads, so a newly mounted frame below the
fold, which has not started loading, keeps its calls until it does. You do not
have to wait for the kaleidr-embed:ready message before calling the handle.
That message, and kaleidr-embed:error, remain the way your code learns that
the map loaded or failed. See Viewer messages.
The handle has no ready promise, no on() method, and no map or iframe
member.
Kaleidr.init()
Upgrades every <kaleidr-map> element that is in the document at the moment
you call it. The loader calls it for you when it runs, and elements added
later mount themselves when they are inserted, so you rarely need it.
Calling it more than once is harmless.
The enable gate
Embeds are on by default. The enable gate is the off switch: while it is
off, Kaleidr.mount loads nothing, sends nothing, and returns a handle whose
methods do nothing. Use it to hold embeds until a visitor consents, or to
switch embeds off on a page without removing the markup.
The three levers
| Lever | Scope | Set it with |
|---|---|---|
| Page flag | Every mount on the page | window.__KALEIDR_EMBED_ENABLED__ = false, assigned before the mounts you want to hold |
Kaleidr.enable() | Every mount after the call | Kaleidr.enable(false) |
| Per mount | One mount | The enabled: false option, or flag="false" on the element |
How a mount decides
Every time something mounts, the SDK reads all three levers:
- If any lever is explicitly
false, the mount is held. - Otherwise, if any lever is explicitly
true, the mount proceeds. - Otherwise the default applies, which is on.
Three consequences follow, and they are the part that is easy to get wrong:
- A
falsecan only be released by the lever that set it.Kaleidr.enable(true)does not liftenabled: falseon a mount, and does not lift a page flag offalse. No lever overrides another. - The gate is checked at mount time only. Releasing a lever does not
mount anything that was held, and switching a lever off does not remove
anything that is already mounted. After you release, mount again: call
Kaleidr.mountagain, or take the element out of the document and put it back. - The page flag is read at every mount, not only when the script loads. You can assign it later and it applies to the next mount.
| To release a hold set by | Do this |
|---|---|
window.__KALEIDR_EMBED_ENABLED__ = false | Assign true to it (or delete it). |
Kaleidr.enable(false) | Call Kaleidr.enable(true), or Kaleidr.enable(null) to return to the default. |
enabled: false or flag="false" | Mount again without it. Changing the flag attribute on a mounted element does nothing. |
When a mount is held, the console shows one warning that names the lever:
[kaleidr] embed is disabled — window.__KALEIDR_EMBED_ENABLED__ is false. Reassigning it releases that lever — Kaleidr.enable() cannot.
Accepted values
The levers are strict about what counts as a boolean, so that a stray string cannot switch embeds on by accident.
| Lever | Counts as true | Counts as false | Ignored, with a console warning |
|---|---|---|---|
window.__KALEIDR_EMBED_ENABLED__ | true | false | Anything that is not a real boolean, including the strings "true" and "false" |
Kaleidr.enable(x) | no argument, true, "true", "1", "yes", "on" | false, 0, "", "false", "0", "no", "off" | Any other string, undefined, objects, functions |
enabled option | true, "true", "1", "yes", "on" | false, 0, "", "false", "0", "no", "off" | Any other string, objects, functions |
flag attribute | flag with no value, "true", "1", "yes", "on" | "false", "0", "no", "off" | Any other value |
Words are matched without regard to case or surrounding spaces. Two details matter when the value comes from storage:
Kaleidr.enable(null)resets the lever to the default. It does not mean "no opinion".localStorage.getItem(key)returnsnullwhen nothing is stored, which is exactly the state before a visitor has consented, so never pass a stored value straight through. Compare it first:Kaleidr.enable(stored === 'true').- An ignored value leaves the lever as it was. It neither enables nor disables.
Example: hold embeds until consent
This page holds every embed with the page flag, which is the lever that works for elements, because elements mount as soon as the loader runs. On consent it releases the same lever and re-inserts the elements so they mount.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Consent-gated map</title>
<script>
// A real boolean, set before the loader. A stored string is compared, never passed through.
window.__KALEIDR_EMBED_ENABLED__ =
localStorage.getItem('embeds-consent') === 'true';
</script>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
</head>
<body>
<div id="banner">
This page shows an interactive map from Kaleidr.
<button id="accept">Show the map</button>
</div>
<kaleidr-map product="viewer" share-id="YOUR_MAP_ID"
style="display:block; height:480px"></kaleidr-map>
<script>
const banner = document.getElementById('banner');
if (window.__KALEIDR_EMBED_ENABLED__) banner.hidden = true;
document.getElementById('accept').addEventListener('click', () => {
localStorage.setItem('embeds-consent', 'true');
banner.hidden = true;
// 1. Release the lever that held the embeds.
window.__KALEIDR_EMBED_ENABLED__ = true;
// 2. A held element does not mount by itself. Re-insert each one.
document.querySelectorAll('kaleidr-map').forEach((el) => {
const parent = el.parentNode;
const next = el.nextSibling;
el.remove();
parent.insertBefore(el, next);
});
});
</script>
</body>
</html>
Before consent, the Network tab shows kaleidr.js and nothing else from
Kaleidr: no product bundle, no session request, no iframe.
If you mount from JavaScript, you do not need the gate at all: do not call
Kaleidr.mount until you have consent. The gate earns its place when embeds
are written as elements, or when a tag manager or another team's code calls
mount and you need one switch for the whole page:
Kaleidr.enable(false); // hold every later mount
// …
Kaleidr.enable(true); // release it
const handle = Kaleidr.mount('#map', { product: 'viewer', shareId: 'YOUR_MAP_ID' });
Page globals
| Global | Type | Read | Purpose |
|---|---|---|---|
window.__KALEIDR_EMBED_ENABLED__ | boolean | At every mount | The page flag of the enable gate. |
window.__KALEIDR_EMBED_BASE__ | URL string | At every mount | The origin product bundles load from, for a self-hosted copy. Only the origin is used, and it must be one the base override accepts. See CDN versioning. |
Knowing whether a mount worked
The SDK gives your code no readiness or failure signal. What you can use:
| Product | Loaded | Failed |
|---|---|---|
| viewer | The iframe posts kaleidr-embed:ready. | The iframe posts kaleidr-embed:error, or the console shows a [kaleidr:viewer] error. |
| tile | The iframe posts kaleidr-embed:ready. | A refused session appears only in the console. A drawing problem posts kaleidr-embed:error. |
| chat | No signal. The panel appears in the page. | Console only. |
| editor | No signal. The editor appears in the page. | Console only. |
The two messages are documented in Viewer messages.
What happens when a mount fails
| Cause | What you see | Request sent? |
|---|---|---|
| The target selector matches nothing | [kaleidr:<product>] mount target not found: <selector> | No |
The element has no valid product | [kaleidr] <kaleidr-map> requires product="chat|viewer|editor|tile" | No |
| A server key was passed | [kaleidr:<product>] Kaleidr: a server key (kld_sk_…) must never be used in the browser — it is a standing secret. Use your publishable key (kld_pk_…) here and keep the server key server-side. | No |
| The key is unknown or revoked, or the page's origin is not allowed | A browser CORS error for the sessions request, then a [kaleidr:<product>] fetch error. Nothing renders. | Yes |
| The key lacks the product's scope, the key is limited to other products, or the plan does not admit it | [kaleidr:<product>] kaleidr session exchange failed (403). Nothing renders. | Yes |
| The organization's map loads are used up (tile), or too many session requests | [kaleidr:<product>] kaleidr session exchange failed (429). Nothing renders. | Yes |
| The product bundle cannot load | [kaleidr:<product>] [kaleidr] failed to load https://cdn.kaleidr.com/embed/v1/<product>.js | n/a |
| Viewer with no id or a malformed id | [kaleidr:viewer] Kaleidr map: invalid id "" — must match ^[A-Za-z0-9._~-]{1,100}$. | No |
| Editor with no key | [kaleidr:editor] [editor-embed] attach() requires an { apiKey } | No |
Chat with neither a key nor a shareId | The panel renders, and every question fails. | Not at mount |
| The enable gate is off | [kaleidr] embed is disabled — … | No |
The SDK never shows the server's reason. For a refused session it logs
only the HTTP status. The reason is in the response body. A 403 or 404
carries its code in the detail field, for example
{"detail":"insufficient_scope"}. A 429 carries its code in the error
field, with a sentence in detail; the exception is
map_load_quota_exceeded, which arrives in detail. Open the Network tab,
select the sessions request and read the response. When the browser reports a CORS
error it may hide the body. In that case repeat the request from a terminal:
curl -i https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessions \
-H "x-api-key: kld_pk_live_…" \
-H "Origin: https://www.example.com" \
-H "content-type: application/json" \
-d '{"product":"chat"}'
HTTP/2 403
content-type: application/json
{"detail":"origin_not_allowed"}
Use the origin of the page that fails. Use "chat", "editor" or "viewer"
as the product when you test: a successful "tile" exchange counts a map
load. Every code is explained in Errors.
A failed first exchange is final for that mount. The SDK does not retry it. Fix the cause and mount again.
The session exchange, in brief
For chat, editor and tile, the SDK never uses your publishable key to call Kaleidr's runtime API. It trades the key for a session first.
- At mount, the SDK sends
POST https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessionswith the headerx-api-key: <your publishable key>and the body{"product":"<product>"}. The browser adds your page'sOrigin. - Kaleidr checks, in order: that the key is a publishable key, that the
request has an origin, that the origin is in the key's allowed origins
(when the key has any), that the key and the plan admit the product, and
that the key carries the product's scope:
aifor chat,designfor editor,mapsfor tile, none for viewer. - The response is
{"session_token": "…", "expires_in": 900, "product": "…", "basemap_style_id": …}.basemap_style_idis your organization's default basemap, ornullwhen none is set. - The product uses the session token for its requests. The token is bound to your origin and to that product.
- The SDK renews the session 90 seconds before it expires, and again when a background tab becomes visible with a session that is about to expire. If a renewal fails, it tries again every 30 seconds while the mount exists.
handle.destroy()stops the renewals.
A viewer needs no session. A chat on a published map, mounted with a
shareId and no key, gets its session from the map instead of from a key.
That flow is described in
Chat on a published map.
The request and response are specified in Endpoints. Key types, sessions and what an allowed-origins list does and does not protect are explained in the Security model.
Other console messages
| Message | Meaning |
|---|---|
[kaleidr] <method>() is not supported on product "<product>" — the call is a no-op. … | You called a handle method that product does not have. |
[kaleidr] <method>() IS supported on product "<product>", but this mount did not expose it, so the call did nothing. … | The product bundle in the browser's cache is older than the loader. Reload without the cache. |
[kaleidr] the map frame has not reported ready — calls held for it are sent now, and may reach no map. … | The map in a Viewer or Tile frame did not report ready within the wait described under the handle. The setCamera and setStyle calls kept for it were sent anyway. The frame may be showing an error: listen for kaleidr-embed:error. |
[kaleidr] theme must be an object of CSS tokens, not an array; ignored. | theme was an array. |
[kaleidr] theme string must be JSON …; named string themes are not supported. Ignored. | theme was a string that does not start with {. |
[kaleidr] theme JSON parse failed (…); ignored. | theme was malformed JSON. |
[kaleidr] <attribute>="<value>" is not a recognized boolean — the attribute is IGNORED. … | The flag or contained attribute had a value outside the accepted words. The message names the attribute: flag="…" or contained="…". |
[kaleidr] base "<value>" is IGNORED — bundles load from <origin>. … | A base option or attribute, or window.__KALEIDR_EMBED_BASE__, named an origin that the base override does not accept. Bundles load from the default CDN. |
[kaleidr] window.__KALEIDR_EMBED_ENABLED__ must be a boolean; got <type> (…). … | The page flag was not a real boolean and was ignored. |
[kaleidr] Kaleidr.enable(): "<value>" is not a recognized boolean — the value is IGNORED. … | Kaleidr.enable received an unusable value. |
[kaleidr] enabled: "<value>" is not a recognized boolean — the value is IGNORED. … | The enabled mount option received an unusable value. |
[kaleidr] Kaleidr.enable(undefined) is IGNORED and leaves the gate exactly as it was. … | Kaleidr.enable was called with undefined. |
[kaleidr] Kaleidr.enable(null) RESET the gate to the build default, releasing the <value> set earlier. … | Kaleidr.enable(null) released an earlier setting. |
Not available
- An npm package, ES module build or type definitions. A declaration you can copy is in Framework integration.
- A native SDK for iOS, Android, React Native or Flutter.
- A
readypromise, events, or callbacks on the handle. - A JavaScript client for calling the API without mounting a product. Call the server API from your backend instead.