Nodes/ComfyUI-ScriptFlow/MultiOutputScript
ComfyUI Node

MultiOutputScript

Stop stacking Math and Text nodes — script it in one box instead

By kantan-kanto·Created 5 months ago·Updated 2 months ago· 2
MultiOutputScript
  • in_text_1
  • in_text_2
  • in_text_3
  • in_value_1
  • in_value_2
  • in_value_3
  • out_text_1
  • out_text_2
  • out_text_3
  • out_value_1
  • out_value_2
  • out_value_3
code# UI inputs: in_text_1, in_text_2, in_text_3, in_value_1, in_value_2, in_value_3 # Script inputs: it1, it2, it3, iv1, iv2, iv3 # UI outputs: out_text_1, out_text_2, out_text_3, out_value_1, out_value_2, out_value_3 # Script outputs: ot1, ot2, ot3, ov1, ov2, ov3 # Example: width, height = (512, 384) if iv1 > iv2 else (384, 512) ov1, ov2 = width, height

The name isn't subtle, but it's honest: MultiOutputScript takes three numbers and three strings, runs a short Python-like script on them, and hands back three numbers and three strings. In practice that means the little chain of Math + Text + Compare + Switch nodes you drag around can collapse into a single box. If you've ever built a "if the image is portrait, use these dimensions, else those" contraption out of five nodes, this is the node that eats it whole.

The flagship use case is exactly that. Wan 2.2 I2V is picky about aspect ratio - 480p and 720p want the frame to match your input image's orientation. The README's starter script is the whole thing in five lines: connect GetImageSize width and height to the value inputs, and MultiOutputScript picks 512×384 for landscape or 384×512 for portrait. No comparison nodes, no conditional switch, no spaghetti. Same trick handles sampler settings, seed clamping, timestamp filenames.

How it works

Behind the curtain it's an AST interpreter, not a real Python runtime. Your code gets parsed into a syntax tree, run through a validation pass, then walked by the pack's own evaluator. That means no import, no open, no eval/exec, no getattr, no file or OS access, and a hard cap of 10,000 steps and 10,000 loop iterations. It's a deliberately sandboxed Python subset.

Worth saying plainly: this is safer than a node that execs your string, but it's still code running with full user-level access to your machine. The README's rule is one line - only run trusted scripts, don't paste unknown code. Not paranoia: a malicious custom node already burned Disney contractors in this ecosystem.

The inputs and outputs that matter

The only required input is code - a multiline string that comes pre-filled with a working template, which is a nice touch. The optional sockets are the interesting part: all six (in_text_1..3, in_value_1..3) accept any type. They're validated at runtime, so text inputs must coerce to str and value inputs to int/float - wire a mask or image into a value socket and you'll get a clean error, not garbage. That's the same "accept anything" design the community told one famous extension to stop doing, and the tradeoff is real, but here it's contained: validation happens at runtime and the errors are legible.

Inside the script you work with these variables:

# Inputs
it1, it2, it3      # the three text inputs ("" if unconnected)
iv1, iv2, iv3      # the three value inputs (0.0 if unconnected)

# Outputs - set these and they appear on the matching sockets
ot1, ot2, ot3      # STRING outputs, must be str or None
ov1, ov2, ov3      # INT outputs, must be int/float or None

Unassigned outputs are just None (nothing appears on that socket). You'll usually only set one or two ovs for the resolution/seed math, or an ot for text. The allowed toolbox is familiar - math, datetime, random, round, min/max/sum, string methods like replace/split/startswith, list.append - plus f-strings, loops, and even helper functions defined in your script.

Installing it

It ships in ComfyUI-ScriptFlow by kantan-kanto. Easiest route: ComfyUI Manager → search "ComfyUI-ScriptFlow" → Install → restart. Or the manual way:

cd ComfyUI/custom_nodes
git clone https://github.com/kantan-kanto/ComfyUI-ScriptFlow
# restart ComfyUI

Good news: zero dependencies - no requirements.txt, no model downloads. Pure Python, self-contained interpreter.

Common issues

The interpreter is strict and tells you exactly why when it trips:

  • "Keyword arguments are not supported" - this is the one that'll get you. Only positional args are allowed. round(x, ndigits=2) fails; round(x, 2) works.
  • "Unsupported function call" - you used something off the allowlist. datetime.now() works, time.time() doesn't. No import.
  • "Script exceeded step limit" - a runaway loop. Your while True is the problem.
  • NameError: Name is not defined - a typo, or a variable from an older draft of the script.
  • Output socket stays empty - you forgot to assign the matching ot/ov, or you put a float where a string belongs. And remember numeric outputs truncate to INT: 384.7 becomes 384, so round()/math.ceil() yourself if you care.
  • strftime fails - it needs v1.2.0+; on older versions the README shows the manual formatting workaround.
  • Outputs change every run - random and datetime are non-deterministic by design. Fine for filenames, wrong for reproducible seeds.

There's also a genuinely clever recipe in the README: it converts an LLM dialogue transcript into a dialogue_segments_json string for downstream TTS nodes, speaker tags and timestamps included. If you've ever fought to get structured output out of a text-generation node, that script alone justifies the install.

Categoryutils

Inputs (7)

NameTypeDefaultDescription
codeSTRING# UI inputs: in_text_1, in_text_2, in_text_3, in_value_1, in_value_2, in_value_3 # Script inputs: it1, it2, it3, iv1, iv2, iv3 # UI outputs: out_text_1, out_text_2, out_text_3, out_value_1, out_value_2, out_value_3 # Script outputs: ot1, ot2, ot3, ov1, ov2, ov3 # Example: width, height = (512, 384) if iv1 > iv2 else (384, 512) ov1, ov2 = width, height
in_text_1opt*
in_text_2opt*
in_text_3opt*
in_value_1opt*
in_value_2opt*
in_value_3opt*

Outputs (6)

NameTypeDescription
out_text_1STRING
out_text_2STRING
out_text_3STRING
out_value_1INT
out_value_2INT
out_value_3INT