- variables
- rendered_text
If you've ever rebuilt a prompt twelve times because you wanted to swap "close-up" for "wide shot" in every text-encode node, you know the feeling this node kills. BmzJinja2String renders a Jinja2 template with the variables you give it, and hands the finished string to anything that takes a STRING. One box, one edit point, and every downstream prompt follows along. It's the kind of small plumbing node from the ComfyUI-BmzUtils pack that reads as boring until you're mid-workflow and realize you've been hand-editing the same sentence in five places.
How it works
Jinja2 is the template engine Python itself standardizes on - it's what Flask renders HTML with. Inside ComfyUI it becomes a tiny prompt-assembly language: you write a string like a {{ style }} portrait of {{ subject }}, {{ mood }} lighting, feed in the variables, and get back a fully rendered string.
The node has two inputs. templated_text is your multiline template. variables is the interesting one - it accepts either a dict (wire it in from another node) or a plain string of key:value pairs. The pairs must be separated by |, like subject:grandmother | mood:warm. Here's the trap: the README claims newlines work as separators too, but the shipped code only splits on | - feed it newline-separated pairs and the whole block gets swallowed into one key with newlines embedded in the value. Stick to pipes. The parser trims whitespace, splits each pair on the first colon (so a value containing colons survives), and "last wins" if you repeat a key. Output is rendered_text - a plain STRING you can drop straight into a CLIP Text Encode.
The mechanism matters for one gotcha in particular: when a variable is missing, Jinja2's default behavior applies, which means it renders as an empty string. No error, no warning - {{ typo }} just silently disappears from your prompt. The pack's own README says this is expected behavior, but it's the thing that will waste twenty minutes of your life. If your render comes back with words missing, check your key names first.
What you'll actually set
- templated_text - the template. Go wild:
{% if %}conditionals and{% for %}loops work, not just{{ }}interpolation. - variables - the kv string or dict. Two real notes: every value comes in as a string, so
{{ n + 1 }}won't do arithmetic; and a malformed line (no colon) is silently skipped rather than erroring. A typo'dsubjct:...just vanishes.
Wire rendered_text into as many CLIP Text Encodes as you like and you've got one dial controlling a whole workflow. That's exactly the "one source, many consumers" pattern the ComfyUI plumbing layer exists for.
Installing it
ComfyUI-BmzUtils is a small four-node pack. Easiest route is ComfyUI Manager - search "ComfyUI-BmzUtils" and hit install, then restart ComfyUI. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/bmalhz/ComfyUI-BmzUtils
Then restart. The only Python dependency is jinja2, which the pack's requirements.txt pulls in for you - ComfyUI Manager handles this automatically, and most installs already have it around.
When it goes sideways
Beyond the missing-variable silence, the other trap is treating it like a python node. It isn't - it's pure templating. If you need actual expressions, filters like {{ n | int }} are your friend, but you're not going to run arbitrary code in there. And one honest caveat: this is a brand-new, zero-star pack from early 2026, so the author's format is the only documentation you'll find. The good news is the format is small, readable, and stable.
For a beginner, the win is real: one template box, one set of variables, and your prompts stop being a copy-paste minefield.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| templated_text | STRING | — | |
| variables | * | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| rendered_text | STRING | — |