Skip to main content

Authoring HTML

warning

Experimental. This feature may change or be removed.

Your shell is a normal HTML document loaded in an iframe. The host is Admin for you and TagoRUN for Run users, and it talks to the shell with window.postMessage. The same contract applies to both. There is no custom-widget library and no requirement to host the page yourself.

To run saved TagoSQL queries or call Analysis Endpoints, use the SQL and Endpoint Bridge. To stream device and entity writes into the page, see Live Data. This page covers ready, theme, and style.

Contract​

1. Shell signals ready​

When your page has registered message listeners and is ready:

parent.postMessage({ type: "dashboard:ready" }, "*");

Post ready after you attach the message listener so the first host reply is not dropped.

2. Host sends theme and optional style​

The host listens for dashboard:ready from your iframe, then posts:

Message typePayloadPurpose
dashboard:theme{ theme }"dark" or "light" to match the host.
dashboard:style{ style }Optional host-driven style object. May be empty.

Theme may be sent again when the host color scheme changes, without a full shell reload.

3. Data over request/response​

The shell initiates requests. Post dashboard:request for sql.list, sql.run, endpoint.list, endpoint.call, stream.open, or stream.close; the host answers with dashboard:response and the same id. Full shapes, errors, and examples for the SQL and Endpoint operations: SQL and Endpoint Bridge. The stream operations are documented on Live Data.

4. Host pushes stream events​

After stream.open, the host posts dashboard:event messages for that stream until it is closed. The shell does not request them one by one. Shape and handling: Live Data.

Minimal example​

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Custom Dashboard</title>
<style>
:root {
color-scheme: light dark;
}
body {
font-family: system-ui, sans-serif;
margin: 1.5rem;
}
</style>
</head>
<body>
<h1>Custom Dashboard</h1>
<p id="theme">theme: ...</p>
<script>
const themeEl = document.getElementById("theme");

window.addEventListener("message", (event) => {
// Prefer checking event.origin against the Admin and TagoRUN origins in production
// (the parent that posts theme/style), not the API host of this HTML.
const msg = event.data;
if (!msg || typeof msg !== "object") return;

if (msg.type === "dashboard:theme") {
document.documentElement.dataset.theme = msg.theme; // "dark" | "light"
themeEl.textContent = "theme: " + msg.theme;
}

if (msg.type === "dashboard:style") {
Object.assign(document.body.style, msg.style ?? {});
}
});

// Signal ready so the host can send theme (and optional style)
parent.postMessage({ type: "dashboard:ready" }, "*");
</script>
</body>
</html>

This matches the snippet shown on the empty state in Admin. For fetching data, add the request helper from SQL and Endpoint Bridge.

Origin checks​

There are two directions. Do not mix them up:

Who listensWhat event.origin isWhat to allowlist
Your shell (message in the iframe)The host parent window that posts theme, style, and responsesAdmin origins for your region (for example https://admin.tago.io) and your TagoRUN domain (for example https://mycompany.run.tago.io or your custom domain), not the API host
Host (message on the parent)Your iframe document originThe API host that serves GET /dashboard/{id}/html

The host posts into the iframe with the API origin as the postMessage target so the browser only delivers to that document. The sender origin on those messages is still the host page. If your shell allowlists only the API host, every legitimate dashboard:theme, dashboard:style, dashboard:response, and dashboard:event message is dropped. If it allowlists only Admin, the page loads in Admin but gets no messages in TagoRUN.

Ignore messages from origins you do not expect.

What not to do​

  • Do not expect the custom-widget SDK handshake. Live data comes through stream.open on the bridge, not through custom-widget realtime variables. That is a different product surface; see Custom Dashboard vs Custom Widget.
  • Do not embed profile tokens in the HTML file. Data comes through the postMessage request/response bridge you initiate; the host uses the viewer's own session (yours in Admin, the Run user's in TagoRUN).
  • Do not rely on an in-app editor: download, edit, upload (HTML shell).

Sandbox​

Admin and TagoRUN load the shell in an iframe with a restricted sandbox (allow-scripts and allow-same-origin in the current host). Design for that environment: no parent DOM access beyond postMessage, and careful use of storage and navigation APIs.