Jupyter Notebook
A real Jupyter cell inside your ComfyUI graph
- value
- b
- c
- d
- e
- f
- g
- h
- i
- j
- value
- b
- c
- d
- e
- f
- g
- h
- i
- j
Every ComfyUI power user has been here: your custom node quietly returns garbage, you don't know if the tensor is BHWC or BCHW or what dtype it actually is, so you duct-tape print(value.shape) into the node and re-queue. JupyterNotebook is the version of that ritual that doesn't suck. Drop it onto any wire, queue once, and the incoming value is live in a real Jupyter namespace you can poke at - no print statements, no rebuild loop, no guessing.
It comes from ComPyter, by sxela (the Stable WarpFusion person), which is one of the nicer debugging packs in the ecosystem precisely because it's one idea done well: one persistent IPython kernel shared by its two nodes. JupyterNotebook is the friendly one - the code cell, rich output panel, and Resume button live inside the node body, so you never leave ComfyUI. Its sibling, Jupyter Breakpoint, is the headless version for attaching an external front-end.
How it works
Under the hood there's a single IPKernelApp running in a daemon thread - ComfyUI doesn't get restarted, and the kernel survives across queue runs. The in-node UI talks to it over ZMQ through HTTP routes registered on ComfyUI's own server (/compyter/execute and friends). Your value and label get bound into the kernel namespace, your cell runs against them, and whatever value is bound to when the graph resumes is what flows downstream. That's the trick that makes it more than a viewer: it's an inline Python transform. Mutate value in the cell and the next node gets your mutated tensor.
State persists between runs, which is both the feature and the trap. session (a STRING, default "default") partitions the namespace - same session string = shared variables, different = isolated. Imports and helpers you define stick around, so you can iterate on one function across several queues. The one ordering caveat: ComfyUI only guarantees execution order along the dependency graph. Two Notebooks sharing a session but not wired to each other may run in either order, so wire any slot from A to B to force A → B.
The inputs that matter
The node's IO is dynamic, up to 10 wildcard slots, but you'll mostly touch three things:
value- required, accepts any type. This is your tensor, live in the kernel asvalue.code- the multiline STRING cell. It saves with the workflow and re-runs on every queue.mode- a 3-way dropdown that decides what happens when you queue.
mode is where the real choices live:
kernel + pause(default) - the workflow stops; you run cells in the node, click Resume ▶, and your rebinds flow downstream.kernel + continue- no pause; your savedcoderuns once per queue against the incoming value, in the persistent namespace.print()and matplotlib output render into the node's output panel within about a second of queue completion.no kernel- stateless transform in a throwaway namespace. No shared state, no IPython hooks (no inline plots), and IPython magics get stripped. Use it when you want pure, side-effect-free processing.
The optional wildcards b through j are hidden until you wire the trailing slot - wire b's output and b appears in the namespace; reassign it in the cell and that's what b outputs. And yes, label exists as an input, but it's legacy: the real label is your node's title (right-click → Rename), sanitized to ASCII. Rename the node and the status bar and error messages follow.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/sxela/ComPyter ComPyter
pip install -r ComPyter/requirements.txt
# restart ComfyUI once
Or just search ComPyter in ComfyUI Manager and let it handle the deps. The only requirements are ipykernel and jupyter_client, Python 3.10+. No models, no heavy downloads. Optional niceties: qtconsole for a GUI console, imageio for inline GIFs, plotly for interactive charts.
Where people get burned
The workflow that "freezes" is usually just sitting paused waiting for a Resume click you didn't notice. A code error in kernel + continue or no kernel mode fails the queue with the traceback rendered in the node - read the output panel before assuming the node is broken. And a word of caution: this node always executes (it never caches), so don't leave it in a production workflow you're sharing. It's a debugging instrument; pull it out when you're done.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| value | * | — | |
| mode | COMBO | kernel + pause | 3 options: no kernel, kernel + pause, kernel + continue |
| label | STRING | — | |
| code | STRING | — | |
| session | STRING | default | — |
| bopt | * | — | |
| copt | * | — | |
| dopt | * | — | |
| eopt | * | — | |
| fopt | * | — | |
| gopt | * | — | |
| hopt | * | — | |
| iopt | * | — | |
| jopt | * | — |
Outputs (10)
| Name | Type | Description |
|---|---|---|
| value | * | — |
| b | * | — |
| c | * | — |
| d | * | — |
| e | * | — |
| f | * | — |
| g | * | — |
| h | * | — |
| i | * | — |
| j | * | — |