Power Lora Hooks (fixed slots)
One Node, Eight LoRAs — and a Different Stack for Your Negative Prompt
- prev_hooks
- positive
- negative
- hooks
- positive
- negative
Stacking LoRAs in ComfyUI normally means a chain of LoraLoader nodes, and every one of them patches MODEL and CLIP. That's fine until you want a style LoRA on your positive prompt and not on your negative, or two characters each carrying their own LoRA into one generation. A LoraLoader can't do that - it rewrites the model once and both conditionings inherit the result, so the workaround has always been "load the model twice." Power Lora Hooks (fixed slots) gets you there a different way, on ComfyUI's own hooks API, patching nothing.
Hooks, not patches
Hooks are ComfyUI's second mechanism for applying a LoRA. Instead of rewriting weights up front, you build a HookGroup and attach it to a CONDITIONING; the sampler resolves that hook against your model at sample time (comfy/samplers.py's calc_cond_batch). This node does the building: up to eight fixed slots, each turned into a hook with comfy.hooks.create_hook_lora and folded into the running group with HookGroup.clone_and_combine. There's no separate "apply hooks to model" node to hunt for - the conditioning carries the hooks, the sampler finds them.
That's why you'd come here: positive and negative each carry a different set of LoRAs into the same run, which plain MODEL/CLIP patching can't do without two copies of the model.
The inputs that matter
Eight identical blocks, lora_1 through lora_8. Each has a picker (default None), an enable toggle (on by default), and strength_model / strength_clip (float, -20 to 20, default 1.0). A slot contributes nothing if it's disabled, empty, or has both strengths at 0 - so if a LoRA seems to have no effect, a stray 0.0 is the first thing to check. Slots apply in order, 1 through 8; that's your stack order, and the old advice to put the broad, higher-impact LoRA first still holds.
The three optional inputs are where the node stops being "a nicer LoraLoader":
positive/negative(CONDITIONING) - wire your text-encode output in and the accumulated hooks are applied to it, then passed straight back out. Application is additive (comfy.hooks.set_hooks_for_conditioning), so hooks already on that conditioning combine rather than getting replaced.prev_hooks(HOOKS) - chaining, same convention as ComfyUI's native hook-creating nodes. Another Power Lora Hooks or a nativeCreate Hook LoRAplugs in here and the stacks accumulate, which is your answer past eight.
Outputs are hooks, positive, negative. hooks is always produced, even with every slot off, so it can feed another hook consumer (the native Set CLIP Hooks, say) or the next node's prev_hooks. The conditioning outputs are the ones that surprise people: wire the positive output somewhere without connecting the positive input, and that output is blocked - everything downstream simply doesn't run. Better than a silent None reaching your sampler, but it reads as "the run stopped early" until you know.
Installing
Through Manager, search for JJMaden Random Nodes and install it. If it doesn't turn up, use Manager's Install from Git URL with the repo, or do it by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/jjmaden/comfyui-jjmaden-random-nodes
# restart ComfyUI
There is no pip step. The pack's pyproject.toml declares zero dependencies and the node only imports comfy.hooks, comfy.utils and folder_paths - all core. You'll find it under advanced/hooks/create. Fair warning: this is a one-person pack with essentially no community footprint and a git history that is a single commit. The code is small and readable; there's nobody upstream to escalate to.
Where it bites
GGUF pipelines and other model patchers. The pack's own model card documents a real, user-confirmed crash - applying hooks at sample time against some GGUF-quantized models (loaded via UnetLoaderGGUF) combined with other model-patching nodes dies with AttributeError: 'Linear' object has no attribute 'temp'. It reproduces identically with ComfyUI's native hook nodes, so it's an upstream limitation, not this node's bug. On GGUF, use LoraLoader or rgthree's Power Lora Loader instead.
It's the experimental API. The node is flagged EXPERIMENTAL because it sits directly on the hooks API ComfyUI itself marks experimental, so a future update can move the ground under it.
It's heavier per step. The word from people using ComfyUI's native Create Hook LoRA nodes is consistent: this route drinks more VRAM than the standard loader chain and samples slower. One commenter flatly calls it unreliable.
And the ordinary LoRA failures still apply. A Flux LoRA does nothing on SDXL - LoRAs are architecture-bound. A trigger word you forgot to use reads as a LoRA that did nothing. Pony/Illustrious anime LoRAs often want CLIP skip 2. None of that is this node's fault, and none of it is fixed by it.
So is it worth it?
For plain "stack four LoRAs on a character shot," no. Chain LoraLoaders, or use rgthree's Power Lora Loader - which also gives you dynamic add/remove rows and an info dialog that pulls trigger words off CivitAI for you. This node has neither; the slots are plain widgets and you get exactly eight.
It earns its place when the separation is the point: a style LoRA only on positive, a character LoRA that must not touch the negative, different stacked sets on two conditionings in one pass. That's a capability rather than a convenience, and this is a tidy way to get it on the native hooks API.
Inputs (35)
| Name | Type | Default | Description |
|---|---|---|---|
| prev_hooksopt | HOOKS | Optional. Hooks from an upstream hook-creating node (another PowerLoraHooks, or a native Create Hook LoRA / Create Hook Model as LoRA, etc.) -- combined with this node's own enabled slots, same chaining convention as ComfyUI's native hook-creating nodes. | |
| positiveopt | CONDITIONING | Optional. If connected, the accumulated hooks are applied to it (comfy.hooks.set_hooks_for_conditioning) and it's passed through 'positive' below. If not connected, 'positive' out is blocked. | |
| negativeopt | CONDITIONING | Optional. Same as 'positive', for the negative conditioning. | |
| lora_1_enabledopt | BOOLEAN | true | — |
| lora_1opt | COMBO | None | 1 options: None |
| lora_1_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_1_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_2_enabledopt | BOOLEAN | true | — |
| lora_2opt | COMBO | None | 1 options: None |
| lora_2_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_2_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_3_enabledopt | BOOLEAN | true | — |
| lora_3opt | COMBO | None | 1 options: None |
| lora_3_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_3_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_4_enabledopt | BOOLEAN | true | — |
| lora_4opt | COMBO | None | 1 options: None |
| lora_4_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_4_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_5_enabledopt | BOOLEAN | true | — |
| lora_5opt | COMBO | None | 1 options: None |
| lora_5_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_5_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_6_enabledopt | BOOLEAN | true | — |
| lora_6opt | COMBO | None | 1 options: None |
| lora_6_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_6_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_7_enabledopt | BOOLEAN | true | — |
| lora_7opt | COMBO | None | 1 options: None |
| lora_7_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_7_strength_clipopt | FLOAT | 1.00-20–20 | — |
| lora_8_enabledopt | BOOLEAN | true | — |
| lora_8opt | COMBO | None | 1 options: None |
| lora_8_strength_modelopt | FLOAT | 1.00-20–20 | — |
| lora_8_strength_clipopt | FLOAT | 1.00-20–20 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| hooks | HOOKS | — |
| positive | CONDITIONING | — |
| negative | CONDITIONING | — |