Use a script offline in Confluence from Atlassian

Hi, quite a noob here…
I’m trying to get an observablehq generated script into Confluence using the notebook-runtime, but I’m not able to get it into the main content part of confluence (a div with id=“main content”. All the notebooks I fork have this part in it: const svg =, height))
I should probably change that in some way, but I can’t figure out how.

Any help is appreciated!

Rather than edit code from a notebook to try to render a particular cell into your div, I think it may be easier to use the runtime itself to place the cell of interest into that div. (It is often possible (particularly for simple, single-cell visualization notebooks) to rewrite code from Observable into standalone JS scripts (see e.g. these two posts), but this is generally a lot more work).

I don’t know anything about Confluence, but from my very brief googling it seems like including the following example code from the runtime docs in a “HTML macro” might work:

<script type="module">
import {Runtime, Inspector} from ""; 
// replace the following URL with the v3 URL for the notebook you want to embed (see below)
import define from "";
const runtime = new Runtime(); 
const main = runtime.module(define, name => { 
  // replace "hello" with the name of the cell you want to display
  if (name === "hello") {
    // replace "#main" with an appropriate selector for your div
    return new Inspector(document.querySelector("#main"));

I’ve added a few comments there to mark the places where you will likely have to make edits.

To get the v3 URL for a notebook, click on the three dots at the top right and open the menu. Click “Download code”, and take the URL of the resulting JS file and add ?v=3 to the end. (Note that the Download code link will only appear for shared and public notebooks.)

If the cell in the notebook you’re trying to embed isn’t already named (e.g. its code starts with { and not someName =), then you’ll have to give it a name before you can use the above.

For general testing purposes, I’d actually suggest you start with this code snippet from the runtime docs which renders an entire notebook into an HTML element using Inspector.into:

<script type="module">

import {Runtime, Inspector} from "";
// replace the following URL with the v3 URL for the notebook you want to embed
import define from "";

const runtime = new Runtime();
const main = runtime.module(define, 
  // replace "#main" with an appropriate selector for your div


If you can get this to work in your Confluence setup then getting the more specific embed code to work shouldn’t be too bad.

Good luck and feel free to ask if you get stuck!


Is there a typo here? I think id attributes should not contain spaces.

Awesome, thanks for the quick response. Using your example I got it to work using a html macro with the v3 URL!
The only thing I wasn’t able to achieve yet is that ideally I would like to use the .js file locally (as an attachment on the confluence page), because I also want to keep the data locally. I tried to replace

with the location on confluence, but then there (of course) is no v3 URL…

I also tried to use the online api version of the .js and get the data (in a d3.csv call) from the local location, which (also of course) also did not work…

The goal is to get it running with the data remaining staying inside our company’s network…

Yes, a typo :slight_smile: should have been main-content…

It should be possible to do something like this - did you get an error message in your browser’s console and if so, what did it say?

What URL did you put inside d3.csv? I’m not at all familiar with how data is stored / accessed via Confluence.

It seems that confluence treats the file as an attachment, I do have an URL for it, but if I paste it in a browser, it immediately wants to save the .csv file. I also tried to put the csv on other locations that are blocked from outside, but now I’m not able to republish the page on observable (it keeps saying You have 7 unsaved changes*, which will be saved when the server reconnects.* and in observable it then says TypeError: Failed to fetch, and in the console it says: from origin … has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

This should be a temporary error. If you refresh the page, it should (eventually) go away.

You won’t be able to read your data file on Confluent into a notebook running from without enabling CORS on your Confluent server (possibly relevant link, see “Allow incoming”) or using a CORS proxy like

However, it should be possible for a copy of the notebook embedded in an HTML macro on the same Confluent server to fetch that data file. When you’re able to republish your Observable notebook again, try the following:

  • First edit the notebook so that it fetches from the URL to your data file on the Confluent page (note that the notebook won’t work on, but it will hopefully work after embedding in Confluent)
  • Republish / share your notebook and copy its v3 link (if you’re using the same notebook that you were testing before, this should be the same URL)
  • Try re-embedding the notebook in an HTML macro in a Confluent page on your server (you may need to force refresh your browser in case the old copy of the notebook script is cached.)

Let me know if you still get errors in the last step.

It worked!!!
Thank you so much for helping out, very much appreciated! Now I can experiment with more of all those beautiful things that people create here!

1 Like