Sprucely title background

Integration

You can integrate interactive Sprucely.io dashboards into your own website or web application in three ways: by embedding them with a standard HTML iframe, by rendering them natively with the standalone JavaScript runtime, or by mounting the standalone React components. The iframe is the fastest to set up; the standalone runtimes draw the dashboards directly in your page and let you push in new data at runtime. The JSON shape the standalone runtimes read - dashboards, charts and datasets - is documented in the data format reference. For AI-driven dashboard automation, see the MCP APIs section.

Embedding Dashboards in HTML

The simplest integration uses the standard HTML iframe element and works on any website, or even local HTML pages. The dashboard needs to be shared, either in the Cloud or On-premise, for it to load successfully. Do note that On-premise dashboards will only load when the client accessing it is within the same corporate network.

Instructions:

1) Extract the dashboard embed link - In the Dashboards page, ensure your dashboard is shared and then click the icon for this dashboard. A green popup banner will notify you that the link was copied to the clipboard. You will use this to replace the HTML iframe src contents below.

2) Embed the dashboard with a fixed size

<html>
  <head>
    <title>Sprucely.io Dashboard</title>
  </head>
  <body>
    <h1>Sprucely.io Dashboard</h1>
    <iframe src="https://www.sprucely.io/service/dashboards/embed/[userId]/[dashboardId]/"
            allow="clipboard-write"
            style="height: 500px; width: 700px;"
            title="Sprucely.io Dashboard"></iframe>
  </body>
</html>

(or) Embed the dashboard with dynamic width - Automatically resizes the dashboard based on the parent document’s width

You can use the aspect-ratio style parameter to automatically recalculate the height based on the width.

<html>
  <head>
    <title>Sprucely.io Dashboard</title>
  </head>
  <body>
    <h1>Sprucely.io Dashboard</h1>
    <iframe src="https://www.sprucely.io/service/dashboards/embed/[userId]/[dashboardId]/"
            allow="clipboard-write"
            scrolling="no"
            style="width: 100%; aspect-ratio: 1.5; border: none;"
            title="Sprucely.io Dashboard"></iframe>
  </body>
</html>

3) Other useful embed options

You can add custom style modifications to the style block of the iframe element, to customize the look and feel of the frame. These parameters follow standard HTML CSS guidelines. The example below adds a grey border around the dashboard:

<html>
  <head>
    <title>Sprucely.io Dashboard</title>
  </head>
  <body>
    <h1>Sprucely.io Dashboard</h1>
    <iframe src="https://www.sprucely.io/service/dashboards/embed/[userId]/[dashboardId]/"
            allow="clipboard-write"
            style="height: 100%; width: 100%; border: 2px solid grey;"
            title="Sprucely.io Dashboard"></iframe>
  </body>
</html>

Embedding with Standalone JavaScript

The standalone runtime renders dashboards directly inside your page - no iframe. Your data is loaded into an in-browser database and drawn as fully interactive charts; the runtime only contacts Sprucely.io to validate your access token.

Instructions:

1) Create an access token - In the Access Tokens section of your profile, create an access token for the origin your pages are served from (for example https://www.yourdomain.com). The runtime validates that the embedding page’s origin matches the token before rendering.

2) Load the runtime and render a dashboard - Add the runtime script to your page, connect the in-browser database once with sprucely_db, then render each dashboard with sprucely_create. The complete page below also wires a button that appends more rows:

<html>
  <head>
    <title>Orders Dashboard</title>
  </head>
  <body>
    <div id="database"></div>
    <div id="orders_dashboard"></div>
    <button onclick="addRows()">Add rows</button>

    <script src="https://www.sprucely.io/cross-origin/sprucely-runtime.min.js"></script>
    <script>
      const data = {
        name:    "Orders",
        headers: ["region", "amount", "items"],
        types:   ["VARCHAR", "FLOAT", "INTEGER"],
        entries: [
          ["North", 120.50, 2],
          ["South",  89.95, 1],
          ["North", 432.00, 5],
          ["South",  74.50, 1]
        ]
      };

      const dashboard = {
        config: {
          type: "dashboard",
          style: {
            widget: {
              color: "#667FFF",
              backgroundColor: "#0C0B29",
              primaryColor: "#01EEAE",
              primaryColorSubtle: "#C70584",
              secondaryColor: "#2C2B49"
            }
          }
        },
        data: {
          type: "dash",
          children: [
            {
              type: "dash_stacker_hor",
              children: [
                { type: "dash_chart", datasetId: "Orders", x: "region", chartType: "bar", dataFunction: "count" },
                { type: "dash_chart", datasetId: "Orders", x: "amount", y: "items", chartType: "hexbin", dataFunction: "count" }
              ]
            }
          ]
        }
      };

      sprucely_db({
        id: "database",
        host: "https://www.yourdomain.com",
        accessToken: "YOUR_ACCESS_TOKEN"
      });
      sprucely_create({ id: "orders_dashboard", dashboard, data });

      function addRows() {
        sprucely_add({
          name:    "Orders",
          headers: ["region", "amount", "items"],
          types:   ["VARCHAR", "FLOAT", "INTEGER"],
          entries: [
            ["East", 310.40, 3],
            ["West", 129.95, 1]
          ]
        });
      }
    </script>
  </body>
</html>

3) Append data at runtime - Call sprucely_add with additional rows at any time. Every dashboard that uses the dataset refreshes automatically:

sprucely_add({
  name:    "Orders",
  headers: ["region", "amount", "items"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["East", 310.40, 3],
    ["West", 129.95, 1]
  ]
});

Function reference

  • sprucely_db({ id, host, accessToken }) - connects the in-browser database and validates your access token against the page origin. Renders a status banner into the element identified by id. Call it once per page, before creating dashboards.
  • sprucely_create({ id, dashboard, data }) - renders one dashboard into the element identified by id. The dashboard parameter defines layout and styling; data supplies the dataset with its name, headers, column types (VARCHAR, INTEGER, FLOAT or TIMESTAMP) and rows - see the data format reference.
  • sprucely_add(data) - appends rows to the dataset whose name matches an already loaded dataset, then refreshes all dashboards that use it.

Embedding with Standalone React

If your site is built with React, you can render dashboards as components instead of loading the script manually. The components use the same access tokens, dashboard definitions and dataset format as the standalone JavaScript runtime.

Instructions:

1) Create an access token - As for standalone JavaScript, create an access token for your site’s origin in the Access Tokens section of your profile.

2) Render the dashboard components - Get the runtime from https://www.sprucely.io/cross-origin/sprucely-runtime.min.cjs.js and place it in your project. Mount one Sprucely.Database component per page, and one Sprucely.Dashboard component per dashboard. Dashboards show a loading indicator until the database has validated the access token, then render. The complete application below renders two dashboards from separate datasets and appends new rows to the first after five seconds:

// Get https://www.sprucely.io/cross-origin/sprucely-runtime.min.cjs.js
// and place it in your project, next to this file.

import React, { useEffect } from "react";
import { createRoot } from "react-dom/client";
import Sprucely, { sprucely_add } from "./sprucely-runtime.min.cjs.js";

const style = {
  widget: {
    color: "#667FFF",
    backgroundColor: "#0C0B29",
    primaryColor: "#01EEAE",
    primaryColorSubtle: "#C70584",
    secondaryColor: "#2C2B49"
  }
};

const orders = {
  name:    "Orders",
  headers: ["region", "amount", "items"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["North", 120.50, 2],
    ["South",  89.95, 1],
    ["North", 432.00, 5],
    ["South",  74.50, 1]
  ]
};

const returns = {
  name:    "Returns",
  headers: ["reason", "amount", "days"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["Damaged", 45.00, 3],
    ["Late",    12.50, 8],
    ["Changed", 99.90, 2]
  ]
};

const moreOrders = {
  name:    "Orders",
  headers: ["region", "amount", "items"],
  types:   ["VARCHAR", "FLOAT", "INTEGER"],
  entries: [
    ["East", 310.40, 3],
    ["West", 129.95, 1]
  ]
};

const ordersDashboard = {
  config: { type: "dashboard", style },
  data: {
    type: "dash",
    children: [
      {
        type: "dash_stacker_hor",
        children: [
          { type: "dash_chart", datasetId: "Orders", x: "region", chartType: "bar", dataFunction: "count" },
          { type: "dash_chart", datasetId: "Orders", x: "amount", y: "items", chartType: "hexbin", dataFunction: "count" }
        ]
      }
    ]
  }
};

const returnsDashboard = {
  config: { type: "dashboard", style },
  data: {
    type: "dash",
    children: [
      {
        type: "dash_stacker_hor",
        children: [
          { type: "dash_chart", datasetId: "Returns", x: "amount", y: "days", d: "amount", r: "days", chartType: "dot", dataFunction: "average" }
        ]
      }
    ]
  }
};

const App = () => {
  useEffect(() => {
    const timer = setTimeout(() => { sprucely_add(moreOrders); }, 5000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <>
      <Sprucely.Database host="https://www.yourdomain.com" accessToken="YOUR_ACCESS_TOKEN" />
      <Sprucely.Dashboard id="orders_dashboard" dashboard={ordersDashboard} data={orders} />
      <Sprucely.Dashboard id="returns_dashboard" dashboard={returnsDashboard} data={returns} />
    </>
  );
};

createRoot(document.getElementById("root")).render(<App />);

Serve Short-Term Keys

The access token you create above is long-lived and is meant to stay a server-side secret. If your own backend serves the embedding pages - rather than a page with no backend of its own - you do not need to put that long-lived token into the page at all - your backend can exchange it, server-to-server, for a short-lived (15 minute) render token immediately before serving each page, and only the render token ever reaches the browser. A render token a visitor extracts from the page is only useful for minutes, not indefinitely.

Instructions:

1) Exchange your access token for a render token - from your backend, call POST https://www.sprucely.io/api/auth/render_token with your access token as a bearer credential. The response carries the new token, its bound host, and its lifetime in seconds:

// On your server, immediately before serving each embedding page:
const response = await fetch("https://www.sprucely.io/api/auth/render_token", {
  method:  "POST",
  headers: { Authorization: "Bearer " + process.env.SPRUCELY_ACCESS_TOKEN }
});
const { token, expires_in } = await response.json(); // expires_in: 900 (15 minutes)

// Send only the render token to the browser - never the long-lived access
// token itself:
res.send(`
  <script src="https://www.sprucely.io/cross-origin/sprucely-runtime.min.js"></script>
  <script>
    sprucely_db({
      id: "database",
      host: "https://www.yourdomain.com",
      accessToken: "${token}"
    });
  </script>
`);

2) Serve the render token to the browser - use it exactly like an access token when calling sprucely_db or mounting Sprucely.Database. Repeat the exchange before serving each page (or on a timer if you cache the rendered page), so the browser never receives a token valid for longer than 15 minutes.

Function reference

  • POST /api/auth/render_token - exchanges a valid access token, sent as Authorization: Bearer <token>, for a render token. Returns { token, host, expires_in } with expires_in fixed at 900 seconds. A render token cannot be exchanged for another render token - only a long-lived access token can request one.

Security Settings

If your site enforces a Content Security Policy (CSP), the standalone JavaScript and React integrations above need a few sources allowed before dashboards will load - the runtime loads its script from Sprucely.io, opens an in-browser database backed by WebAssembly, and validates your access token against our API, and each of those needs an explicit CSP allowance. If your site does not use CSP, you can skip this section.

Add the following sources to your existing policy - these are directives to merge in, not a complete policy to replace what you already have:

script-src   'self' https://www.sprucely.io 'wasm-unsafe-eval';
connect-src  'self' https://www.sprucely.io;
worker-src   'self' blob: https://www.sprucely.io;

What each directive is for:

  • script-src - https://www.sprucely.io loads the runtime script. 'wasm-unsafe-eval' is required to compile and run the in-browser database, which is built on WebAssembly.
  • connect-src - https://www.sprucely.io is contacted to validate your access token and to load the database’s WebAssembly and worker files.
  • worker-src - the in-browser database runs its queries on a background thread, created from a blob: URL.

None of the above require 'unsafe-inline' - the Sprucely runtime itself never needs it. If you keep your own dashboard and dataset definitions in an inline <script> tag, as in the standalone JavaScript example above, or your own page uses inline styles, add 'unsafe-inline' to script-src or style-src or consider CSP nonces.