---
title: Display query creation UI in an Inline Frame
sidebar_label: Iframes
---

You can embed Deephaven’s query creation UI in an iframe, allowing you to add interactive console query capabilities to your custom web applications. By using the Deephaven JS API alongside the iframe, you can integrate data exploration features directly into your own apps. This guide outlines the steps required to set up and communicate with the embedded query UI.

1. Load the JS API from the `/irisapi/irisapi.nocache.js` path of the target Deephaven server.
1. Log in to the server using the JS API and get a refresh token.
1. Add an iframe to the page that loads the `/iriside/iframecontent/createworker` path on the target Deephaven server.
1. Handle `postMessage` communication between the current Window and the iframe.

## Example App

The following example app presents a custom login page that allows the user to provide a Deephaven server URL and to log in with a username and password.

![IFrame Login](../../assets/interfaces/iframes/iframe-login.png)

Upon successful login, you will be redirected to a query creation form where you can create an interactive console query.

![IFrame Create Query](../../assets/interfaces/iframes/iframe-create-query.png)

<details>
  <summary>Full Code Example</summary>

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="icon" type="image/png" href="https://deephaven.io/icon.svg" />
    <title>Deephaven Iframe - Create Query</title>
    <style>
      html,
      body {
        position: relative;
        background-color: midnightblue;
        margin: 0;
        height: 100vh;
        font-family: sans-serif;
      }
      body::before {
        position: absolute;
        color: white;
        font-size: 2rem;
        top: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        content: 'Loading...';
        width: 100%;
        height: 100%;
      }
      #loginForm {
        display: grid;
        position: absolute;
        background-color: midnightblue;
        color: white;
        grid-template-rows: auto auto;
        grid-auto-flow: column;
        align-content: center;
        align-items: center;
        justify-content: center;
        gap: 6px;
        height: 100vh;
        width: 100vw;
        img {
          grid-row: span 2;
          height: 100%;
          margin-bottom: -2px;
        }
        input {
          height: 24px;
          &:first-of-type {
            grid-column: span 2;
          }
        }
        button {
          grid-row: span 2;
          height: 66px;
          padding: 0 1rem;
        }
      }
      iframe {
        display: block;
        position: relative;
        width: 100vw;
        height: 100vh;
        border: none;
      }
    </style>
  </head>
  <body>
    <form id="loginForm">
      <img src="https://deephaven.io/icon.svg" />
      <input
        autofocus
        type="text"
        name="serverUrl"
        placeholder="Deephaven Server Url"
        required
      />
      <input type="text" name="username" placeholder="Username" required />
      <input type="password" name="password" placeholder="Password" required />
      <button type="submit">Login</button>
    </form>

    <script type="module">
      let dhServerUrl; // will be set at login
      let baseThemeKey = 'default-dark';
      let refreshToken;

      // Get DOM elements
      const loginForm = document.getElementById('loginForm');

      // Register event listeners
      loginForm.addEventListener('submit', onLogin);
      window.addEventListener('message', onMessage);

      /** Handle login form submission */
      async function onLogin(event) {
        event.preventDefault();

        const formData = new FormData(event.target);
        dhServerUrl = new URL(formData.get('serverUrl'));
        const username = formData.get('username');
        const password = formData.get('password');

        // Disable the login form elements
        Array.from(loginForm.elements).forEach((el) => {
          el.disabled = true;
        });

        await loadJsapi(dhServerUrl);

        refreshToken = await getRefreshToken(
          dhServerUrl.host,
          username,
          password
        );

        loginForm.remove();

        await loadIframe(dhServerUrl);
      }

      /** Handle postMessage events from the child iframe */
      function onMessage({ data, origin, source }) {
        // Ignore messages that are not from our Deephaven server
        if (origin !== dhServerUrl.origin) {
          return;
        }

        switch (data.message) {
          // Auth token request
          case 'io.deephaven.message.IframeContent.authTokenRequest':
            source.postMessage({ id: data.id, payload: refreshToken }, origin);
            return;

          // Settings request
          case 'io.deephaven.message.IframeContent.settingsRequest':
            const newWorkerName = `IframeContentTester - Create Worker - ${crypto.randomUUID()}`;

            source.postMessage(
              {
                id: data.id,
                payload: {
                  newWorkerName,
                  settings: {
                    heapSize: 0.5,
                    language: 'python',
                  },
                  isLegacyWorkerKindSupported: false,
                  showHeader: false,
                },
              },
              origin
            );
            return;

          // This message will be sent whenever the settings change including
          // from the UI, so it's a good place to see how settings correspond
          // to the UI.
          case 'io.deephaven.message.IframeContent.settingsChanged':
            console.log('Settings changed:', data.payload);
            return;

          case 'io.deephaven.message.IframeContent.workerCreated':
            const serial = data.payload;
            console.log('Interactive query created:', serial);
            break;
        }
      }

      /** Dynamically load the jsapi from the Deephaven server */
      async function loadJsapi(dhServerUrl) {
        const { promise, resolve } = Promise.withResolvers();

        const scriptUrl = new URL(
          'irisapi/irisapi.nocache.js',
          dhServerUrl.origin
        );

        const jsApiScript = document.createElement('script');
        jsApiScript.src = scriptUrl;
        jsApiScript.onload = resolve;
        document.body.appendChild(jsApiScript);

        return promise;
      }

      /** Load Deephaven in an iframe */
      async function loadIframe(dhServerUrl) {
        const { promise, resolve } = Promise.withResolvers();

        const iframeUrl = new URL(
          'iriside/iframecontent/createworker',
          dhServerUrl.origin
        );

        const iframe = document.createElement('iframe');
        iframe.src = iframeUrl.href;
        iframe.onload = resolve;
        document.body.prepend(iframe);

        return promise;
      }

      /** Login and get refresh token */
      async function getRefreshToken(host, username, token) {
        const wsUrl = `wss://${host}/socket`;

        const client = new iris.Client(wsUrl);

        // Wait for client to connect
        await new Promise((resolve) =>
          client.addEventListener(iris.Client.EVENT_CONNECT, resolve)
        );

        // Register for refresh token updates before loggin in
        const refreshTokenPromise = new Promise((resolve) => {
          client.addEventListener(
            iris.Client.EVENT_REFRESH_TOKEN_UPDATED,
            (event) => {
              // Destructure the refresh token so that we have a serializable
              // object to send to DH
              const { bytes, expiry } = event.detail;
              resolve({ bytes, expiry });
            }
          );
        });

        await client.login({ username, token, type: 'password' });

        return refreshTokenPromise;
      }
    </script>
  </body>
</html>
```

</details>

### Download the JS API Dynamically

Once you provide the server URL to the app, it will download the JS API by appending a script tag to the DOM and waiting for its `onload` event:

```javascript
/** Dynamically load the JS API from the Deephaven server */
async function loadJsapi(dhServerUrl) {
  const { promise, resolve } = Promise.withResolvers();

  const scriptUrl = new URL("irisapi/irisapi.nocache.js", dhServerUrl.origin);

  const jsApiScript = document.createElement("script");
  jsApiScript.src = scriptUrl;
  jsApiScript.onload = resolve;
  document.body.appendChild(jsApiScript);

  return promise;
}
```

### Login and get a refresh token

Once the JS API has been loaded, you need to get a refresh token that the iframe can use:

1. Create a JS API client.
1. Register to the `EVENT_REFRESH_TOKEN_UPDATED` event.
1. Log in to the client with the username / password provided by the user.
1. Receive the refresh token provided by the next `EVENT_REFRESH_TOKEN_UPDATED` event.

The example app wraps all of this in a Promise that resolves when the token is received:

```javascript
/** Login and get refresh token */
async function getRefreshToken(host, username, token) {
  const wsUrl = `wss://${host}/socket`;

  const client = new iris.Client(wsUrl);

  // Wait for client to connect
  await new Promise((resolve) =>
    client.addEventListener(iris.Client.EVENT_CONNECT, resolve)
  );

  // Register for refresh token updates before logging in
  const refreshTokenPromise = new Promise((resolve) => {
    client.addEventListener(
      iris.Client.EVENT_REFRESH_TOKEN_UPDATED,
      (event) => {
        // Destructure the refresh token so that we have a serializable
        // object to send to DH
        const { bytes, expiry } = event.detail;
        resolve({ bytes, expiry });
      },
    );
  });

  await client.login({ username, token, type: "password" });

  return refreshTokenPromise;
}
```

### Load the iframe

Once a refresh token is received, the app will load an iframe pointing to the Deephaven server's `iriside/iframecontent/createworker` path.

```javascript
/** Load Deephaven in an iframe */
async function loadIframe(dhServerUrl) {
  const { promise, resolve } = Promise.withResolvers();

  const iframeUrl = new URL(
    "iriside/iframecontent/createworker",
    dhServerUrl.origin,
  );

  const iframe = document.createElement("iframe");
  iframe.src = iframeUrl.href;
  iframe.onload = resolve;
  document.body.prepend(iframe);

  return promise;
}
```

### Handle post messages

Once the iframe has loaded, Deephaven will initiate communication with the containing page via `postMessage` APIs:

1. Deephaven will request a refresh token by sending an `io.deephaven.message.IframeContent.authTokenRequest` message to the parent Window.
1. The parent Window will respond by sending a message back to the iframe with the same ID as the request and whose payload contains the refresh token.
1. Deephaven will authenticate with the refresh token. If successful, it will request initialization settings for the UI by sending an `io.deephaven.message.IframeContent.settingsRequest` message.
1. The parent window must respond to the settings request, optionally overriding UI settings.

```javascript
/** Handle postMessage events from the child iframe */
function onMessage({ data, origin, source }) {
  // Ignore messages that are not from our Deephaven server
  if (origin !== dhServerUrl.origin) {
    return;
  }

  switch (data.message) {
    // Auth token request
    case "io.deephaven.message.IframeContent.authTokenRequest":
      source.postMessage({ id: data.id, payload: refreshToken }, origin);
      return;

    // Settings request
    case "io.deephaven.message.IframeContent.settingsRequest":
      const newWorkerName =
        `IframeContentTester - Create Worker - ${crypto.randomUUID()}`;

      source.postMessage(
        {
          id: data.id,
          payload: {
            newWorkerName,
            settings: {
              heapSize: 0.5,
              language: "python",
            },
            isLegacyWorkerKindSupported: false,
            showHeader: false,
          },
        },
        origin,
      );
      return;

    // This message will be sent whenever the settings change including
    // from the UI, so it's a good place to see how settings correspond
    // to the UI.
    case "io.deephaven.message.IframeContent.settingsChanged":
      console.log("Settings changed:", data.payload);
      return;

    case "io.deephaven.message.IframeContent.workerCreated":
      const serial = data.payload;
      console.log("Interactive query created:", serial);
      break;
  }
}
```

Once the entire sequence of messages is successful, you should be presented with a form that can be used to create interactive queries. Select your desired settings and click `Connect`. Once the query is created, Deephaven will send an `io.deephaven.message.IframeContent.workerCreated` message to the parent Window containing the serial ID for the query. The example code will write this to the console.log.

### Settings API

The message passed to the iframe in response to the `io.deephaven.message.IframeContent.settingsRequest` message is required to complete the UI initialization.

```javascript
source.postMessage(
  {
    id: data.id,
    payload: {},
  },
  origin,
);
```

While the minimal required payload is an empty object, you will likely want to configure the UI. The payload supports the following optional settings:

- `newWorkerName` - the name of the interactive query that will be created. Note that this needs to be unique every time it is provided. In the example, it is generated as `IframeContentTester - Create Worker - ${crypto.randomUUID()}`
- `isLegacyWorkerKindSupported` - determines if legacy worker types are included in the Engine dropdown. Defaults to true
- `showHeader` - whether to show the `New Console Connection` header. Defaults to true for legacy reasons, but typically should be set to `false`
- `settings` - can be used to define the default values of form fields in the query creation UI. These correspond to the payload sent by `io.deephaven.message.IframeContent.settingsChanged` messages any time the UI fields are updated by a user.

## Related documentation

- [Display tables in an Inline Frame](/core/docs/how-to-guides/use-iframes/)
