Overview

A Mode report can be embedded into another website, such as a private wiki, blog, or any other website or application with access to the internet. An embed will display the full contents of the underlying report including charts, tables, and other custom visualizations. Any report can be embedded as an a White-Label Embed (WLE).

White-Label Embed

A White-Label Embed (WLE) may be seen by viewers who are not logged in to Mode or members of your organization who do not have access to the underlying report. For a given embed, your host application securely controls who can see what using a signature process (and optionally, using parameters).

A White-Label Embed offers the following:

  • No Mode viewer authentication: With WLEs, the embed host application, instead of Mode, handles any required viewer authentication and programmatically controls access to the embedded reports by generating a unique, rapidly-expiring signed embed URL and (optionally) parameters.
  • No Mode branding: There is no Mode Analytics in the embed border or loading animation.
  • Reduced viewer interactivity: Some interactive features of Mode’s built-in charts and tables are not available, including some drill-down features and chart-specific URLs.
  • Reduced underlying data access: To prevent unintentional leakage of sensitive data, viewers cannot see any of the underlying report’s metadata, code, historical runs, or query results. You may, however, elect to expose the ability for viewers to download a CSV of the embedded report’s results in your host application.
Example implementation

To show a White-Label Embed in action, we built a lightweight analytics portal for a fictional paper company named Parch & Posey. The analytics in the portal are provided by two parameterized and White-Label Embedded Mode reports.

Access the Parch & Posey example portal using any of the following credentials:

Username Password
walmart pw
merck pw
utc pw

When accessed, the portal runs the embedded reports with a parameter value specific to the logged-in customer. This means that the embeds will display data only for the logged-in customer. If a viewer attempts to change the embed URL in any way (including the parameter value) and then access the URL, the signature will no longer be valid and the URL will return an error.

The report shown on the “Overview” tab is built entirely with built-in visualizations. The report on the “All Orders” tab is built with a custom D3 visualization.

Requirements

To embed a report, the following requirements must be met:

  • The Mode organization containing the target report must be on a paid plan.
  • If you plan to implement a White-Label Embed, White-Label Embeds must be included in your organization’s paid plan.
  • The creator of the embedded report must be an active member of the same Mode organization that contains the report.

Implementation steps

Embed a Mode report into a host application by following the steps below.

Create an embed URL

An embed can point to either a standard report URL (which displays results from the most recent report run) or a static report run URL (which displays results from a specific run).

TIP: Static report run URLs always include /runs/[RUN_TOKEN]/ in the URL path.

Obtain the URL that suits your specific case and add /embed to the end of the path, for example:

<!-- Report URL (always shows latest run) -->
https://app.mode.com/[ORG_NAME]/reports/[REPORT_TOKEN]/embed
                                                           ^^^^^^

<!-- Static Report Run URL (always shows a specific run from a point in time) -->
https://app.mode.com/[ORG_NAME]/reports/[REPORT_TOKEN]/runs/[RUN_TOKEN]/embed
                                                                            ^^^^^^

Set refresh frequency

Control how often your embed refreshes by specifying a max_age in the embed URL query string. The max_age specifies the maximum allowable age, in seconds, that a cached report run’s results may be:

  • If the report has never run or the most recent run of the report is older than the specified number of seconds, the report will be fully run when accessed. Otherwise, the most recent report run results from Mode’s servers will be returned.
  • If the URL query string includes values for parameters, Mode will return the most recent cached results using the same set of parameter values as long as those results are not older than the value of max_age. If the report has never been run with the specified parameter values, Mode will run the report using them.

This ensures the embedded report’s data is refreshed as regularly as necessary while allowing you to balance the load the embed places on your database. For example, including max_age=3600 in the URL query string ensures that the underlying report will refresh once the most recent results are more than 3600 seconds (i.e., one hour) old.

TIP: A valid embed URLs must include a max_age. To ensure the embedded report’s data is refreshed every time the embed is accessed, specify max_age=0.

The following example URL will update all queries and visualizations in the target report if it was last run over 3600 seconds (i.e., 1 hour) prior to being accessed:

https://app.mode.com/benn/reports/31baa986cdfd?max_age=3600

Specify parameter values (if required)

A field-value pair must be provided for each report parameter, if any are defined in the underlying report. For example:

<!-- Example embed URL query string with a text parameter -->
.../embed?param_sales_region=Asia
         ^^^^^^^^^^^^^^^^^^^^^^^^

To include one or more values for a multiselect parameter in your report’s URL query string, each individual value must be passed as it’s own key-value pair. For example:

<!-- Example embed URL query string with multiple values for a multiselect parameter -->
.../embed?param_sales_rep[]=Susan&param_sales_rep[]=Joe
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Important notes:

  • Remember to URL encode any special characters in the URL’s query string, including parameter values and the [] in multiselect parameter keys.
  • Parameter keys and values are case sensitive.
  • Parameter keys should be ordered alphabetically in the URL.

Sign the embed URL (WLE only)

To create a signed embed URL, follow the steps below to add three fields to the query string: a timestamp, a signature token access_key, and a unique signature that your host application generates each time the embed is rendered.

Before you begin, ensure your back-end environment has access to the following information:

  • Signature token: Generated by an admin of the organization that contains the target report. Ensure you have both the public access key and private access secret.
  • Embed URL: Includes the full path and query string (from the first two steps of this guide).
  • Dynamic parameter values: All parameter values, including dynamically generated ones from your application’s database, (e.g., customer ID, etc.) must be URL encoded. You’ll need these values to build the request URL (outlined in step 2 below).
  • Current timestamp: The current time, in seconds, in UNIX epoch time.

IMPORTANT: Your host application must create the signature using a process that is not transparent to embed viewers. Exposing your signature token’s access secret in front-end, client-side code will compromise the security of your Mode account.

1. Enable WLE for the report

White-Label Embedding must be explicitly enabled on a report-by-report basis. To enable it:

  1. Open the report that you want to White-Label Embed and click Edit in the header.
  2. Select Embed in the header.
  3. In the pop-up, click the White-Label Embed tab.
  4. Toggle the switch so it says White-Label embedding of this report is ON.

TIP: If you don’t see the White-Label Embed tab, your organization does not have White-Label Embeds enabled. Contact your organization’s Mode admins or Mode support to learn more about enabling White-Label Embeds.

2. Build request URL

To create a valid request URL from an embed URL:

  1. Add the timestamp and access_key fields to the query string, set equal to the current UNIX epoch time and the signature token access key, respectively.
  2. Sort the query string alphabetically by field name.

For example:


<!-- Request URL (used to generate the signature) -->
https://.../embed?access_key=9a51794bgrb3&param_sales_region=North%20America&run=now&timestamp=1532446786
                             ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^           ^^^^^^^^^^
                              Access key            Sorted alphabetically                    UNIX epoch time
3. Generate a signature

To sign your WLE embed URL, you must first construct a request_string. This request_string will be hashed to create the signature.

A request_string consists of a constant (GET,,1B2M2Y8AsgTpgAmY7PhCfg==), your request URL, and the current timestamp, separated by commas. For example:

<!-- Request string structure -->
'GET,,1B2M2Y8AsgTpgAmY7PhCfg==,[YOUR_REQUEST_URL],[YOUR_TIMESTAMP]'

<!-- Example request string -->
'GET,,1B2M2Y8AsgTpgAmY7PhCfg==,https://app.mode.com/octan/reports/0d57a7jr4b03/embed?access_key=9a51794bgrb3&param_sales_region=North%20America&timestamp=1532446786,1532446786'

IMPORTANT: The fields in the request_url query string must be in the order given above (i.e., sorted alphabetically) or the signature will be invalid.

Now, generate a signature by creating a base64-encoded SHA256 hash of the request_string using the signature token access secret as the secret (i.e., hashing key). For example:

import hmac
import hashlib

signature = hmac.new(secret, msg=request_string, digestmod=hashlib.sha256).hexdigest()
const crypto = require('crypto');

var signature = crypto.createHmac('sha256', secret).update(requestString).digest('hex');
require 'openssl'

digest = OpenSSL::Digest.new('sha256')
signature = OpenSSL::HMAC.hexdigest(digest, secret, request_string)
$signature = hash_hmac('sha256', $request_string, $secret, false);
4. Create a signed embed URL

Create a signed embed URL by adding the signature field, set equal to the signature generated in the previous step, to the end of the query string of the request_url. For example:

<!-- Request URL (used to generate the signature) -->
https://app.mode.com/octan/reports/e2e53h45ba18/embed
?access_key=9a51794bgrb3
&param_sales_region=North%20America
&run=now
&timestamp=1532446786

<!-- Signed embed URL (suitable for White-Label Embed) -->
https://app.mode.com/octan/reports/e2e53h45ba18/embed
?access_key=9a51794bgrb3
&param_sales_region=North%20America
&run=now
&timestamp=1532446786
&signature=410b49b1d4042bcb0f72b657598cf41bb4f66005267eaf78e55307e18b293cb1
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                       Unique signature generated at run time

The signed embed URL will now provide access to the specified report. If any part of the URL is changed or if the signature expires (which happens after 10 seconds), your host application must regenerate the signature.

Example code

The following examples show how to assemble a request_string and generate a signed embed URL given a signature token access_key and access_secret, a timestamp, and a request_url with all components of the query string already in alphabetical order:

import md5
import hmac
import base64
import hashlib

def sign_url(url, key, secret, timestamp):
  request_type = 'GET'
  content_type = ''
  content_body = ''
  content_hash = md5.new(content_body).digest()
  content_digest = base64.encodestring(content_hash).strip()

  request_string = ','.join([request_type, content_type, content_digest, url, str(timestamp)])

  signature = hmac.new(secret, msg=request_string, digestmod=hashlib.sha256).hexdigest()

  signed_url = '%s&signature=%s' % (url, signature)
  return signed_url

sign_url(request_url, access_key, access_secret, timestamp)
const crypto = require('crypto');

function signUrl(url, key, secret, timestamp) {
    var requestType = 'GET';
    var contentType = null;
    var contentBody = '';
    var contentDigest = crypto.createHash('md5').update(contentBody).digest().toString('base64');

    var requestString = [requestType, contentType, contentDigest, url, timestamp].join(',');

    var signature = crypto.createHmac('sha256', secret).update(requestString).digest('hex');

    var signedUrl = url + '&signature=' + signature;
    return signedUrl
}

signUrl(request_url, access_key, access_secret, timestamp);
require 'digest'
require 'openssl'

def sign_url(url, key, secret, timestamp)
  request_type = 'GET'
  content_type = nil
  content_body = ''
  content_digest = Digest::MD5.base64digest(request_body)

  request_string = [request_type, content_type, content_digest, url, timestamp].join(',')

  digest = OpenSSL::Digest.new('sha256')
  signature = OpenSSL::HMAC.hexdigest(digest, secret, request_string)

  signed_url = "#{url}&signature=#{signature}"
  signed_url
end

sign_url(request_url, access_key, access_secret, timestamp)
function signUrl($url, $key, $secret, $timestamp) {
    $request_type = 'GET';
    $content_type = '';
    $content_body = '';
    $content_digest = base64_encode(pack('H*', md5($content_body)));

    $request_array = array($request_type, $content_type, $content_digest, $url, $timestamp);
    $request_string = implode(',', $request_array);

    $signature = hash_hmac('sha256', $request_string, $secret, false);

    $signed_url = "{$url}&signature={$signature}";
    return $signed_url;
}

> echo signUrl($request_url, $access_key, $access_secret, $timestamp);

Place the embed URL into an iframe

To finish creating your embed, add an iframe to your host application with the src attribute set equal to the signed embed URL. For example:

<!-- iframe with Signed URL -->
<iframe
  src="https://app.mode.com/[ORG_NAME]/reports/[REPORT_TOKEN]/embed?access_key=[ACCESS_KEY]&max_age=[MAX_AGE]&param_xyz=123&timestampt=[TIME_STAMP]&signature=[SIGNATURE]"
  width="100%"
  height="300"
  frameborder="0"
</iframe>

Remember to adjust the height attribute so that the embed results are visible. You can also use Mode’s JavaScript embed helper library to dynamically set the height.

If you are implementing a White-Label Embed, your back-end code will need to update the src attribute of the iframe with the dynamically-generated signed embed URL whenever the page loads or whenever the embed needs to be reloaded.

Testing and troubleshooting

Most issues with embeds stem from problems with generating the embed URL signature or other parts of the embed URL. Mode offers a number of resources to help you test and troubleshoot White-Label Embeds:

Common techniques

Parameter forms

The automatically-generated form at the top of a report containing parameters will not render in an embed. However, you can add interactive HTML elements to your host application (e.g., buttons, drop-down menus, etc.) that viewers can manipulate, and then use JavaScript to update the parameter values in the embed URL, re-sign, and subsequently refresh the embed.

An example of this can be seen in the Parch & Posey example portal.

CSV Export

When a White-Label Embed loads successfully, it will post a JSON response. That response contains URLs that can be used to obtain a CSV of the data that was returned by the embedded report. Only the results returned by the database to the embed, not the underlying data, are included.

In your application, you can add an event listener to retrieve these URLs and expose them using any element on the page.

For example, the following JavaScript listens for the embed response from Mode, retrieves the CSV download URLs, and assigns them as the targets of a couple of corresponding <a> elements:

<a id="csv-export-link" href=""> CSV Export </a>

<script>
  window.addEventListener('message', function (e) {
    // always check the origin and make sure it is from app.mode.com
    if (e.origin === 'https://app.mode.com') {
      if (e.data['type'] == 'reportExportPaths') {
        const modeBaseUrl = e.origin

        // CSV Export
        const csvExportUrl = e.data['report_csv_export_path']
        csvExportLink = document.getElementById('csv-export-link')
        csvExportLink.href = modeBaseUrl + csvExportUrl
      }
    }
  })
</script>

Unique CSV download URLs are generated every time a White-Label Embed renders and they expire after 24 hours. If the White-Label Embed fails to load for any reason, the embed will not post a response containing these URLs.

PDF Export

Unlike CSV Export where a click on the endpoint url itself would invoke a direct download, PDF Export requires a four-part process as described here in the Mode API cookbook.

  • Get Report Latest Run
  • Get status of PDF Generation
  • Post Request to start PDF Generation
  • Get request to Download PDF

We recommend that the four-part PDF requests be handled on the host application server side so that api keys and tokens will not be exposed on the client side.

Here below is a Javascript example code snippet on server side:

<script>
  app.get("/pdf", (req, res) => {
    if (fs.existsSync("./mode-report.pdf")) {
      return res.status(200).download(path.join(__dirname, "/mode-report.pdf"));
    } else {
      request(
        {
          url: `${host}/api/${org}/reports/${report_token}/runs`,
          json: true,
          auth: { username, password }
        },
        (err, res, body) => {
          let {
            state,
            token: mostRecentReportRunToken
          } = body._embedded.report_runs[0];

          if (state === "succeeded") {
            state = "";
            setTimeout(() => {
              const timeout = Date.now() + 60 * 5; // close the call after 5 min
              setInterval(() => {
                if (state === "" || state === "enqueued") {
                  request(
                    {
                      url: `${host}/api/${org}/reports/${report_token}/exports/runs/${mostRecentReportRunToken}/pdf`,
                      method: "POST",
                      auth: { username, password },
                      json: true,
                      body: { trk_source: "report" }
                    },
                    (err, res, body) => {
                      const fileName = body.filename;
                      state = body.state;
                      if (state === "enqueued") {
                        console.log("Download faild :( Please try again.");
                      } else if (state === "completed") {
                        request({
                          url: `${host}/api/${org}/reports/${report_token}/exports/runs/${mostRecentReportRunToken}/pdf/download`,
                          auth: { username, password },
                          encoding: null
                        })
                          .on("error", err =>
                            console.error(
                              "Report's latest run failed. Please fix the queries errors before exporting again."
                            )
                          )
                          .pipe(fs.createWriteStream("mode-report.pdf"));
                      }
                    }
                  );

                  if (Date.now() >= timeout) return;
                }
              }, 1000);
            }, 5000);
          }
          return;
        }
      );
    }
  });

  app.get("/pdfstatus", (req, res) => {
    if (fs.existsSync("./mode-report.pdf")) {
      return res.status(200).json("completed");
    } else {
      return res.status(200).json("incompleted");
    }
  });
</script>

On the client side, you can add an <a> tag to invoke a request to the host application server side to initiate the PDF export process. Please note that generating PDF will take a while, in order to provide a better user experience, you can add an event listener (“click”) to check PDF status which serves as a PDF generating progress indicator to end users.

Here below is a Javascript example code snippet on client side:

<a href="{base_url}/pdf" id="pdfExport">PDF Export</a>

<script>
  const pdfExport = document.getElementById("pdfExport");
  pdfExport.addEventListener("click", () => {
      pdfExport.innerText = "Generating PDF...";
      const timer = setInterval(() => {
          fetch("{base_url}/pdfstatus")
          .then(res => res.json())
          .then(data => {
              if (data === "completed") {
              pdfExport.innerHTML = "Download PDF";
              pdfExport.style.backgroundColor = "lightblue";
              clearInterval(timer);
              }
          })
          .catch(err => {
              console.log(err);
          });
      }, 1000);
  });
</script>

Filter panel toggle

For White-Label Embeds, you can programmatically expose or hide the filter panel (if applicable) by posting a message to the embed iframe using JavaScript’s Window.postMessage() function.

The following example toggles the panel (i.e., it opens the panel if it is closed, and closes the panel if it is open):

// if you have multiple iframes, ensure you're referencing the correct one:
const iframe = window.document.querySelector('iframe');

iframe.contentWindow.postMessage({type: 'reportFilterPanelDisplay', togglePanel: true} ,'*');

The postMessage call supports the following three options:

  • Override the panel to be in the “open” state: {type: 'reportFilterPanelDisplay', showPanel: true}
  • Override the panel to be in the “closed” state: {type: 'reportFilterPanelDisplay', showPanel: false}
  • Toggle the panel state:
    {type: 'reportFilterPanelDisplay', togglePanel: true}

You can set up a “Toggle Filters” button by adding the following code to your page:

<script type="text/javascript">
  function toggleFilterPanel() {
    const message = {type: 'reportFilterPanelDisplay', togglePanel: true};
    const iframe = window.document.querySelector('iframe');
    iframe.contentWindow.postMessage(message, '*');
  };
</script>
<button onclick="toggleFilterPanel()">Toggle Filters</button>

Styling and branding

You can use a custom theme or CSS to style an embedded report so its look and feel matches that of its host application.

Learn more about how to style reports using custom CSS.

Hide a report element

You can instruct an embed to hide individual report elements by adding the embed-hidden class to it using the HTML editor. The following example is the HTML for a report with two side-by-side charts:

<div class="mode-grid container">
  <div class="row">
    <div class="col-md-6">
      <!--This chart will be visible if the report is accessed in Mode and when rendered in an embed-->
      <mode-chart id="chart_c5eff481380d" dataset="dataset" options="chart_options"></mode-chart>
    </div>
    <div class="col-md-6">
      <!--This chart will be visible if the report is accessed in Mode, but NOT when rendered in an embed-->
      <mode-chart id="chart_0d486a3942b3" class="embed-hidden" dataset="dataset" options="chart_options"></mode-chart>
    </div>
  </div>
</div>

The embed-hidden class is automatically added to the <div> that contains the report’s name and description. Accordingly, these are not rendered by default in an embed.

IMPORTANT: Attaching the embed-hidden class to an element hides it when an embed renders but does not prevent that element’s contents from being transmitted to the host application page. These contents will still be accessible if the host application viewer inspects the page.

Embedding reports built using the Mode Public Warehouse

Any Mode user can create an embed from a report built with data from the Mode Public Warehouse, even if the report is in an organization that is not on a paid Mode plan. However, the following limitations apply:

  • The underlying report must be in a community Space and therefore may only use data contained in the Mode Public Warehouse.
  • Parameter values cannot be set using the embed URL’s query string.
  • The run=now and max_age fields cannot be used in the embed URL’s query string. In other words, you cannot set these embeds to run upon view nor to refresh when they reach a certain age.

Anyone can view an embedded report built using Mode Public Warehouse data when it is in a community Space, even if they are not logged in to Mode or do not have a Mode account.

Last updated August 27, 2020