ComfyUI Extension: ComfyTimeline
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.
Maya-style global timeline for ComfyUI — a shared bottom-panel bar (scrub, range, transport, keyframe dope-sheet) + curve editor + frame-accurate viewer, with a public JS & Python API any node can connect to.
README
ComfyTimeline — a global timeline standard for ComfyUI
A VFX-style global timeline for ComfyUI — one bottom-panel bar that owns frame state (playhead, animation range, playback range, fps, transport, loop) for the whole graph, plus a VFX Graph-Editor-style curve editor node and a frame-accurate image-sequence viewer.
It also ships ComfyTimeline, a small public API so any node or extension can connect to the timeline — read the current frame/range/fps, react to scrubbing and playback, or draw keyframe ticks on the bar — from both JS and Python.
Status:
apiVersion 1.0.0. The surface listed in API_REFERENCE.md is the committed stable contract.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/FemtoStudio/comfyui-timeline
Restart ComfyUI, then open the bottom panel → "Timeline" tab (the panel is hidden by default — toggle it from View → Bottom Panel, or press Alt+T to jump straight to the Timeline tab). No Python dependencies beyond ComfyUI itself.
Transport shortcuts
Defaults (rebindable in Settings → Keybindings, discoverable in the command palette under "Timeline:"):
| key | action | |---|---| | Space | play / pause | | → / ← | next / previous frame (wraps in the play range) | | Alt+T | open the Timeline panel tab |
Transport buttons on the bar: ⏮ go-to-start · ‹ step · ▶/⏸ play · › step · ⏭ go-to-end · ⟳ loop · EF play-every-frame (no frame-drop, vs real-time).
Nodes provided:
- Timeline Animation — author animation curves (Hermite tangents, pan/zoom, marquee, key/tangent editing). Its keys show as ticks on the global bar.
- Timeline Image Viewer — scrub/play an
IMAGEbatch against the global playhead.
Connect a node to the timeline
Python (read the frame/range/fps in execute())
from comfy_api.latest import io
try:
from comfy_timeline import timeline_inputs, read_timeline
except ImportError: # timeline pack not installed
timeline_inputs = lambda **k: []
read_timeline = None
class MyNode(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="MyNode",
inputs=[io.Image.Input("image"), *timeline_inputs()],
)
@classmethod
def execute(cls, image, **tl):
t = read_timeline(**tl)
frame = t.current_frame # the live playhead at queue time
...
timeline_inputs() adds standard current_frame / start_frame / end_frame
/ fps widgets. The frontend auto-binds and hides them and drives them from
the bar, so the value the user set on the bar bakes into the prompt at queue
time — cache-correct, no server state. Don't convert these to socket inputs
(force_input); they must stay widgets.
JS (react to the timeline live — no boilerplate for "read")
If your node already declares the standard widgets (via timeline_inputs() or by
hand: current_frame, or both start_frame+end_frame), the auto-bind
wires them with zero JS.
To react (e.g. repaint at the new frame), subscribe to the model:
import { app } from "/scripts/app.js";
app.registerExtension({
name: "MyExt",
setup() {
const T = window.ComfyTimeline; // capability check
if (!T || parseInt(T.apiVersion) < 1) return;
const off = T.model.subscribe(["frame", "range"], (e) => {
myRepaint(T.model.currentFrame); // or e.detail.frame
});
// call off() to unsubscribe
},
});
To drive the timeline (e.g. a "shot info" node sets the range):
window.ComfyTimeline.model.setAnimRange(1001, 1100);
window.ComfyTimeline.model.setPlayRange(1001, 1100);
Events
window.ComfyTimeline.model is an EventTarget. Each event's detail carries
a source token — pass your own stable token to mutators and ignore events
where detail.source is your own, to avoid feedback loops.
| event | detail | when |
|---|---|---|
| frame | { frame:int, source } | playhead moved |
| range | { which:'anim'\|'play', animStart, animEnd, playStart, playEnd, source } | a range changed |
| fps | { fps:float, source } | fps (or speed) changed |
| play | { playing:bool, source } | transport toggled |
| loop | { loop:bool, source } | loop toggled |
| mode | { mode:'realtime'\|'everyframe', source } | playback mode toggled |
| change (on curves) | { curveId } | a curve was registered / removed / edited |
Full method/field reference: API_REFERENCE.md. TypeScript/editor typings: web/js/comfytimeline.d.ts.
Versioning
window.ComfyTimeline.apiVersion— semver of the public surface. Minor = additive, major = breaking. Feature-detect with it.window.ComfyTimeline.schemaVersion— integer; only theapp.graph.extra.timelineserialization. Independent ofapiVersion.
Migration
The pre-1.0 window.timelineAnim cross-node registry has been removed.
Use window.ComfyTimeline (model/curves/events) instead.
License
MIT — see LICENSE.
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.