EditUtils: Flux3Klein BBox (Element Table) lrzjason
FLUX.3 Wants Its Layout as JSON — This Node Lets You Draw It Instead
- configs
- config
- image
- configs
- bbox_json
FLUX.3 doesn't take boxes as a parameter. It takes them as text - a JSON array of rows appended to your prompt, coordinates on a 0–1000 grid from the top-left, so a box means the same thing regardless of output size. Handy for the model, miserable for a human. Writing [{"id":"wolf_1","tgt_bbox":[817,120,950,167],...}] by hand and discovering you put the wolf's ear where its tail should be is a bad use of an afternoon.
Flux3KleinBBox_EditUtils is the drawing surface for that array. Drag boxes on the canvas, type an id and a description per row, and it produces the element table.
One note on timing, since FLUX.3 is new: BFL announced it in July 2026 as a multimodal family and the community was still asking whether a Klein variant would exist at all. The pack's flux3klein route, added 2026-10-16, assumes it arrived - and it leans on ComfyUI's own native flux3 implementation rather than reimplementing the model, so reference latents travel on the standard reference_latents conditioning key. You need a ComfyUI build with FLUX.3 support, plus flux3_image_klein_sd_int8_convrot.safetensors (UNET), qwen3vl_4b_fp8_scaled.safetensors (CLIP, type flux3) and flux2-vae.safetensors.
The two modes are not a strength setting
Beginners get this wrong constantly. bbox_mode decides which question each row answers - pick it by what you're doing to the image, not by how hard you want the edit.
generation is for laying out a new image: one box per element, {"id", "bbox", "desc"}, the box saying where that thing goes. A reference image, if you connect one, is only the drawing underlay. editing is for changing an existing image: two boxes per row, {"id", "from", "src_bbox", "tgt_bbox", "desc"} - where the element is now, where it should end up, and which reference it comes from. The four row kinds fall out of those facts: keep (boxes equal), move (different), new (from is null), remove (no target).
There's no disabled option, deliberately: this node exists to build a table, so connecting it should produce one. Want it muted? Bypass it with Ctrl+B.
Inputs and outputs
Everything on the input side is optional. configs is the chain built by the config preparers, config is one image's single config, and image is the underlay - never encoded, only drawn on. bbox_mode picks the schema; bbox_rows, bbox_ids and bbox_descs are hidden canvas-owned state so your rows survive a workflow save. Don't hand-edit them.
Outputs are configs and bbox_json - no separate config socket, because the decorated config is already in the chain and a second copy would just be a second source of truth.
How the chain surgery works, since it explains a wiring question everyone has: if the config you pass in is already in configs (the normal case), the entry is replaced, matched by identity and then by a stamped config_id so it survives a deepcopy. If it isn't found - say configs is unconnected - it's appended. One image, one entry, no duplicates.
And if you left config unconnected but drew boxes? It appends a table-only entry with no image, so generation mode still sends the layout. That's the "new scene from boxes and a caption, no reference at all" case.
Where the boxes actually go
Nowhere on this node. EditTextEncode_EditUtils walks the whole chain, concatenates the rows from every config, and appends the JSON array to your prompt as "<caption> [ {...}, {...} ]". If you want to see what the model received, the generic encoder's seventh prompt output is the literal string handed to the tokenizer. The extractor's composed_prompt gives you the same thing.
bbox_json mirrors the merged table (upstream first, this node's last) as a plain string, useful for a text preview when a result looks wrong. It returns "" for an empty table, not "[]".
Install
Manager → search ComfyUI-EditUtils → install → restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/lrzjason/ComfyUI-EditUtils
No pip dependencies - the drawing surface is plain browser JS in the pack's web/ directory, so you need a reasonably current ComfyUI frontend and a hard browser refresh after updating.
Where people get burned
Tiny boxes. In BFL's own tests a new element in roughly a 40×25 px box often just didn't appear. Boxes guide placement, they don't clip - a slightly generous box beats a precise one that gets ignored.
Aspect ratio drift. The 0–1000 grid stretches with the frame. Draw for 832×1216, then generate at 1344×768, and your composition is not what you designed.
Instructions only in the table. Write the edit into the caption as well as the rows, and add an explicit keep row for anything that must not move. The shipped example graph has an empty prompt widget with the instruction living solely in a row's desc - and its note box is stale text copied from a BFL template that says "50 steps, cfg 3.5". Ignore it: the graph actually runs 8 steps at CFG 1.0 with a zeroed negative, which is what a step-distilled flow-map checkpoint expects.
Confusing the two deletes. Remove deletes the element from the image. Delete row throws away the row and exports nothing. Use the right one.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| configsopt | LIST | Configs chain from other config nodes | |
| configopt | ANY | Single config for this image (the preparer's config output). The element table is attached to it. | |
| imageopt | IMAGE | Reference image shown under the boxes while drawing (guide only, not part of the output). | |
| bbox_modeopt | COMBO | generation | generation: laying out a NEW image. Each row is {'id','bbox','desc'} and its box says where that element goes. editing: changing an EXISTING image. Each row is {'id','from','src_bbox','tgt_bbox','desc'} with two boxes - where the element is now (source) and where it should end up (target) - and names the reference image it comes from, so one element can be kept, moved, created or removed. |
| bbox_rowsopt | STRING | Element-table rows, owned by the canvas. Draw boxes instead of editing this by hand. | |
| bbox_idsopt | STRING | Hidden: one element id per row (canvas-owned). | |
| bbox_descsopt | STRING | Hidden: the row descriptions, JSON encoded (canvas-owned). |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| configs | LIST | — |
| bbox_json | STRING | — |