Nodes/Comfyui-LayerForge/Layer Forge (Editor, outpaintintg, Canvas Node)
ComfyUI Node

Layer Forge (Editor, outpaintintg, Canvas Node)

A Photoshop-ish layer canvas that lives inside your ComfyUI graph

By Azornes·Created about a year ago·Updated 4 days ago· 340
Layer Forge (Editor, outpaintintg, Canvas Node)
  • input_image
  • input_mask
  • input_image_1
  • input_image_2
  • input_image_3
  • input_image_4
  • input_image_5
  • input_image_6
  • input_image_7
  • input_image_8
  • input_image_9
  • input_image_10
  • input_image_11
  • input_image_12
  • input_image_13
  • input_image_14
  • input_image_15
  • input_image_16
  • input_image_17
  • input_image_18
  • input_image_19
  • input_image_20
  • input_image_21
  • input_image_22
  • input_image_23
  • input_image_24
  • input_image_25
  • input_image_26
  • input_image_27
  • input_image_28
  • input_image_29
  • input_image_30
  • input_image_31
  • input_image_32
  • image
  • mask
fit_on_addfalse
show_previewfalse
auto_refresh_after_generationfalse
trigger0
node_id0

LayerForge is the node you reach for when your workflow's problem stopped being generating an image and became assembling one. It's a full layer-based canvas editor - stack images, move and scale and rotate them, blend, mask, cut out, even inpaint - without ever leaving the node graph. The README's pitch is Photoshop in ComfyUI, and for compositing work that's honestly not far off. It's a fork of Comfyui-Ycanvas by yichengup, and it's the fork that took the concept somewhere: multi-layer z-ordering, 12 blend modes, per-layer opacity, freeform lasso inpainting, and optional AI matting all shipped on top.

This matters because ComfyUI's strength was always the pipeline, not the editing. You'd generate, export, open an editor, cut things up, come back. LayerForge closes that loop: generate an element, drag it onto the canvas, composite it, and send the flattened result back downstream - or feed a mask into an inpainting pass and have the result land back in the canvas automatically.

How it actually works

The clever bit is that the heavy lifting happens in your browser, not the Python backend. The node's frontend (all the js/ files) runs a real canvas editor with layers, an undo history, and pan/zoom, and it persists your whole canvas state to IndexedDB so a page reload doesn't nuke your work. The backend side - process_canvas_image - is almost embarrassingly thin: the frontend pushes your flattened composite and combined mask to the server over WebSocket as base64 keyed by your node's ID, and the Python just decodes it to tensors and hands back (image, mask). No model runs, no sampling happens here. If there's no canvas data for your node, you get a blank 512×512 instead of an error.

Because state lives in the browser, which node instance the canvas data is attached to matters more than you'd expect - which is why node_id is on the input list at all.

The inputs that matter

Most of the five required inputs are toggle behavior, not data:

  • node_id - the one that bites. It should auto-fill with your node's ID, and the frontend even tries to auto-correct it, but when it doesn't fill you get a black image and no idea why. The fix is in the README: Settings → enable "Show node IDs", find the ID shown on your node, type it in.
  • trigger - an int you bump to force the node to re-run, so an external workflow can poke the canvas along.
  • auto_refresh_after_generation - leave this off and the lasso-inpaint workflow silently stops working: after you run inpainting, the new output won't appear in the canvas. Turn it on for any edit loop.
  • fit_on_add / show_preview - fit pasted/dropped images to the canvas, and show a preview. Nice-to-haves, defaults off.

Then the optional data inputs: input_image (a single IMAGE - if you want more than one, feed a batch through the core ComfyUI Batch Image node and they land as separate layers) and input_mask (a MASK applied to the output area when you hit Run).

The outputs are what you'd expect from a flatten: image (the composited layer stack) and mask (the merged alpha of all visible layers). Wire image into a Save Image or an inpainting model; wire mask into the mask input of your inpaint setup. That's the whole plugin story.

Installing it

ComfyUI Manager: search Comfyui-LayerForge and install. Or the manual route:

cd ComfyUI/custom_nodes/
git clone https://github.com/Azornes/Comfyui-LayerForge.git

Then restart ComfyUI. The dependency list is short - torch, torchvision, numpy, Pillow, aiohttp, tqdm, transformers - and that last one only exists for the optional BiRefNet matting feature, so don't panic about the Hugging Face stack if you never use cutout. If you do want AI background removal, drop a BiRefNet checkpoint into ComfyUI/models/BiRefNet/ (get it from ZhengPeng7's repo); it's the same model that became ComfyUI's default background remover, and it's genuinely good on hair and fur.

Where people get burned

Three gotchas, all real, all with workarounds:

  • Black output - the node_id issue above. Check that first; it's the #1 "my node is broken" report.
  • Vue Nodes incompatibility. The modern "Vue Nodes" frontend (ComfyUI's Nodes 2.0 rewrite) breaks this node's canvas display. If the editor renders as nothing, go Settings → search "Vue Nodes" → disable Modern Node Design (Vue Nodes) and use the legacy canvas.
  • Lasso inpaint not sticking. Missing auto_refresh_after_generation again. Also: feather your polygon mask 10–50px and extend the output area a bit, or you'll chase seams forever. The README's manual blend menu exists precisely because lasso edges hide seams.

It's fiddly - it's a whole editor bolted onto a node graph, and it shows. But for assembling a scene from multiple generations without ever leaving ComfyUI, it's the one I reach for.

CategoryazNodes > LayerForge

Inputs (39)

NameTypeDefaultDescription
fit_on_addBOOLEANfalse
show_previewBOOLEANfalse
auto_refresh_after_generationBOOLEANfalse
triggerINT00–99999999
node_idSTRING0
input_imageoptIMAGE
input_maskoptMASK
input_image_1optIMAGE
input_image_2optIMAGE
input_image_3optIMAGE
input_image_4optIMAGE
input_image_5optIMAGE
input_image_6optIMAGE
input_image_7optIMAGE
input_image_8optIMAGE
input_image_9optIMAGE
input_image_10optIMAGE
input_image_11optIMAGE
input_image_12optIMAGE
input_image_13optIMAGE
input_image_14optIMAGE
input_image_15optIMAGE
input_image_16optIMAGE
input_image_17optIMAGE
input_image_18optIMAGE
input_image_19optIMAGE
input_image_20optIMAGE
input_image_21optIMAGE
input_image_22optIMAGE
input_image_23optIMAGE
input_image_24optIMAGE
input_image_25optIMAGE
input_image_26optIMAGE
input_image_27optIMAGE
input_image_28optIMAGE
input_image_29optIMAGE
input_image_30optIMAGE
input_image_31optIMAGE
input_image_32optIMAGE

Outputs (2)

NameTypeDescription
imageIMAGE
maskMASK