WebToe A web-native dataflow engine for real-time visuals.
Operators, wires and a live cook loop, TouchDesigner-style, in a browser tab. TOPs run as GPU passes, CHOPs drive parameters through expressions, SOPs and MATs feed a 3D renderer. Drop in a .toe and your network opens.
MIT · zero runtime dependencies · WebGL2 + WebGPU · 260 KB of JavaScript
- wire output into input
- dashed a CHOP channel read by an expression
- ƒ expression-driven parameter
- ● display flag
In the editor
-
3D pipeline
Example 10: SOP ribbons and instanced spheres in Geometry COMPs, a camera, two lights, a Render TOP and a glow chain. The output is painted behind the network, as in TouchDesigner.
-
Every family at once
Example 09: 27 nodes, a kaleidoscope COMP with in/out tunnels, and a CHOP rig driving the whole patch through live expressions.
-
Feedback
A feedback TOP hands back the previous frame; a fading level turns mouse movement into trails.
-
CHOP scope
Select a CHOP and the viewer scopes its channels, here a raw sum against its lagged copy.
-
Create operator
Tab or double-click opens the palette: a tab per family, one search across all of them.
-
WebGPU
The same project on
?backend=webgpu. Every 2D TOP ships hand-written GLSL and WGSL.
01 · Import
Drop a .toe. It opens.
Drag a TouchDesigner project onto the editor and it becomes a live WebToe network. Supported operators run; everything else is kept as a stub with its name, wires, parameters and code, and the import report says exactly how much of each.
Decoded in the browser
WebToe reads the binary .toe / .tox container itself: no TouchDesigner install, no bridge, and the file never leaves your machine.
- to decode a 20 MB show file in the browser (33,923 files)
- 0.35 s
- production project files decoded byte-exact against TouchDesigner’s own
toeexpand - 125 / 125
Native .toe decoding is provided for research purposes only.
The bridge, as fallback
The local bridge runs the toeexpand of your own TouchDesigner install on 127.0.0.1 and hands the expansion to the page. One command, or bridge.py run with the Python that ships inside TouchDesigner.
npx webtoe # → http://127.0.0.1:9881/app/Binds to 127.0.0.1 by default, ships nothing of Derivative’s, and deletes every upload when it is done.
Measured on a real show file
A 20 MB production project dropped on the page and decoded natively, in Chrome on a MacBook Pro (M4 Max), 2026-10-04. The bridge path took 8.0 s on a comparable file in August.
- nodes imported
- 14,656
- runnable (10,343 nodes)
- 71%
- Python expressions translated
- 2,246
- files decoded from the container
- 33,923
- from drop to a running graph
- 0.54 s

Recovered: node types and hierarchy, wires across COMP boundaries and through in/out tunnels, parameter values and modes, DAT text and tables, network layout, and live Python expressions, translated where faithful (absTime.seconds*0.2 → time.seconds*0.2) and kept inert otherwise. Imported Python never runs.
02 · Dataflow
Operators, wires, expressions, one cook loop.
A network is operators wired together. Each frame the engine pulls from the output, cooks what it depends on once, and memoizes the result for that frame. Feedback is the one deliberate cycle: it reads the previous frame.
-
TOP28
GPU image operators: generators, filters, compositing, feedback, lookup, render, media and NDI in/out.
-
CHOP14
Channels: LFO, noise, math, lag, speed, mouse in, SOP to. The values that drive parameters.
-
SOP21
Typed-array geometry: primitives, copy, skin, noise, twist, and in/out for COMP networks.
-
MAT7
Materials for the scene renderer: constant, lit (phong and pbr), line, point sprite, wireframe.
-
COMP5
Containers with in/out tunnels, and the 3D objects: geometry (with instancing), camera, lights.
-
DAT6
Text and tables that operators read: a ramp can take its colour keys from a table.
81 operator types, counted from the engine’s registry at build time. All operators →
Every parameter can be an expression
Expressions are JavaScript evaluated against a fixed scope: time, me, op(), parent(), ext() and a math library (sin, clamp, fract, lerp, rand …). They can read channels and other parameters, live, with a cycle guard.
op('lfo1')['chan1'] // a CHOP channel, by name
parent().par.speed * 0.5 // a parameter up the hierarchy
time.seconds * 0.2 // engine time
ext('energy', 0.5) // a value the host page sends in03 · Parity
Measured against real TouchDesigner work.
“Complete” is defined by data, not by a feature list: 60 real TouchDesigner projects from a daily generative-art practice (28,698 nodes, 2022–2026), expanded with the official toeexpand and counted by operator type. Only aggregate numbers are published.
Each step was re-measured on the same 28,698 nodes (docs/ROADMAP.md). The rest import as stubs: structure, wires, layout and code survive.
- runnable nodes in a 213-node reference project, across the same three steps
- 56 → 71 → 88
- operators in the official TouchDesigner inventory, across 7 families
- ~675
- operator types WebToe ships today
- 81
- runnable in a 2025 POP-heavy project once POPs mapped onto the SOP implementations
- 43% → 73%
Next, in order
- Multi-sample CHOPs and time slicing, the prerequisite for audio, trail, resample and wave.
- GLSL TOP: TouchDesigner’s injected shader contract, shimmed onto WebGL2.
- POPs on WebGPU compute, the reason the second backend exists.
Python is a permanent boundary. Imported Python is kept and shown, never executed; the planned answer is an opt-in JavaScript callback DAT.
04 · Engine
Small, explicit, no dependencies.
An original engine written for the web, not a port. It implements the workflow (operator families, wired networks, expression-driven parameters, a live cook loop) on browser GPUs.
- Zero runtime dependencies
- The app is engine, editor and shaders, nothing else: 260 KB of JavaScript, 76 KB gzipped.
- Two GPU backends
- WebGL2 by default, WebGPU with
?backend=webgpu, both behind one backend-agnostic pass contract. 2D is at parity; the 3D scene pass is WebGL2 today. - One dependency rule
- Imports flow downward only, and
coreimports nothing. Operators register through the publicregisterOpAPI. - Embeddable
mountEditor(el, options)puts the framework-free editor in any page. Hosts load projects and drive expressions overpostMessage.- A versioned file format
.webtoe.jsoncarries a version and a migration chain; unknown operator types degrade to family stubs instead of failing.- WASM where it pays
- The NDI pixel conversion runs in a 1 KB WebAssembly kernel with a JavaScript fallback. WASM is adopted only on a measured win.
apps/web ──▶ @webtoe/editor ──▶ @webtoe/ops ──┐
│ │ ├──▶ @webtoe/core
├──▶ @webtoe/gpu ───────┤
└──▶ @webtoe/io ────────┘
05 · Engine ↔ show
WebToe is the engine. open-audiovisual is the show.
open-audiovisual is the sister project: a web-native framework for audiovisual performance, with MIDI, chord and pose inputs, a signal-to-parameter mapping layer, a timeline with scenes and cues, and a backstage monitor.
Its @openav/world-webtoe adapter embeds this editor in an iframe, reads the show’s resolved parameters every frame and posts the ones that changed into the patch. Inside the network they are plain numbers: ext('energy') in any expression.
// the host page drives the patch; expressions read ext('energy')
frame.contentWindow.postMessage(
{ type: 'webtoe:ext', values: { energy: 0.8 } }, '*');
// swap the project
frame.contentWindow.postMessage(
{ type: 'webtoe:load', url: 'https://example.com/patch.webtoe.json' }, '*');06 · Start
Three ways in.
-
In the browser
Nothing to install. Open the editor, load one of the ten examples from the toolbar, or drop a project file on the page.
-
On your machine
Serves the editor and the import bridge on 127.0.0.1 and opens it. Needs Node 20 or newer.
npx webtoe # → http://127.0.0.1:9881/app/ -
From source
The monorepo: engine, operators, both GPU backends, editor, importer, bridges.
npm run checkruns the type check and the test suite.git clone https://github.com/frank890417/WebToe.git cd WebToe && npm install npm run dev # → http://localhost:8643/app/
Documentation
- Getting startedOpen the editor in a browser, run WebToe locally with npx webtoe, or set up the monorepo for development.
- ExamplesThe ten projects bundled with the editor, what each one shows, and a link that opens it.
- Importing TouchDesigner projectsDrop a .toe or .tox on the editor. What comes across, what becomes a stub, how expressions are translated, and the bridge and toeexpand paths.
- The .toe formatHow WebToe reads TouchDesigner's binary project container in the browser.
- OperatorsEvery operator WebToe ships, by family, generated from the engine's registry, plus how cooking, stubs and custom operators work.
- ExpressionsDrive any parameter with a JavaScript expression that reads time, channels, other parameters and values sent by a host page.
- Embedding and external controlPut the editor in an iframe or mount it in your own page, load projects by URL, and drive patches from outside with postMessage and ext().
- NDI bridgeReceive and send NDI video from the browser through a small local bridge, with the pixel conversion running in a 1 KB WebAssembly kernel.
- ArchitectureThe packages and their one dependency rule, the GPU pass contract behind both backends, the cook model, the file format and the decisions behind them.
- TouchDesigner parityWhat "toward TouchDesigner" means for a browser engine, how it is measured on 60 real projects, where WebToe stands, and what comes next.