Comfy Canvas is a ComfyUI-native layered editor that runs directly inside the ComfyUI tab. It gives your workflow a persistent editing session, in-app painting and masking tools, prompt persistence, and a live round-trip path for reviewing the latest generated result without leaving ComfyUI.
v1.0 is the release that moves Comfy Canvas fully local inside ComfyUI.
Older builds relied on a separate side webapp and server workflow. That is no longer the primary experience.
- No separate Flask server to launch
- No extra browser tab to keep open
- No split workflow between ComfyUI and a side app
- Everything now opens and runs from inside the ComfyUI tab itself
Comfy Canvas now behaves like part of ComfyUI instead of a detached companion tool.
- The editor opens as an in-app overlay from the node
- Sessions stay attached to the node through
session_id - Layer data, prompt text, and editor state persist locally under ComfyUI user data
- Workflow outputs can be pushed back into the same session through
Comfy Canvas Output - The full paint -> run -> review -> continue loop happens in one place
If you used an older standalone or server-backed version, the key change is simple: Comfy Canvas now runs locally inside the ComfyUI tab.
I'm Zlata Salyukova, the developer behind Comfy Canvas. My work focuses on building practical AI and creative tools across Python, JavaScript, and user-facing product design.
I'm open to opportunities in AI and creative technology. You can reach me on X: @Zlata_Salyukova
- Full editor overlay inside ComfyUI
- Persistent per-node sessions
- Layer stack persistence, not just a flattened PNG
- Non-destructive per-layer masks with thumbnail-based mask editing
- Brush, eraser, eyedropper, paint bucket, marquee, and move/transform tools
- Layer thumbnails, rename, duplicate, delete, opacity, blend modes, and drag reorder
- Prompt dock with prompt persistence and workflow run button
- Autosave during editing plus final save on close
IMAGE,MASK,session_id, andpromptoutputs from the main node- Output preview loop through
Comfy Canvas Output - Copy Output workflow for bringing the latest generated result back into the canvas as a new layer
- Download the current right-pane output image directly from the editor
This package registers two ComfyUI nodes:
Comfy CanvasComfy Canvas Output
It also serves the local web app from web/ through ComfyUI's WEB_DIRECTORY hook in __init__.py.
The frontend is self-contained inside this repo, including a vendored PIXI runtime under web/app/vendor/, so it does not need a CDN or separate web service at runtime.
- Copy or clone this folder into
ComfyUI/custom_nodes/comfy_canvas. - Restart ComfyUI.
- Add
Comfy Canvasto your graph. - Optionally add
Comfy Canvas Outputif you want the right pane to show the latest generated result from the workflow.
No separate server or standalone app process is required.
Comfy Canvas targets current ComfyUI builds. If your installation is missing newer frontend or extension APIs, update ComfyUI before troubleshooting the custom node.
Comfy Canvas is released under the MIT License. See LICENSE.
Bundled third-party code keeps its own license notice:
- Add
Comfy Canvasto your graph. - Optionally connect an input image if you want to begin from an existing image.
- Add
Comfy Canvas Outputif you want the result preview loop. - Connect
Comfy Canvas.session_idtoComfy Canvas Output.session_id. - Connect your final workflow image to
Comfy Canvas Output.image. - Click
Open Canvason the node. - Paint, prompt, run the workflow, review the result, and continue editing in the same session.
Upstream image -> Comfy Canvas -> rest of workflow
Comfy Canvas.session_id -> Comfy Canvas Output.session_id
Final workflow image -> Comfy Canvas Output.image
This gives you:
- a stable session tied to the node
- a working editable document on the left pane
- the latest generated result on the right pane
- a fast edit-and-iterate loop without leaving ComfyUI
Download the example workflow JSON
Defined in comfy_canvas_node.py.
Inputs:
session_id(STRING)canvas_width(INT, default1024)canvas_height(INT, default1024)background(transparentorwhite)image(IMAGE, optional)
Outputs:
image(IMAGE)mask(MASK)session_id(STRING)prompt(STRING)
Execution behavior:
- If
edited.pngexists for the session, the saved edited image is returned. - Otherwise, if an input
imageis connected, that image is returned and seeded into the session asseed.png. - Otherwise, the node returns a blank document using the configured canvas size and background.
The mask output comes from the saved session mask when one exists.
If no saved mask exists, the mask is derived from the returned image alpha.
Current mask workflow:
- the node output mask is still created from the final visible canvas transparency
- opaque visible pixels in the edited image become black /
0in the mask - transparent pixels become white /
1in the mask - per-layer masks are now available in the editor and affect that final transparency non-destructively
- you can also still erase or delete pixels directly to create transparency
- for the most useful downstream mask output, start from a
transparentcanvas background - if you use a
whitebackground and keep the final composite fully opaque, the mask output will also stay fully filled
The prompt output returns the latest prompt saved from the editor dock, so the text you queue from the canvas can be consumed by the rest of the workflow on that execution.
Defined in comfy_canvas_node.py.
Inputs:
image(IMAGE)session_id(STRING)
Outputs:
image(IMAGE)
Behavior:
- saves the incoming image as
result.pngfor the matching session - updates session metadata with the result dimensions and timestamp
- returns the same image unchanged to the graph
Use the session_id output from Comfy Canvas as the session_id input to this node.
The ComfyUI integration lives in web/js/comfyCanvasOverlay.js. It:
- hides the raw
session_idwidget - adds the
Open Canvasbutton and node context-menu entry - opens the editor in a modal iframe inside ComfyUI
- loads and saves the current session
- polls for updated workflow result images so the right pane stays current
Current overlay header actions:
Reset SessionClose
Saving is automatic:
- edits mark the session dirty immediately
- autosave is debounced to roughly 1 second after the last change
- closing the overlay forces a final save before dismissal
When the editor opens, it initializes in this order:
- Saved session image, if one exists
- Upstream preview image discovered from connected nodes
- Blank document based on node width, height, and background
The app code lives under web/app/.
Current feature set includes:
- layered compositing with per-layer opacity, visibility, and blend mode
- layer thumbnails, rename, duplicate, delete, and reorder handles
- per-layer masks with separate content and mask thumbnails
- brush and eraser with adjustable size, hardness, opacity, and flow
- eyedropper sampling from the visible composite
- paint bucket fill on the active layer
- marquee selection and move/scale/rotate transforms
- Select All and Ctrl/Cmd+click layer thumbnails create tight pixel selections instead of full-canvas boxes
- Photoshop-style text layers with double-click edit, placeholder text on new point-text boxes, point text auto-sizing, and optional fixed-width paragraph wrapping
- 20-step undo and redo history with stable pan/zoom framing
- upload image to a new layer
- copy current output preview to a new layer
- download the current output preview as a PNG from the editor top bar
- bottom prompt dock with prompt persistence and workflow run button
- fit editor, fit output, fit both, and pane swap controls
Tool shortcuts shown in the UI:
B: BrushE: EraserI: EyedropperG: Paint bucketM: MarqueeL: LassoW: Magic WandP: PenT: Text- Double-click existing text with the Text tool: Edit that text layer
V: MoveF: Fit both panes[ / ]: Decrease or increase brush / eraser sizeShift+[ / Shift+]: Decrease or increase brush / eraser hardnessCtrl/Cmd+A: Select all opaque pixels on the active layerCtrl/Cmd+clicka layer thumbnail: Select that layer's opaque pixelsCtrl/Cmd+DorEscape: Clear the current selectionDeleteorBackspace: Delete the current selection contentsEnterwhile editing point text: Commit textShift+Enterwhile editing point text: Insert a new lineCtrl/Cmd+Enterwhile editing fixed-width text: Commit textEnterin the prompt dock: Run workflowShift+Enterin the prompt dock: New lineCtrl/Cmd+Z: UndoCtrl/Cmd+YorCtrl/Cmd+Shift+Z: Redo
Selection behavior:
Ctrl/Cmd+AandCtrl/Cmd+clickon layer thumbnails build a real pixel mask from opaque pixels on that layer- disconnected painted regions stay part of the same selection instead of collapsing to one rectangular box
- selected edge pixels stay with the floating selection cleanly when moving, scaling, or rotating
Layer mask behavior:
- click
+Mon a layer card to add a non-destructive mask to that layer - click the main layer thumbnail to edit layer pixels
- click the smaller
Mthumbnail to edit that layer's mask - while editing a mask, the editor locks to the Brush tool
- while editing a mask, the other paint/select/transform tools are temporarily disabled
- layer masks now use a Photoshop-style grayscale paint workflow
- paint with black to hide, white to reveal, and gray for partial transparency
- the top color picker is normalized to grayscale while editing a mask
- Shift+click the
Mthumbnail to disable or re-enable the mask without deleting it - use the
M-button in the layers footer to remove the selected layer mask completely - mask thumbnails show the saved grayscale mask, while the main thumbnail shows the masked visible result
Mask output behavior:
- the node's
maskoutput is based on transparency in the final edited image, not on a separate global output-mask channel - erased or deleted areas on a transparent canvas become the mask output
- hidden areas created by per-layer masks also contribute to the mask output because they change the final composite alpha
- the saved session mask is generated from the editor composite's inverse alpha
- for best results, start with a
transparentbackground when you plan to use themaskoutput downstream
Text tool behavior:
- clicking with the Text tool creates a new point-text layer with a visible
Type textplaceholder - enabling
Fixed Widthswitches the tool into paragraph-text mode and wraps lines inside the configured width - double-clicking an existing text layer with the Text tool reopens it for editing
- while editing text, drag the text box by its border to reposition it without committing the edit
- pixel-edit tools rasterize a text layer automatically before direct paint, fill, or move-style pixel edits
Output preview behavior:
- the editor's download button saves the current right-pane output image, not the left editor canvas
Copy Outputpulls the current right-pane output into the layer stack as a new layer
Undo and redo behavior:
- the editor keeps up to 20 undoable edit steps
- redo can move forward to the latest state until a new edit creates a new history branch
- undo and redo restore canvas content without changing the current pan or zoom framing
Session files are stored under ComfyUI's system user data directory:
user/__comfy_canvas/sessions/<session_id>/
Possible files:
edited.pngmask.pngseed.pngresult.pngmetadata.jsondocument.jsonlayers/layer_####.pnglayers/layer_mask_####.png
Session helpers are implemented in comfy_canvas_session.py.
Important session details:
session_idis normalized to alphanumeric,_,-, and.characters and truncated to 128 characters- legacy sessions stored under
data/sessions/are migrated on first access - preview prefers
edited.png, then falls back toseed.png - reopening prefers
document.jsonso the saved layer stack is restored before falling back to flat image files Reset Sessiondeletes the full saved session directory- the saved mask is generated from the editor composite's inverse alpha, so transparent or deleted pixels become the exported mask
- per-layer masks are stored separately inside
document.jsonandlayers/layer_mask_####.png, then reapplied when the layered document is reopened - normal runtime use should not create session or cache files inside the
comfy_canvas/repo folder itself
Custom routes are registered in comfy_canvas_routes.py:
GET /comfy_canvas/sessions/{session_id}POST /comfy_canvas/sessions/{session_id}POST /comfy_canvas/sessions/{session_id}/clearGET /comfy_canvas/sessions/{session_id}/documentGET /comfy_canvas/sessions/{session_id}/imageGET /comfy_canvas/sessions/{session_id}/maskGET /comfy_canvas/sessions/{session_id}/result
These routes are internal to the ComfyUI extension and are used by the overlay for session load/save, preview image access, mask access, document restore, and result preview updates.
comfy_canvas/
|-- .gitignore
|-- __init__.py
|-- LICENSE
|-- README.md
|-- comfy_canvas_node.py
|-- comfy_canvas_routes.py
|-- comfy_canvas_session.py
`-- web/
|-- js/
| `-- comfyCanvasOverlay.js
`-- app/
|-- index.html
|-- styles.css
|-- assets/
| |-- Comfy_Canvas_Banner.png
| |-- Comfy_Canvas-UI.png
| |-- Comfy_Canvas-Canvas_Node.png
| |-- Comfy_Canvas-Output_Node.png
| |-- Comfy_Canvas-Workflow_Example(Flux2-Klein).png
| |-- Comfy_Canvas-Example_Workflow.json
| `-- icons/
| `-- sprite.svg
|-- vendor/
| |-- pixi.min.mjs
| `-- PIXI_LICENSE.txt
`-- src/
|-- editor.js
|-- layers.js
|-- main.js
|-- output.js
`-- pixi.js
- The web UI is a static app served by ComfyUI through
WEB_DIRECTORY - The overlay and editor communicate through a small iframe API exposed from
web/app/src/main.js - The canvas/editor implementation is PIXI-based
- PIXI is vendored locally under
web/app/vendor/ - Session storage is file-based and local to the current ComfyUI installation/user profile
- The overlay does not run inference itself; generation still happens through the normal ComfyUI graph execution path
- Session data is stored locally on disk and is not designed as a multi-user shared service
- The right-pane preview depends on
Comfy Canvas Outputwritingresult.pngfor the same session - This package is an editor/session layer, not a sampler or generation workflow by itself
- The node
maskoutput is driven by final composite transparency rather than a separate standalone mask-output channel - Older ComfyUI builds that lack the current extension/frontend APIs may need to be updated before Comfy Canvas will load correctly




.png)