Nodes/ComfyTV/Image Concat
ComfyUI Node

Image Concat

Image Concat Stitches Your Images in the Browser

By jtydhr88·Created 5 months ago·Updated 2 days ago· 1,185
Image Concat
  • images
  • batch
  • image
◄force_run_token0►
◄project_id►
◄parent_output_id0►
◄clip_order►
◄layouthorizontal►
◄cols2►
◄gap0►
◄outer_gapfalse►
◄bg►

What it's actually for

You generated eight variations of a character and now you want all eight in one frame - a contact sheet, a storyboard strip, a single reference picture for the next model. In plain ComfyUI that's a small graph project: batch, stitch, resize, re-run when you want to swap the order.

Image Concat is the shortcut. Drop it on the ComfyTV canvas, wire your images in, drag the thumbnails into order, done - in the browser, live, no Run button. That last part isn't marketing. It's how the node is built, and it's both the reason to reach for it and the one trap worth knowing.

It's one of ~190 stages in ComfyTV, a pack that turns ComfyUI into a TapNow/LibTV-style canvas app where every operation is its own node feeding the next. Image Concat sits in the Compose category beside Grid Split, its inverse: Grid Split cuts an image into cells, this glues them back, and they share the same cols and gap vocabulary so a split-then-rejoin round trip lands near where you started.

How it works

The compositing doesn't happen in Python. A Vue panel in the node loads each wired image into an offscreen <canvas>, lays them out with 2D draw calls, and uploads the finished PNG into ComfyUI's input folder under comfytv/imageconcat/. That file's URL becomes the stage's output. Change the layout, gap, or order and it re-joins after about a quarter second.

The layout math is browser math, so know what decides your result:

  • horizontal scales everything to the height of the first image, keeping aspect. The first image is the reference - wire a tall portrait into slot one and the whole strip comes out tall.
  • vertical mirrors that: everything matches the first image's width.
  • grid makes each cell the size of the first image, then fits every image inside its cell preserving aspect and centers it, so odd shapes letterbox instead of stretching.

Cells fill left to right, cols per row, capped at the image count. A browser canvas tops out around 16384 px a side, so bigger results get scaled down uniformly - a 6K strip of twelve images lands smaller than you'd think.

The inputs that matter

Two ways to feed it. images is a growable block of up to 12 slots for individual images you place by hand. batch takes a COMFYTV_IMAGES bundle (Grid Split or Image Stage output) and every image inside joins, labelled B1, B2… in the strip. Wire both and the strip lists them all.

  • layout - horizontal, vertical or grid. The choice that matters most.
  • cols - images per row, grid only, 1–12.
  • gap / outer_gap - space between images in pixels of the first image, and whether that same margin wraps the outer edge too. Match Grid Split's border and outer-border values and your cells reassemble at their original size.
  • bg - CSS fill colour for gaps and empty grid cells; empty means transparent. Set one if the result goes somewhere that flattens alpha.

The rest - force_run_token, project_id, parent_output_id, clip_order - is internal plumbing the frontend writes. clip_order is the one you touch indirectly: it's the JSON list the reorder strip fills, which is why the strip, not slot number, decides order.

Output is a single image of type COMFYTV_IMAGE - a file reference, not a tensor. It wires straight into any other ComfyTV stage. To hand it to a plain ComfyUI node like Save Image, put ← ComfyTV Image (ComfyTV.BridgeFromImage) in between; the two type systems don't talk otherwise.

Install

ComfyUI Manager, search ComfyTV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/jtydhr88/ComfyTV

Restart ComfyUI; nodes appear under the ComfyTV category. Dependencies are unusually clean - pyproject.toml declares an empty dependency list, there's no requirements.txt hauling half of PyPI along, and this node needs no model download.

The README's install warning is earned: on Desktop, macOS, or a machine with two ComfyUIs, a relative cd quietly lands in the wrong instance. Clone by absolute path into the instance your startup log names, one line, quoted:

git clone https://github.com/jtydhr88/ComfyTV.git "/Users/you/Downloads/ComfyUI (1)/ComfyUI/custom_nodes/ComfyTV"

Where people get burned

Cloned, but no nodes appear. Check the layout first: custom_nodes/ComfyTV/__init__.py must be one level deep, not ComfyTV/ComfyTV/. Then fully restart the backend - a browser refresh does nothing for a new custom node.

A composite you can't find later. It isn't your workflow's output. It's a PNG in input/comfytv/imageconcat/ - ComfyUI's input directory. Hunting in output/ is the mismatch.

Headless runs don't concat. The real gotcha, straight from the source: the Python execute is a passthrough returning the first wired image (or first batch item), because all the stitching is an open browser page doing canvas work. Both images and batch are optional, too, so an unwired node just sits there. Drive the graph over the API with no ComfyTV page open and you get your first image, quietly, no error. It's a canvas node, not a batch processor.

The pack is young, so calibrate. ComfyTV launched mid-2026 into a thin footprint - its r/comfyui announcement pulled about 19 upvotes. Not a knock on the engineering (jtydhr88 has a long record of fast, solid ports like InstantMesh and HY-Motion1), but you'll be reading source and the comfytv.org reference, not tutorials.

CategoryComfyTV/Compose

Inputs (11)

NameTypeDefaultDescription
force_run_tokenINT00–2147483647Internal — bumped on Run to invalidate ComfyUI's input cache.
project_idSTRINGInternal — populated by the projectStore on the frontend.
parent_output_idINT00–2147483647Internal — lineage parent set by spawn handlers on the frontend.
clip_orderSTRINGJSON list of image keys in concat order — driven by the reorder strip in the node body.
layoutCOMBOhorizontalhorizontal = one row (heights matched to the first image), vertical = one column (widths matched), grid = cols per row.
colsINT21–12Columns per row in grid layout.
gapINT00–4096Gap between images, in px of the first image.
outer_gapBOOLEANfalseAlso put the gap around the outer edges.
bgSTRINGCSS fill color for gaps and empty grid space; empty = transparent.
imagesCOMFY_AUTOGROW_V3—
batchoptCOMFYTV_IMAGESImage batch (e.g. Grid Split or Image Stage output); each item joins the concat.

Outputs (1)

NameTypeDescription
imageCOMFYTV_IMAGE—