Donut Dynamic LoRA Stack
The LoRA Stack With No Row Limit — Edit a JSON Array Instead of 3 Dropdowns
- lora_stack
- lora_stack
LoRA stacking is standard practice now - characters, styles, and negative-LoRA helpers all get loaded at once. The classic DonutLoRAStack node from this pack gives you exactly three hardcoded slots per node, and when you need six you start daisy-chaining stacks into stacks and your graph turns into a game of Jenga. DonutDynamicLoRAStack is the same idea with the slot cap removed: rows live in a single slots_json value (serialized as JSON, no hardcoded row limit), and the frontend renders them as a proper table editor with per-row dropdowns instead of the old fixed three-slot layout.
It's worth being clear about what it is and isn't. This node only builds a LORA_STACK - a list of (lora, model_weight, clip_weight, block_vector) entries that describes what you want loaded. It never touches a MODEL or CLIP. You hand the stack to something that applies it (the pack's DonutLoRALoader, or a legacy apply node) and that's where the actual model patching happens. Think of it as the shopping list, not the checkout.
How the rows work
The slots_json value is a JSON array, one object per row. Each row is one LoRA with a block_vector - a comma-separated string of per-block strengths, which is the pack's whole angle: default LoRA loaders apply one strength to the whole model, but block-weighted loading lets you put a style LoRA only into the layers that carry style, or a face LoRA only where identity lives. A serialized row looks like this (this one is an SDXL row, so 13 numbers: the leading position-0 slot plus the model's 12 blocks):
[
{"id": "1", "enabled": true, "lora_name": "my_style_v1.safetensors",
"model_weight": 0.8, "clip_weight": 1.0,
"block_preset": "SDXL-CORE-WIDE", "block_vector": "1,1,1,1,1,1,1,1,1,1,0,0,0",
"inherit_block_vector": false}
]
The fields a beginner actually sets: lora_name is the filename as it appears in your ComfyUI/models/loras folder, model_weight/clip_weight are the strengths, and the per-block weighting lives in block_vector. block_preset is the named shorthand (SDXL-CORE-WIDE, ZIT-IN-ONLY, KREA2-HALF and friends) - in the row editor, picking one auto-expands the preset's numbers into block_vector, which is the field the backend actually applies. That distinction saves you a silent failure: if you hand-write JSON, don't fill in a preset name and leave block_vector empty expecting it to expand - either include the vector or flip inherit_block_vector to true so the row pulls from global_block_vector. The presets shown are filtered by model_type - Auto shows everything, or narrow to SDXL, SD15, FLUX, ZIT, ZIT-NE or KREA2 (the pack's preset library knows Z-Image Turbo's 30 transformer layers and Krea 2's 28 DiT blocks, not just SDXL's).
There's a neat escape hatch in the global_block_vector input: any row with inherit_block_vector set to true inherits that single string, so you can drive the block weighting of several LoRAs from one place instead of editing each row.
Inputs and output, short list
slots_json- the rows, as above. This is the one you actually edit.model_type- preset filter by architecture (Autoif you're not sure).global_block_vector- shared block string for rows set to inherit.civitai_lookup(Ondefault) - fetch LoRA metadata/hashes from CivitAI; needs an API key in the pack's settings, and helps relocate renamed/moved files.
Output is a single lora_stack, which can also chain: the optional lora_stack input lets you feed one stack into the next and combine. Mechanically, the node feeds your rows through the legacy stack builder three at a time, so block-vector resolution, hash-based relocation and CivitAI metadata all stay consistent with the rest of the pack. The table editor keeps it friendlier than the "you must type JSON" impression - but if you edit the raw value, a stack becomes copy-pasteable, and there's no upper bound on how many LoRAs pile up.
Install and quirks
It ships in ComfyUI-DonutNodes - ComfyUI Manager, search "DonutNodes", or clone into custom_nodes and pip install -r requirements.txt with ComfyUI's own Python. If you get a "LoRA could not be resolved" error downstream, it's almost always a typo in lora_name or a file that moved - the pack's resolver is hash-aware specifically so it can find renamed files when CivitAI lookup is on. Keep in mind this whole pack is a personal project with a near-zero community footprint: no big Reddit tutorial base, so treat the tooltips in the node as the documentation.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| model_type | COMBO | Auto | Filter block presets by model architecture. Auto shows all presets. |
| slots_json | STRING | [] | — |
| global_block_vector | STRING | Used only by rows with Inherit global enabled. | |
| civitai_lookup | COMBO | On | Fetch LoRA info from CivitAI (requires API key in settings) |
| lora_stackopt | LORA_STACK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| lora_stack | LORA_STACK | — |