UmiAI Wildcard Processor
Inside the UmiAI Wildcard Processor
- model
- clip
- model
- clip
- text
- negative_text
- width
- height
- lora_info
- input_text
- input_negative
- bypass_matches
- explain_json
- prompt_diff
- artist_chain
The author's own pitch for this node is the clearest reason it exists: you roll a Cyberpunk background and your character decides to wear plate armor, because the two wildcards never talked to each other. The UmiAI Wildcard Processor is a single node that lets those rolls reference each other - define a choice once as a variable, reuse it everywhere, and gate other fragments on what got picked.
It lives in Comfy-UmiAI (the pack calls itself C-UMI), which is a lean prompt toolkit rather than a 100-node monster. That was the point: it grew out of a thread where someone wanted text-file wildcards without installing a huge pack for one feature. The wildcard processor is the load-bearing part of it.
Where it fits
Wildcard expansion in ComfyUI has belonged to two camps. Impact Pack owns __wildcard__ for a large share of the ecosystem - it's a side feature of a detailing pack - and ComfyUI-Dynamic-Prompts is the dedicated alternative with its own syntax. Neither gives you variables, conditionals, negative extraction, resolution overrides and inline LoRA loading in one node that also hands back a matching negative prompt.
Reach for this when your prompts are getting generative - batch runs where you want variation that still hangs together - rather than when you need one text box to rotate three words. The syntax lives in SYNTAX.md in the pack folder, and the node has a built-in guide: right-click it.
How it works
The node takes your whole prompt in text and runs it through fixed phases: inline [preset:...] functions, then [section:...] recomposition, then comment stripping, then the core expansion pass, then an optional profile, then bypass matching, then inline LoRA extraction, then @@width=...,height=...@@ settings, then a final clean-up. Everything reads from one seeded RNG, so the same seed plus the same files plus the same settings reproduces the exact same prompt, down to which line each wildcard picked. That trace is what explain_json is for.
The syntax you'll actually type:
$hair={silver|blue|pink}, 1girl, $hair hair
__pose|standing calmly__
[if $hair==silver: pale lighting | warm lighting]
[neg: blurry, bad hands, watermark]
<lora:style_model.safetensors:0.8>
$hair resolves once and stays consistent, because assigning a value once and reusing the variable - rather than rolling the same wildcard twice - is the difference between a coherent character and two different hair colors.
The inputs that matter
text and seed are the only required ones. Beyond that:
modelandclip- connect both if you want inline<lora:...>tags to actually load. Neither alone is enough.dry_run- expands the prompt and reports which LoRAs would load without touching weights. Turn this on while you're building.width/height- default dimensions you can wire into an empty latent.@@width=832,height=1216@@in the prompt overrides them (clamped to 64–8192).bypass_phrases- a comma-separated list matched as whole words, case-insensitively, against the finished prompt.catwill not matchcategory. Results come out ofbypass_matchesas a JSON boolean list.prompt_profile/prompt_preset/preset_placement- model-family defaults and reusable fragments, from the same YAML files the Profile and Preset nodes read.
The inputs named _frozen_text, _frozen_negative and _frozen_seed are the Pin button's storage. Leave them alone; the frontend hides them, and the Pin button ("Lock seed and prompt below") is how you keep a good roll instead of losing it to the next queue.
Wiring it up
text goes to your positive text encoder, negative_text to the negative one - the node pulls [neg: ...], **phrase** and [negative]...[/negative] blocks out of the positive half and into the negative output for you. width/height feed the latent node, and model/clip pass through to the sampler when you use inline LoRAs. input_text and input_negative are the untouched originals, which is exactly what Umi Save Image wants.
Install
ComfyUI Manager can install it - search for the pack title, or install via git URL. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/Tinuva88/Comfy-UmiAI
cd Comfy-UmiAI && pip install -r requirements.txt
Core needs pyyaml and nothing heavier; requests and curl_cffi are only for the optional UI-tools overlay. Restart ComfyUI and look under UmiAI and UmiAI/prompt. The README's own release path is a zip extracted into custom_nodes that unpacks a folder named C-UMI - either name is fine, just don't nest it twice.
Where people get burned
The seed widget is the classic ComfyUI landmine: its control_after_generate mode fires after the run, so finding a roll you like and then switching to fixed often locks in the next seed, not the one you got. UmiAI's answer is the Pin button - pin the roll and it reuses that exact expansion instead of rolling again.
If a wildcard name isn't found, nothing explodes, but don't rely on that: write the fallback (__pose|standing calmly__) while you build, and run Umi Prompt Syntax Lint on the template before queueing a forty-image batch. And if the node doesn't show up in the menu, it's almost always the missing pyyaml install or a restart that never happened.
Inputs (19)
| Name | Type | Default | Description |
|---|---|---|---|
| text | STRING | Prompt using UmiAI syntax: __wildcards__, {a|b} choices, $variables, [if ...] conditionals, [neg: ...] negatives, <lora:...> tags. See SYNTAX.md. | |
| seed | INT | 00–18446744073709550000 | Same seed, template, wildcard files and settings reproduce the same expansion. Use the seed control's increment/randomize mode for new rolls; __~name__ cycles by seed. |
| modelopt | MODEL | Connect to load inline <lora:...> tags into the model. | |
| clipopt | CLIP | Connect together with model to load inline <lora:...> tags. | |
| lora_tags_behavioropt | COMBO | Append to Prompt | Where to inject LoRA trigger/activation tags into the prompt. |
| lora_cache_limitopt | INT | 50–50 | How many loaded LoRA weight sets to keep cached in RAM. 0 disables caching. |
| widthopt | INT | 102464–8192 | Default width output. Overridden by @@width=...@@ in the prompt. |
| heightopt | INT | 102464–8192 | Default height output. Overridden by @@height=...@@ in the prompt. |
| input_negativeopt | STRING | Optional incoming negative prompt. Wildcards/variables are expanded; extracted negatives are appended to it. | |
| bypass_phraseopt | STRING | — | |
| bypass_phrasesopt | STRING | Comma-separated phrases matched as whole words, ignoring case, against the processed prompt. Results come out of bypass_matches for Umi Bypass nodes. | |
| prompt_profileopt | COMBO | None | Model-family defaults and lint rules from prompt_profiles.yaml. |
| prompt_presetopt | COMBO | none | Named prompt fragment from prompt_presets.yaml applied to this prompt. |
| preset_placementopt | COMBO | append | How the selected preset combines with the prompt text. |
| section_orderopt | STRING | Comma-separated order for [section:name] blocks in the prompt. | |
| dry_runopt | BOOLEAN | false | Expand the prompt and report LoRA info without actually loading LoRAs. |
| _frozen_textopt | STRING | Set by the node's Pin button. When present, this exact expansion is reused instead of rolling. | |
| _frozen_negativeopt | STRING | Negative half of a pinned roll. | |
| _frozen_seedopt | STRING | Seed captured with a pinned roll. Empty means nothing is pinned. |
Outputs (13)
| Name | Type | Description |
|---|---|---|
| model | MODEL | — |
| clip | CLIP | — |
| text | STRING | — |
| negative_text | STRING | — |
| width | INT | — |
| height | INT | — |
| lora_info | STRING | — |
| input_text | STRING | — |
| input_negative | STRING | — |
| bypass_matches | STRING | — |
| explain_json | STRING | — |
| prompt_diff | STRING | — |
| artist_chain | STRING | — |