π§ͺ PerfectLab - Concat
Merge two prompt lists without the 3Γ7 explosion
- PROMPT
π§ͺ PerfectLab - Concat is a plumbing node, and I mean that as a compliment. It takes two prompt strings or two prompt lists and glues them together element by element. That's it. The reason it exists is that ComfyUI's own string nodes don't do this - you want "house style" prepended to every shot in a series without writing the same twelve lines twice, and no core node will merge two batches of prompts for you.
Reach for it when you have a stream on one side and something to layer onto it on the other: a Shot Series output plus a lighting line, a batch of scene descriptions plus one shared quality footer, an LLM-written draft plus your own tag block.
How the merge actually works
The two inputs are both wired (forceInput), and both are declared to arrive as lists. The node reads them, pads them to the same length, and joins each pair with separator in the middle.
The important detail: it is element-wise, not a product. The output length is max(len(a), len(b)). If a has 3 prompts and b has 7, you get 7 merged lines - items 4 through 7 of b are paired with nothing and come out alone. You do not get 21 combinations. That's the deliberate design, and it's also the thing people get wrong: if you wanted every style against every shot, this node is not going to give it to you, and no amount of rewiring will change that.
The other half of the design is broadcast. A length-one list stretches across the whole merge, so a single style line becomes the prefix on all 7 shots. That's the one-style-for-every-shot case, and it's why the node is useful at all - you don't need to pad anything by hand.
Empty parts are skipped, so a missing or blank side just yields the other one.
Inputs and output
a- first prompt or list of prompts.b- second prompt or list of prompts.separator- what sits between the two merged parts, defaulting to a blank line (\n\n).
One output, PROMPT, and it is always a list - even if you feed in a single string. That's correct for a stream, but it does mean everything downstream iterates. If you wanted a one-off merged string for a single image, that's fine; it just runs once.
Wire the output into your CLIP text encode, or feed it into another Concat if you're layering three sources. Chained, they stack left to right.
Setting the separator
The default blank line is the right call for LLM-encoded models - Flux, Z-Image, Qwen-Image - where paragraphs separate cleanly and block structure is what the encoder wants. On an SDXL-lineage checkpoint running booru tags, a blank line in the middle of a tag string is noise. Set the separator to a comma and space:
,
One caveat on tag models: this node joins, it does not deduplicate. Put the same quality tags on both sides and you get them twice, which costs attention you'd rather spend on the subject.
Install
Ships in the same pack as the Prompt Assistant, no Python dependencies (requirements.txt is intentionally empty; pyproject.toml declares none).
cd ComfyUI/custom_nodes
git clone https://github.com/perfectmodelslab/ComfyUI-PerfectLab PerfectLab
Manager works too - Install via Git URL with the repo address. Restart, then Ctrl+F5. Python 3.10+.
Where people get burned
Length mismatch is silent. Because the shorter side just runs out, a 12-shot stream merged with a 5-line list quietly becomes 12 prompts where lines 6β12 have no style on them. It looks fine in the graph and only shows up in the images. Check that your two sides are compatible lengths, or rely on the length-one broadcast deliberately.
Assuming it's a combinator. Again, because this bites: it's a zip, not a cross product. Different name, different job.
Feeding it a non-list. Both sockets are string inputs, so anything upstream that emits a bare string arrives as a one-item list and broadcasts - including over a stream of 50. That's usually what you want, occasionally isn't.
Both inputs unwired. Optional inputs mean you can queue with nothing connected, and you'll get a single empty string out rather than an error. Empty prompt, sampler runs, you spend a minute wondering why the image ignored you.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| aopt | STRING | First prompt (or list of prompts). | |
| bopt | STRING | Second prompt (or list of prompts). | |
| separatoropt | STRING | Text placed between merged parts. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| PROMPT | STRING | β |