Observable Framework
1.12.0 GitHub️ 2.4k

Embedding

In addition to standalone apps, you can use Framework to embed interactive views within other applications. Framework supports multiple approaches to embedding:

Exported modules

Framework allows JavaScript modules to be exported for use in another application. Exported modules are vanilla JavaScript and behave identically in an external web application as on a Framework page. As with local modules, exported modules can load data from a static file or a data loader, import other local modules, and import libraries from npm or JSR.

Exported modules typically define data components: functions that render dynamic content, such as a chart or table, by returning a DOM element. Data components can take options (or “props”), and load any needed data using FileAttachment. For example, the chart.js module below exports a Chart data component that loads a CSV file and renders a responsive scatterplot of global surface temperature.

import * as Plot from "npm:@observablehq/plot";
import {FileAttachment, resize} from "observablehq:stdlib";

export async function Chart() {
  const gistemp = await FileAttachment("./lib/gistemp.csv").csv({typed: true});
  return resize((width) =>
    Plot.plot({
      width,
      y: {grid: true},
      color: {scheme: "burd"},
      marks: [Plot.dot(gistemp, {x: "Date", y: "Anomaly", stroke: "Anomaly"}), Plot.ruleY([0])]
    })
  );
}

Data components benefit from Framework’s baked data architecture for instant loads. File and import resolutions are baked into exported modules at build time. Libraries from npm are self-hosted for stability, security, and performance. And transitive static imports are preloaded to avoid long request chains.

To export a module, declare the module’s path in your config file using the dynamicPaths option. For example, to export the module named chart.js:

export default {
  dynamicPaths: [
    "/chart.js"
  ]
};

Or for parameterized routes, name the module product-[id]/chart.js, then load a list of product identifiers from a database with a SQL query:

import postgres from "postgres";

const sql = postgres(); // Note: uses psql environment variables

export default {
  async *dynamicPaths() {
    for await (const {id} of sql`SELECT id FROM products`.cursor()) {
      yield `/product-${id}/chart.js`;
    }
  }
};

Importing exported modules

An exported module can then be imported into a vanilla web application like so:

<script type="module">

import {Chart} from "https://my-workspace.observablehq.cloud/my-app/chart.js";

document.body.append(await Chart());

</script>

Observable Cloud support for cross-origin resource sharing (CORS) is not yet generally available and is needed for exported modules. If you are interested in beta-testing this feature, please email us. For public apps, you can use a third-party host supporting CORS such as GitHub Pages.

In React, you can do something similar using dynamic import and useEffect and useRef hooks (see this example live on StackBlitz):

import {useEffect, useRef} from "react";

export function EmbedChart() {
  const ref = useRef(null);

  useEffect(() => {
    let parent = ref.current, child;
    import("https://my-workspace.observablehq.cloud/my-app/chart.js")
      .then(({Chart}) => Chart())
      .then((chart) => parent?.append((child = chart)));
    return () => ((parent = null), child?.remove());
  }, []);

  return <div ref={ref} />;
}

Some web tooling such as Vite and Webpack erroneously rewrite external dynamic imports. You may need to include a comment such as import(/* @vite-ignore */ …) or import(/* webpackIgnore: true */ …) to disable this behavior.

Exported files

You can declare specific files to export using the dynamicPaths config option. Exported files are published under a stable URL that can be linked to and loaded from an external application. Exported files can be either static or generated dynamically by data loaders. And you can use parameterized routes.

For example, say you want to chart downloads of open-source libraries you maintain. You could use a data loader to server-side render SVG with Observable Plot. (See Plot’s Getting Started guide.) In your config file, list the charts you want to build:

export default {
  dynamicPaths: [
    "/@observablehq/framework/downloads-dark.svg",
    "/@observablehq/framework/downloads.svg",
    "/@observablehq/plot/downloads-dark.svg",
    "/@observablehq/plot/downloads.svg",
    "/@observablehq/runtime/downloads-dark.svg",
    "/@observablehq/runtime/downloads.svg"
  ]
};

Once your app is deployed, you can then load the generated SVG into another app — or READMEs on GitHub — using the img tag. For example, below is a chart of daily downloads of Observable Framework powered by our open-source analytics.

Daily downloads of Observable Framework
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://observablehq.observablehq.cloud/oss-analytics/@observablehq/framework/downloads-dark.svg">
  <img alt="Daily downloads of Observable Framework" src="https://observablehq.observablehq.cloud/oss-analytics/@observablehq/framework/downloads.svg">
</picture>

Iframe embeds

You can alternatively embed Framework pages using iframes. Pages that are intended to be embedded via iframe typically disable Framework’s built-in user interface using Markdown front matter:

---
sidebar: false
header: false
footer: false
pager: false
---

For the page /chart, you can declare an iframe like so:

<iframe scrolling="no" src="https://my-workspace.observablehq.cloud/my-app/chart"></iframe>

With a little bit of additional JavaScript, you can also implement responsive iframe embeds which resize automatically to fit the content of the page.