Iframe postMessage API
Once you've embedded a Rill dashboard iframe in your page, the parent page can communicate with the iframe using the postMessage API via a JSON-RPC 2.0-like protocol. This communication happens entirely in the browser; no requests to Rill's servers are involved.
Overview
The iframe exposes two functions, getState and setState, and a notification, stateChanged, that lets the parent page read and update the dashboard's UI state. Communication is bidirectional: the parent sends requests to the iframe, and the iframe sends notifications back to the parent.
Dashboard state is represented using the same query strings you see in Rill Cloud URLs as you browse, for example view=pivot&tr=PT24H&grain=hour.
Wait for the ready notification (see Notifications) before sending requests. The iframe does not process messages before it has fully loaded.
Embedding and Initialization
Embed the iframe in your page:
<iframe id="my-iframe" src="<your rill embed url>" width="600" height="400"></iframe>
Set up message handling and send requests from the parent window:
const iframe = document.getElementById("my-iframe");
window.addEventListener("message", (event) => {
const { id, result, error, method, params } = event.data;
// notifications
if (method === "ready") {
console.log("Iframe is ready");
}
if (method === "stateChanged") {
console.log("State changed to:", params.state);
}
if (method === "navigation") {
console.log("Navigated from:", params.from, "to:", params.to);
// params.from and params.to will be dashboard names or "dashboardListing"
}
if (method === "resized") {
console.log("Iframe resized to:", params.width, "x", params.height);
}
// responses
if (id && result) {
console.log("Response to request:", result);
}
if (id && error) {
console.error("RPC error:", error);
}
});
Supported Methods
These methods are called from the parent and handled by the iframe.
Note: if including an id, the iframe will respond with a matching id. If you do not need a response, you can omit the id property.
setState(state)
Updates the iframe's dashboard's UI state.
iframe.contentWindow.postMessage({
id: 1,
method: "setState",
params: "view=pivot&tr=PT24H&grain=hour",
}, "*");
Parameters:
state(string): A URL query string describing the dashboard view, filters, time range, etc. Uses the same format as the query strings in URLs on Rill Cloud.
Response:
{ "id": 1, "result": true }
getState()
Returns the iframe's dashboard's current UI state.
iframe.contentWindow.postMessage({
id: 2,
method: "getState"
}, "*");
Response:
{ "id": 2, "result": { "state": "view=pivot&tr=PT24H&grain=hour" } }
getThemeMode()
Fetches the current theme mode of the iframe.
iframe.contentWindow.postMessage({
id: 3,
method: "getThemeMode"
}, "*");
Response:
{ "id": 3, "result": {"themeMode": "light"} }
The themeMode value will be one of: "light", "dark", or "system".
setThemeMode(themeMode)
Sets the theme mode inside the iframe.
iframe.contentWindow.postMessage({
id: 4,
method: "setThemeMode",
params: "dark"
}, "*");
Parameters:
themeMode(string): The theme mode to set. Must be one of:"light","dark", or"system".
Response:
{ "id": 4, "result": true }
Error Response (if invalid themeMode):
{
"id": 4,
"error": {
"code": -32603,
"message": "Expected themeMode to be one of \"dark\", \"light\", or \"system\""
}
}
getTheme()
Fetches the current theme name applied to the dashboard.
iframe.contentWindow.postMessage({
id: 5,
method: "getTheme"
}, "*");
Response:
{ "id": 5, "result": {"theme": "my-custom-theme"} }
If no theme is set, the response will be:
{ "id": 5, "result": {"theme": "default"} }
The theme value will be the name of the theme resource, or "default" if no theme is set.
setTheme(theme)
Sets the theme name to apply to the dashboard.
iframe.contentWindow.postMessage({
id: 6,
method: "setTheme",
params: "my-custom-theme"
}, "*");
Parameters:
theme(string | null): The theme name to set. Must be the name of an existing theme resource. To clear the theme and use the default, passnullor"default".
Response:
{ "id": 6, "result": true }
Error Response (if invalid theme):
{
"id": 6,
"error": {
"code": -32603,
"message": "Expected theme to be a string or null"
}
}
The theme name must correspond to an existing theme resource in your Rill project. Setting an invalid theme name will not return an error, but the theme will silently not be applied.
getAiPane()
Fetches the current visibility state of the AI chat pane.
iframe.contentWindow.postMessage({
id: 7,
method: "getAiPane"
}, "*");
Response:
{ "id": 7, "result": {"open": false} }
The open value will be true if the AI pane is currently visible, or false if it is hidden.
Note: The AI pane is only available for Explore dashboards when the chat feature is enabled. If the AI pane is not available, the method will still return the current state (which will typically be false).
setAiPane(open)
Sets the visibility state of the AI chat pane.
iframe.contentWindow.postMessage({
id: 8,
method: "setAiPane",
params: true
}, "*");
Parameters:
open(boolean): Whether to show (true) or hide (false) the AI chat pane.
Response:
{ "id": 8, "result": true }
Error Response (if invalid parameter):
{
"id": 8,
"error": {
"code": -32603,
"message": "Expected open to be a boolean"
}
}
Note: The AI pane is only available for dashboards when the chat feature is enabled. If the AI pane is not available, calling this method will not cause an error, but the pane will not be shown.
Notifications
Notifications are sent from the iframe to the parent window. These do not include an id.
ready()
Fired once when the iframe is initialized and ready to receive messages.
{ "method": "ready" }
stateChanged({ state: string })
Fired whenever the internal state of the iframe changes.
{ "method": "stateChanged", "params": { "state": "<rill state string>" } }
navigation({ from: string, to: string })
Fired whenever a user navigates between dashboards. This event is only emitted when navigation is enabled in the embed configuration.
from: The name of the dashboard the user navigated from, or"dashboardListing"if navigating from the dashboard listing pageto: The name of the dashboard the user navigated to, or"dashboardListing"if navigating to the dashboard listing page
This event fires for all dashboard navigation scenarios:
- Navigating from one explore dashboard to another
- Navigating from one canvas dashboard to another
- Navigating from an explore dashboard to a canvas dashboard (or vice versa)
- Navigating from the dashboard listing page to any dashboard
- Navigating from any dashboard to the dashboard listing page
{ "method": "navigation", "params": { "from": "dashboard-name", "to": "another-dashboard-name" } }
Example when navigating from the listing page:
{ "method": "navigation", "params": { "from": "dashboardListing", "to": "dashboard-name" } }
Example when navigating to the listing page:
{ "method": "navigation", "params": { "from": "dashboard-name", "to": "dashboardListing" } }
resized({ width: number, height: number })
Fired whenever the iframe content is resized.
{ "method": "resized", "params": { "width": 1200, "height": 800 } }
aiPaneChanged({ open: boolean })
Fired whenever the AI pane visibility state changes (opened or closed).
open:trueif the AI pane is now open,falseif it is now closed
This event fires when:
- The AI pane is opened or closed by the user clicking the AI button
- The AI pane state is changed programmatically via the
setAiPane()method - The AI pane state changes for any other reason
{ "method": "aiPaneChanged", "params": { "open": true } }
Example when the AI pane is closed:
{ "method": "aiPaneChanged", "params": { "open": false } }
Note: This notification is only emitted for Explore dashboards when the chat feature is enabled.
Error Handling
All errors follow the JSON-RPC 2.0 structure:
{
"id": 3,
"error": {
"code": -32601,
"message": "Method not found"
}
}
Common Error Codes:
| Code | Message | Description |
|---|---|---|
| -32600 | Invalid Request | Malformed request |
| -32601 | Method Not Found | Unknown method |
| -32602 | Invalid Params | Parameters incorrect |
| -32603 | Internal Error | Unexpected failure |
| -32700 | Parse Error | Malformed JSON |
Full Example
const iframe = document.getElementById("my-iframe");
function sendRequest(method, params) {
const id = Math.random().toString(36).slice(2, 11);
return new Promise((resolve, reject) => {
function handler(event) {
if (event.data?.id === id) {
window.removeEventListener("message", handler);
if (event.data.result !== undefined) resolve(event.data.result);
else reject(event.data.error);
}
}
window.addEventListener("message", handler);
iframe.contentWindow.postMessage({ id, method, params }, "*");
});
}
window.addEventListener("message", async (event) => {
if (event.data?.method === "ready") {
console.log("Iframe ready");
await sendRequest("setState", "view=pivot&tr=PT24H&grain=hour");
const currentState = await sendRequest("getState");
console.log("Current state:", currentState.state);
const currentThemeMode = await sendRequest("getThemeMode");
console.log("Current theme mode:", currentThemeMode.themeMode);
await sendRequest("setThemeMode", "dark");
const currentTheme = await sendRequest("getTheme");
console.log("Current theme:", currentTheme.theme);
await sendRequest("setTheme", "my-custom-theme");
const aiPaneState = await sendRequest("getAiPane");
console.log("AI pane open:", aiPaneState.open);
await sendRequest("setAiPane", true);
}
if (event.data?.method === "stateChanged") {
console.log("State changed:", event.data.params.state);
}
if (event.data?.method === "navigation") {
console.log("Navigated from:", event.data.params.from, "to:", event.data.params.to);
}
if (event.data?.method === "resized") {
console.log("Iframe resized:", event.data.params.width, "x", event.data.params.height);
}
if (event.data?.method === "aiPaneChanged") {
console.log("AI pane changed:", event.data.params.open ? "opened" : "closed");
}
});