Nodes/m9-prompts-comfyui/ScramblePromptsText [m9]
ComfyUI Node

ScramblePromptsText [m9]

Twenty Variations of One Prompt, Without Typing Twenty Prompts

By MarcusNyne·Created 3 years ago·Updated 3 days ago· 1
ScramblePromptsText [m9]
    • prompt
    prompt
    order_prompts_percent20
    remove_prompts_percent0
    keep_prompts
    modify_weights_percent20
    weight_range0.5
    max_weight1.9
    print_outputfalse
    seed_optional0

    You have a prompt you mostly like. You want forty images off it, not forty identical images, and you are not going to hand-write forty prompts. So you vary what you already wrote: shuffle the order of the tags, drop one or two of them, nudge a couple of weights. That's ScramblePromptsText [m9], and it's the string-flavoured half of the pack's headline feature.

    It's the ComfyUI port of an A1111/Forge extension the same author shipped as sd-scramble-prompts-m9 - a tool that got its own small fan thread back in 2024, when prompt variation still meant installing extensions in a WebUI. Same idea, moved to ComfyUI, where the natural form turned out to be one node that eats a string and returns a string.

    Why the text version, not ScramblePrompts [m9]

    Same transforms, different sockets. ScramblePrompts [m9] takes clip and outputs CONDITIONING, so it replaces your CLIP Text Encode. This one takes a STRING and returns a STRING, with no clip anywhere. That matters in three situations: you want the scrambled text to go into a stock CLIP Text Encode (or an encoder the pack doesn't touch); you want to see the result before it's encoded, by wiring the output into a preview; or you want to chain it into TweakWeightsText [m9] so one node scrambles the whole prompt and the next targets only the parts you name.

    How it works

    The prompt model is simple and worth internalising, because both this node and TweakWeights use it: a prompt is a phrase between commas, but not inside parentheses. prompt one, prompt two is two prompts. (prompt, one), prompt two is also two - the comma inside the parens doesn't split anything. Each piece becomes a token with an optional weight.

    Then three transforms run, each scoped by a percentage converted to a count (percent × number of non-LoRA prompts / 100):

    • Reordering swaps prompts between positions.
    • Removal deletes prompts entirely - except LoRAs, which are never removed and never counted.
    • Weight modification nudges a random subset and re-emits them.

    That last one comes with a caveat: weight output is lossy and normalised. On parse each ( multiplies the weight by 1.05 and the parens come off; on output the node re-derives the shortest representation, re-emitting up to four parens plus a :weight suffix to three significant digits. The text you get back is not the text you sent. A hand-balanced density of brackets will come out rearranged.

    The fields

    The ones you actually set:

    • prompt - an input socket, not a field. The text lives in whatever feeds this node: a PrimitiveString, another prompt node, or the previous scramble in a chain.
    • order_prompts_percent (default 20) and modify_weights_percent (default 20) - how much of the prompt gets touched. Keep them low; the charm of this technique is a recognisably similar image, not a different one.
    • remove_prompts_percent (default 0, caps at 30) - you probably want this at 0 until you've watched a batch.
    • keep_prompts - comma-delimited keywords that protect a prompt from removal, matched as substrings. Your LoRA trigger words and the subject belong here.
    • weight_range (± this much, default 0.5) and max_weight (default 1.9, a ceiling the change won't cross).
    • print_output - dump the generated text to the console. Turn this on while you're learning the node; it's the only way to see what the percentages are doing.
    • seed_optional - and this is the whole ballgame (below).

    Output: prompt (STRING) → a CLIP Text Encode, or the next text node.

    The mistake everyone makes exactly once

    Leave seed_optional unconnected and the node is deterministic. ComfyUI always passes a value for a widget-backed optional input, unconnected that value is 0, and ComfyUI caches a node whose inputs haven't changed. Result: you queue twenty runs and get one variation, over and over, forever. It looks like the node is broken.

    Wire a seed primitive into it with control_after_generate set to randomize and you get a fresh scramble every queue - while any fixed seed reproduces a result you liked exactly, which is the other half of the feature.

    And a take, since it matters more every month: weights only do something on CLIP-lineage encoders. On SD1.5, SDXL, Illustrious, NoobAI, Pony, (word:1.3) is real attention. On an LLM-encoded 2026 model - Z-Image, Flux 2 Klein, Anima, Krea 2 - the wrapper passes disable_weights=True and your weighted output is fed to the encoder as literal punctuation. Reordering and removal still work there. Weight tweaking is inert. Half of this node's surface is SDXL-era by design, and knowing which half you're using will save you a confusing afternoon.

    Installing it

    Manager → search m9-prompts-comfyui → install; or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/MarcusNyne/m9-prompts-comfyui
    

    Restart. No requirements.txt, no model downloads, no native build - pure Python. The pack's text-string nodes are filed under the utils category rather than text, so if you're hunting the menu, look there or just double-click the canvas and search the node name.

    Categoryutils

    Inputs (9)

    NameTypeDefaultDescription
    promptSTRING
    order_prompts_percentINT200–100
    remove_prompts_percentINT00–30
    keep_promptsSTRING
    modify_weights_percentINT200–100
    weight_rangeFLOAT0.50–2
    max_weightFLOAT1.90–3
    print_outputBOOLEANfalse
    seed_optionaloptINT00–18446744073709550000

    Outputs (1)

    NameTypeDescription
    promptSTRING