# \[ANN\] Snapshot.jl — interactive Pluto exports without a Julia server

**URL:** <https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358>\
**Category:** Package Announcements\
**Tags:** plotting, web, pluto, wasm\
**Created:** [July 19, 2026, 10:30pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358 "2026-07-19T22:30:12Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 19, 2026, 10:30pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/1 "2026-07-19T22:30:13Z")

</div>

I (alongside AI agents, of course) have been working on **[Snapshot.jl](https://github.com/GroupTherapyOrg/Snapshot.jl)**, a package that exports a Pluto notebook as a static page while keeping supported `@bind` interactions live via wasm.

The interactive cells are compiled to WebAssembly with [WasmTarget.jl](https://github.com/GroupTherapyOrg/WasmTarget.jl). When somebody moves a slider, the affected Julia code runs in their browser. The exported page does not need a Julia server, a kernel, or precomputed slider responses. _Because WasmTarget.jl itself is brittle, Snapshot.jl is of course still brittle for now too. That being said, I am having a ton of fun with this already:_

[**Open the live notebook gallery →**](https://grouptherapyorg.github.io/Snapshot.jl/notebooks/)

 ![The Snapshot.jl documentation homepage and quickstart](https://global.discourse-cdn.com/julialang/original/3X/e/6/e6e7e05867f37ae7f3722360e796aceb06a583da.png)

## A small example

Install Snapshot.jl from the General registry:

```julia
import Pkg
Pkg.add("Snapshot")

using Snapshot

# Recommended: lean Therapy-compatible export
export_notebook("notebook.jl")

# One self-contained interactive HTML file
export_notebook("notebook.jl"; single_file=true)

# Classic full-Pluto export
export_notebook("notebook.jl"; therapy=false)

```

This runs the notebook and writes a lean, static Therapy-format page: cell output rendered to HTML at export time plus a WebAssembly island for each reactive group that compiled and passed verification. Pass `fragment=true` when the same output should also be emitted as an embeddable fragment.

The classic Pluto-style HTML export is also available:

```julia
export_notebook("notebook.jl"; therapy=false)

```

Both outputs are static files and can be served by an ordinary static host. The current lean presentation loads pinned DaisyUI, KaTeX and syntax-highlighting resources from public CDNs, so it is static-hostable but not an offline-contained bundle.

## What happens during export?

Snapshot does four things:

1. Runs the notebook once in Pluto.
2. Uses Pluto’s dependency information to find cells affected by each `@bind` value.
3. Lifts those reactive groups into Julia functions and compiles their typed IR to WasmGC through WasmTarget.jl.
4. Runs the compiled result through the verification runtime and checks it against the real notebook output before including the island.

 ![The Snapshot.jl extraction, compilation, verification, and rendering pipeline](https://global.discourse-cdn.com/julialang/original/3X/a/4/a4dedc67ca250d3a2422ffc9724737db60be07d1.png)

If a group does not compile or does not reproduce the notebook result, it does not quietly ship a different answer. The original output stays on the page and the export explains that the interaction is static. A notebook can therefore be partly interactive without being treated as a failed export.

## Some notebooks

The documentation currently contains 16 notebooks: 14 lightly adapted Pluto featured notebooks and two Snapshot demos. It reports coverage per notebook and links back to the `.jl` source.

 ![The Snapshot.jl featured-notebook gallery with per-notebook interactive-cell counts](https://global.discourse-cdn.com/julialang/original/3X/3/2/32cb114944d51ab511f5e14e4fa98757ad090617.png)

The lean collection shell includes a theme picker rather than a single light/dark switch. The screenshots below use different choices from the same exported pages; switching presentation does not rebuild the notebooks.

In the current gallery build, this chemistry notebook has all three reactive cells compiled, so the titration curve is recalculated and plotted in the browser as the inputs change.

[**Open Simulating titrations →**](https://grouptherapyorg.github.io/Snapshot.jl/notebooks/Titration/)

 ![A chemistry notebook exported by Snapshot.jl](https://global.discourse-cdn.com/julialang/original/3X/c/a/ca6d418581a11daed6ca6a9698094777b75d04d5.png)

The output is still a notebook: Markdown, equations, Julia source, controls and figures remain in document order. The lean export removes the Pluto editor and session state rather than redesigning the notebook as an application.

 ![A live image-filtering notebook with a PlutoUI slider and WasmMakie output](https://global.discourse-cdn.com/julialang/original/3X/2/3/238df523ae5923e248c62500321158e473d60073.png)

The examples include larger reactive graphs too. In the current gallery build, this fractals notebook has 15 of 15 interactive cells compiled.

[**Open Fractals and Fractal Art →**](https://grouptherapyorg.github.io/Snapshot.jl/notebooks/fractals/)

 ![The Fractals and Fractal Art notebook exported as a lean page](https://global.discourse-cdn.com/julialang/original/3X/a/3/a322837d2fcae6801c4eaf06a9429a21134119ec.png)

Canvas output uses [WasmMakie.jl](https://github.com/GroupTherapyOrg/WasmMakie.jl), which is itself still a half baked package. The visible canvas remains mounted while a complete new frame is drawn and presented, so rapid slider changes do not replace the figure with an intermediate blank canvas.

 ![A turtle-art notebook drawing to a browser canvas through WasmMakie](https://global.discourse-cdn.com/julialang/original/3X/7/8/786de1338fddbabf59d2ccb320fc2c29afd63102.png)

Snapshot.jl is an exporter, not a Julia interpreter in the browser. Its interactive coverage is the subset that WasmTarget.jl can compile faithfully from type-inferred Julia IR.

That subset already includes ordinary control flow, closures, arrays, strings, dictionaries, exceptions, a growing part of Base and several numerical packages. It is not all of Julia. Dynamic dispatch, runtime facilities, `ccall`-dependent paths and unsupported library internals can still stop an island from compiling.

This boundary is visible on purpose. The gallery reports how many cells are interactive, and failed groups keep their build-time output. As WasmTarget.jl matures and Snapshot updates its pinned compiler dependency, rebuilding may compile more interactions without changing the notebook format or hosting model.

## Status and trust boundary

- Snapshot.jl is pre-1.0 and available from the General registry with `Pkg.add("Snapshot")`.
- It currently targets Julia 1.12 because it works directly with Julia compiler IR.
- The package is MIT licensed. WasmTarget.jl is Apache-2.0 licensed.
- Exporting **executes the notebook** with the permissions of the Julia process. It is not a sandbox; only export notebooks and environments you trust just like you would with any other Pluto notebook.
- The generated page contains static HTML, JavaScript glue and WebAssembly. It does not contain an always-running Julia process.

The package source, tests and current limitations are in the **[Snapshot.jl repository](https://github.com/GroupTherapyOrg/Snapshot.jl)**. I would be especially interested in notebooks that export correctly but leave an interaction static, because those tend to be useful, concrete WasmTarget test cases.

## One small preview

I am also testing **[snapshot.show](https://snapshot.show)**, a separate hosted workflow that builds Snapshot.jl exports from GitHub repositories. I will write about that service separately after the package has had some time on its own.

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 1:54pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/2 "2026-07-20T13:54:45Z")

</div>

Thanks for putting this package forward. It is extremely interesting to those who use Pluto frequently.

I tested your fractals notebook, and the `export_notebook` function worked very smoothly. However, the fractal plots do not react to the changes in the sliders. The sliders respond to changes, but those changes are not reflected in the plots themselves. I am on a Windows 11 machine and Julia 1.12.6. I tested your original fractal Julia file, and it worked immaculately on the same Julia version and Pluto 1.0.3.

I tested your package on one of my notebooks, and it produced a version that looked OK, but the sliders were not responding to changes, probably because there were Unicode characters in the sliders’ definitions.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 2:57pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/3 "2026-07-20T14:57:49Z")

</div>

Do you know what version of Snapshot.jl you used for this test? Is it v0.2.0?

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 3:01pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/4 "2026-07-20T15:01:34Z")

</div>

Yes, it is Snapshot v0.2.0.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 3:13pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/5 "2026-07-20T15:13:13Z")

</div>

Can you share your entire `Pkg.status()` log. I will try to investigate this! I can reproduce that issue in Snapshot v0.1.1 but so far I am unable in v0.2.0

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 3:25pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/6 "2026-07-20T15:25:55Z")

</div>

I have your Snapshot package installed as the single package in one specific env.

 ![image](https://global.discourse-cdn.com/julialang/original/3X/f/8/f8dd400e969f2f4f77ef3ff969914039029ccf0d.png)

Do you need the entire toml file?

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 3:28pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/7 "2026-07-20T15:28:50Z")

</div>

Oh wait, I have one more question: are you opening the HTML directly from the filesystem by double-clicking it? If so, that could be the issue.

When an export is opened as a `file://` URL, the sliders themselves move, but the browser blocks the adjacent WebAssembly files from loading, which prevents the plots and dependent outputs from updating.

Currently, Snapshot exports must be served as a static directory. On Windows, open a terminal in the output directory and run:

```bash
py -m http.server 8000

```

Then open:

`http://localhost:8000/fractals.html`

No Julia process or application server is required. This small HTTP server only allows the browser to load the neighboring `.wasm` and JSON assets. Could you let me know whether this resolves the issue for both notebooks? If this is the issue, I could see how the documentation/announcement does NOT do a good job describing this

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 3:56pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/8 "2026-07-20T15:56:18Z")

</div>

Yes, I was double-clicking on the HTML file.

I implemented your two-step output directory approach, and the output was exactly the same as before: sliders react to changes, but plots do not react to sliders.

Just one sideline question. If double-clicking on an HTML file will not work directly, how can I post a Pluto notebook exported by Snapshot on a website or on an educational platform (e.g., Moodle)?

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 4:05pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/9 "2026-07-20T16:05:48Z")

</div>

Give me like an hour or two. I will push a fix to Snapshot. I think I see what the issue might be, plus I will answer how to deliver a static site using Snapshot/Therapy for easy hosting

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 5:20pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/10 "2026-07-20T17:20:41Z")

</div>

Another question, is it just the fractals.jl notebook that wont work for you or is it all of the notebooks?

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 5:36pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/11 "2026-07-20T17:36:56Z")

</div>

As far as your notebooks are concerned, I only tested your fractals notebook.

I did it because initially I had tested one of my notebooks, and Snapshot spat out some errors during the compilation process. To check whether the problem was confined to my notebook, I ran your notebook using Pluto 1.0.3, and the output came out immaculate on my machine. When I compiled that output with Snapshot, I got the issues that I have described.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 5:40pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/12 "2026-07-20T17:40:00Z")

</div>

Ok thank you. If you have the time, could you try this out. And if you get any errors during this process could you share them here:

In a fresh terminal, launch Julia and set up your temporary environment:

```julia
import Pkg
Pkg.activate(; temp=true)

Pkg.add(url="https://github.com/GroupTherapyOrg/Snapshot.jl",
        rev="fix/static-preview")
Pkg.add("PlutoUI")

using Snapshot

```

Next, create a minimal Pluto notebook programmatically:

```julia
dir = mktempdir()
notebook = joinpath(dir, "preview_test.jl")

write(notebook, raw"""
### A Pluto.jl notebook ###
# v0.20.28

using Markdown
using InteractiveUtils

# ╔═╡ 11111111-1111-4111-8111-111111111111
using PlutoUI

# ╔═╡ 22222222-2222-4222-8222-222222222222
@bind x Slider(1:10; default=3, show_value=true)

# ╔═╡ 33333333-3333-4333-8333-333333333333
x^2

# ╔═╡ 44444444-4444-4444-8444-444444444444
Markdown.parse("The square of **$(x)** is **$(x^2)**.")

# ╔═╡ Cell order:
# ╠═11111111-1111-4111-8111-111111111111
# ╠═22222222-2222-4222-8222-222222222222
# ╠═33333333-3333-4333-8333-333333333333
# ╟─44444444-4444-4444-8444-444444444444
""")

```

Now, export and preview it:

```julia
html = Snapshot.export_notebook(notebook)
Snapshot.preview(html)

```

Your browser should open automatically. Move the slider and confirm the square updates. _Markdown output should show a compile error flag_. Press `Ctrl-C` in the terminal to stop previewing.

To test the classic Pluto export as well, run:

```julia
classic = Snapshot.export_notebook(
    notebook;
    output_dir=joinpath(dir, "classic"),
    therapy=false,
)

Snapshot.preview(classic; port=8001)

```

If this works you can also try to do the same for the fractals.jl notebook.

```julia
import Downloads

fractals = joinpath(dir, "fractals.jl")

Downloads.download(
    "https://raw.githubusercontent.com/GroupTherapyOrg/Snapshot.jl/main/test/notebooks/featured/fractals.jl",
    fractals,
)

fractals_html = Snapshot.export_notebook(fractals)
Snapshot.preview(fractals_html)

```

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 5:44pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/13 "2026-07-20T17:44:57Z")

</div>

OK. Give me a couple of hours. I am in the middle of another task that I have to finish today.

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 9:59pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/14 "2026-07-20T21:59:29Z")

</div>

I followed your instructions, and the notebook was correctly created. However, the Snapshot HTML export does not work correctly. Please see the attached image.

 ![image](https://global.discourse-cdn.com/julialang/original/3X/4/f/4f288539d467f834a90b0abcbdfa821703b2c2f2.png)

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 20, 2026, 10:18pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/15 "2026-07-20T22:18:06Z")

</div>

Thanks for that – can you also check the fractals example I shared above too? I am on mac so having more windows examples is helpful as that might be potentially different

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 20, 2026, 10:25pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/16 "2026-07-20T22:25:33Z")

</div>

The same occurs in the fractals notebook: sliders respond correctly, the plot is stuck. See image below.

 ![image](https://global.discourse-cdn.com/julialang/original/3X/4/d/4d7168ea7ee37c805a506fba7665647b1788a622.png)

I will guess that Snapshot should work in a Linux machine as well. Right? If so I can test it tomorrow.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 21, 2026, 2:23pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/17 "2026-07-21T14:23:53Z")

</div>

Which browser and exact version are you using? I found a Wasm MIME compatibility issue that can occur with simple Windows/Python servers and I might have a fix. Snapshot also currently requires Chrome/Edge 130+ or Firefox 134+

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 21, 2026, 5:22pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/18 "2026-07-21T17:22:28Z")

</div>

I use several browsers and the issue is the same in all browsers. On a Windows 11 machine, my browsers’ versions are as follows: Firefox v1.53.0, Chrome v1.50.0.7871, Edge v1.50.0.4078.

---

<div class="post-metadata">

**Author:** ![Dale\_James\_Black](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dale_james_black/32/11595_2.png) [@Dale\_James\_Black](https://discourse.julialang.org/u/Dale_James_Black)\
**Post date:** [July 21, 2026, 6:37pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/19 "2026-07-21T18:37:45Z")

</div>

Thanks for helping me test this. Could you please try the updated PR from a fresh environment?

```julia
import Pkg
Pkg.activate(; temp=true)
Pkg.add(
    url="https://github.com/GroupTherapyOrg/Snapshot.jl",
    rev="fix/static-preview",
)

using Snapshot
import Downloads

test_dir = joinpath(homedir(), "SnapshotTest")
mkpath(test_dir)

notebook_path = joinpath(test_dir, "fractals.jl")

Downloads.download(
    "https://raw.githubusercontent.com/GroupTherapyOrg/Snapshot.jl/main/test/notebooks/featured/fractals.jl",
    notebook_path,
)

html_path = Snapshot.export_notebook(
    notebook_path;
    single_file=true,
)

println("Open this file in your browser:")
println(html_path)

```

Then copy the printed path into your browser or double-click fractals.html inside the SnapshotTest folder in your home directory. No local server is needed. Please let me know whether moving the sliders updates the plots on Windows.

---

<div class="post-metadata">

**Author:** ![VivMendes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vivmendes/32/16477_2.png) [@VivMendes](https://discourse.julialang.org/u/VivMendes)\
**Post date:** [July 21, 2026, 7:30pm UTC](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358/20 "2026-07-21T19:30:28Z")

</div>

I ran your code, and it worked very smoothly; no errors. The output came out OK, but the same issue remains: the sliders react to changes, but the plots do not react to changes in the sliders.

[Next page](https://discourse.julialang.org/t/ann-snapshot-jl-interactive-pluto-exports-without-a-julia-server/138358.md?page=2)
