Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Static export (no kernel)

manywidgets widgets are ordinary anywidgets in a live Jupyter kernel, but they are authored so they also render in a static MyST site with no kernel — using the myst-anywidget-static-export plugin. manywidgets has no code dependency on the plugin; it just follows the rules the plugin needs.

How it works

  1. Execute your notebook (live kernel) so widget state — including each widget’s _esm JS — is captured into the notebook’s metadata.widgets:

    jupyter nbconvert --to notebook --execute --inplace your_notebook.ipynb
  2. Reference the plugin by release URL in myst.yml:

    project:
      plugins:
        - https://github.com/developmentseed/myst-anywidget-static-export/releases/download/v0.3.0/plugin.mjs
  3. Build the site: myst build --html. The plugin rewrites each widget output into a self-contained anywidget that hydrates in the browser, and lifts jslink/jsdlink links into a page-level registry so links work with no kernel.

The demo notebook and every page under Widgets are built exactly this way — each widget page is a pre-executed notebook, so its example renders a real widget with no kernel.

Making a page render widgets

A page only shows live widgets if it is a pre-executed notebook. A plain Markdown page with a ```python fence shows the code but renders no widget — MyST does not execute Markdown code into widget outputs. So put runnable examples in .ipynb pages (the Widgets pages do this) and pre-execute them before building.

What works statically

What needs a live kernel

The “one listener per trait” rule

The static-export models do not support space-separated event names: model.on("change:a change:b", fn) silently never fires. Every manywidgets widget therefore uses onChanges(model, ["a","b"], fn) from @manywidgets/core, which registers one listener per trait. If you build your own widget, follow the same rule — see Create your own widget.

This is a workaround for a plugin limitation. See docs/upstream/static-export-plugin-notes.md for the upstream fix that would make space-separated names work.