Nodes/Cozy Spoke/Realtime Python <-> Javascript Example Node
ComfyUI Node

Realtime Python <-> Javascript Example Node

The Node That Teaches Custom Nodes to Talk to the Browser

By cozy-comfyui·Created 2 years ago·Updated about a year ago· 13
Realtime Python <-> Javascript Example Node
      DropDownA
      DropDownB

      CozySpoke doesn't generate, upscale, or touch a single pixel. It has zero outputs and, on the surface, does less than nothing. And that's exactly the point: its display name is "Realtime Python <-> Javascript Example Node," which is the honest description of what it is. It's the minimal worked example of how a ComfyUI custom node talks to the browser and gets an answer back - the same machinery that powers live widgets in big packs like Jovimetrix. If you're a normal workflow user, this does nothing for you. If you've ever wanted to write your own custom node with a dropdown that changes based on another dropdown, this is the scaffold you've been missing.

      It comes from cozy-comfyui/cozy_spoke, a deliberately tiny pack by Alexander G. Morano (Amorano, "Joviex" on Reddit) - the same author behind Jovimetrix, the video/webcam/streaming node suite. This is the author stripping one trick out of that pack and publishing it as a lesson.

      How it works

      The whole pack is a two-way bridge, and you can read both halves in the source. On the Python side, CozySpokeNode is a classic-style node - NODE_CLASS_MAPPINGS, CATEGORY = "comfy-ext", no RETURN_TYPES - that does two things. First, it registers a few HTTP routes on ComfyUI's PromptServer (a GET/POST /cozy_spoke message bus and a POST /cozy_spoke/node demo endpoint). Second, when the node executes, its run() method calls PromptServer.instance.send_sync(...), which pushes a websocket event to the frontend. That's the Python → browser direction.

      The browser half lives in web/cozy_spoke.js, loaded via WEB_DIRECTORY = "./web". It's a standard app.registerExtension() block that hooks beforeRegisterNodeDef, grabs the node's widgets in onNodeCreated, and wires a callback to the DropDownA widget. Change that dropdown and the JS does api.fetchApi("/cozy_spoke/node", ...) with the new value; Python replies with a fresh list of sub-options, and the JS rewrites DropDownB's options live. That's the browser → Python → browser round trip. The node also listens for the cozy-event-combo-update event Python sends during execution and logs it to the console.

      What you actually touch

      The info_schema is honest about how little there is:

      • DropDownA - three placeholder options (Option1/2/3). This is the widget that drives everything.
      • DropDownB - two placeholder options, but its choices get replaced at runtime based on DropDownA. If you pick Option2, DropDownB suddenly offers "Option2 - Sub-option 1/2."

      No inputs, no outputs, nothing to wire. The only visible result of running it is a line in your browser's devtools console.

      Installing it

      There's nothing to install beyond the pack itself - requirements.txt is empty and there are no model files. Via ComfyUI Manager, search "cozy_spoke" (or the display title "Cozy Spoke"). Manually:

      cd ComfyUI/custom_nodes
      git clone https://github.com/cozy-comfyui/cozy_spoke
      

      Then fully restart ComfyUI - because it ships a web/ directory, the frontend needs a reload to pick up the extension. A fresh workflow will find it under comfy-ext in the node list.

      Where people get burned

      The biggest trap is expectation: it has no output socket, so beginners hunt for something to wire and find nothing. That's not a bug - the demo's "output" is the console message Received update from Python for Dropdown B, which you'll only see if you open the browser devtools and queue the workflow.

      Second, if you're on ComfyUI's Nodes 2.0 frontend, treat this as a teaching artifact from March 2025. It manipulates widgets through the classic LiteGraph extension API (this.widgets.find, setDirtyCanvas) - precisely the pattern the Nodes 2.0 rewrite has historically broken for packs like rgthree. On the legacy canvas it works; under the new renderer, don't be surprised if the dynamic dropdown stops refreshing. If you're here to learn, that's honestly part of the lesson: the extension API is the fragile half of the bridge.

      One real caveat if you're reusing the code in your own node: the Python message-bus helper polls and raises TimedOutException after three seconds if the frontend never posts the message the node is waiting for. It's a fine pattern for an example, but a 3-second stall per missing message adds up fast in a real workflow.

      Categorycomfy-ext

      Inputs (2)

      NameTypeDefaultDescription
      DropDownAoptCOMBO3 options: Option1, Option2, Option3
      DropDownBoptCOMBO2 options: Other1, Other2

      Outputs (0)

      No outputs