Exists
The \"did anything actually arrive?\" check ComfyUI makes weirdly hard
- any_input
- any
- boolean
Say you've built a workflow where a model loader is optional: if the upscale model is there, use it, and if it isn't, fall through to the plain path. ComfyUI will not help you with this. Wire an input that isn't connected and the graph fails validation before anything runs, so "there's nothing here" never gets a chance to be a value worth branching on.
Exists is the node that turns that absence into an answer.
What it is
One optional input, two outputs. It tells you whether anything came through and passes the value along unchanged - the judgement and the hand-off in a single node, so you don't have to split your wire in two to inspect it.
The design core is that any_input is optional, not required, and that's deliberate rather than tidy-mindedness. A required input that's left unwired never reaches the node: ComfyUI reports a missing input at validation time. Marking it optional means an unconnected socket arrives as None, and the node is allowed to say "nothing here" instead of the run dying before it starts.
What counts as "nothing"
This is where the node earns its keep, because None is only the first of five cases:
| Counts as nothing | Counts as something |
| --- | --- |
| Unwired socket | 0 |
| None | False |
| Empty string "" | A fully black IMAGE |
| Empty list, tuple, dict | Any other value |
| An all-black mask | |
| An all-zero latent | |
The two rows that surprise people are the last one in each column. A fully black image is something - it's a valid picture, and pretending otherwise would swallow legitimate work. A fully black mask is nothing, because an all-black mask handed to a compositor just gives you the original image back; it carries no information. Same for an all-zero latent: upstream sent a tensor, but there's nothing in it.
The mask test uses a 0.02 threshold rather than demanding exact zero, and the reasoning is documented: saving an RGBA mask and reading it back - which Qwen Image 2.1 workflows do - leaves 1/255 to 3/255 of residual, so an "empty" mask previews as pure black but isn't numerically zero. Notably, the author's node only judges on that threshold. The value that comes out of the any output is untouched; it doesn't quietly zero those pixels for you.
One more case worth knowing: an ExecutionBlocker counts as nothing. That's what makes it compose with the pack's Route Blocker - a branch that was cut upstream reads as absent here rather than as a stray object.
Inputs and outputs
any_input (optional, wildcard type) takes literally anything - an image, a mask, a latent, a model, a string. The backend's type check is bypassed for this socket so wiring an IMAGE or a MODEL into it doesn't get rejected as a type mismatch.
Two outputs. any is the value passed straight through, empty if nothing was connected. boolean is the verdict. The intended wiring is boring and correct: take boolean into a branch condition - an if/else node, the pack's bool toggle, or anything else that eats a BOOLEAN - and take any into the branch that actually consumes the value. Missing optional model becomes a fallback instead of a stack trace.
Installing it
Manager → Install Custom Nodes → ComfyUI-QG-tools, or clone it:
cd ComfyUI/custom_nodes
git clone https://github.com/live1617/ComfyUI-QG-tools
Full restart; the console should show QG-tools v0.9.7 | 38 nodes loaded | 8 categories. No pip packages, no models - the node is a few lines of pure Python. Found under QG-tools/Logic.
Where people get burned
A black image isn't detected. People wire an image in, get a black frame out of some upstream failure, and expect boolean to go false. It won't. Only masks and latents get the blackness test; an all-black image is a real image. If you need to catch a black generation, you want a threshold node, not this one.
Testing against a boolean that was never connected. Since an unwired socket is the whole point, a graph that looks broken - a branch that never fires - is often just a wire you forgot. Check the connection before you go hunting for a logic bug.
It judges, it doesn't clean. The any output is bit-identical to what came in, including a mask with faint residual. If you were hoping to get a scrubbed value out of the same node, you'll need a second node that actually zeroes it.
Don't wire any where the value is mandatory. If the branch downstream requires an image and nothing arrived, passing an empty value through only moves the failure one node later. Pair boolean with a lazy branch - an if/else that doesn't evaluate the untaken side - so the absent path genuinely doesn't run.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| any_inputopt | * | Any input. When it is wired and actually delivers a value, the boolean output is true; unwired, or when upstream hands over an empty value, it is false (empty text, empty list, all-black mask and all-zero latent all count as empty). A fully black image is not empty - it is a valid picture. The value itself is passed on through the any output. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| any | * | The value passed through unchanged (empty when nothing is connected). A value means true, nothing means false. Empty text, an empty list, an all-black mask and an all-zero latent all count as nothing; 0 and False count as something. Drives a branch, a switch, or any boolean input. |
| boolean | BOOLEAN | — |