Extensions/Load Image (Recursive)
ComfyUI Extension

Load Image (Recursive)

Load Image that lists sub-directories of ComfyUI/input, with a folder dropdown, a root-only filter, working sub-directory previews, and cyclic arrow navigation.

By yadoo7·Created 22 days ago·Updated 5 days ago· 0
yadoo7/load_image_recursive
Nodes—
On cloudLocal install
Stars0
Updated5 days ago
Readme

Load Image (Recursive)

A drop-in replacement for ComfyUI's built-in Load Image node that also lists images from sub-directories of ComfyUI/input, adds a folder dropdown to filter the list, and — unlike the other subfolder loaders — actually makes those sub-directory images load: canvas preview, image viewer and mask editor all work, instead of being handed a path that can only 404.

中文说明见 下方。

Load Image (Recursive): the folder dropdown filters the image list, and sub-directory images load normally


The problem

The stock Load Image node only lists files sitting directly in ComfyUI/input. Anything you filed into folders is invisible:

input/
├── 001.png              ← visible
├── portraits/
│   └── maya.png         ← invisible
└── scans/
    └── 2026/
        └── roll-01.tif  ← invisible

So you end up either dumping everything into one flat folder, or copying files out of your library every single time.

There are already a few "load image from subfolder" nodes around. They solve the listing problem and then hit a second wall: the image never actually loads. ComfyUI resolves a /view filename against the input root, so a value like scans/2026/roll-01.tif can only ever 404 — blank canvas preview, blank viewer, and nothing for the mask editor to open. That is a gap in ComfyUI's server rather than in those nodes, and this node closes it.

What it does

  1. Recursive listing — every image under input/, at any depth, with paths relative to input (scans/2026/roll-01.tif).
  2. Folder dropdown — pick a folder and the image list narrows to just that folder. Turns a 1200-entry scroll into a 40-entry scroll. A dedicated root-folder entry isolates the files sitting directly in input/, so the top level stays browsable on its own instead of only being reachable through the "all" entry.
  3. Sub-directory images actually load — canvas preview, image viewer and mask editor all work for files in subfolders, because the real path is resolved before ComfyUI's /view handler sees it. Preview-sized requests are capped at 384 px, so browsing a folder does not pull every file down at full resolution.
  4. Bidirectional sync — pick an image and the folder dropdown jumps to its folder; pick a folder and the list filters.
  5. The ◀ ▶ arrows wrap around — stepping past the last image lands on the first, and stepping back from the first lands on the last. Stock combos go grey and stop at either end. Wrapping never drags the folder dropdown along, so a loop over (all) keeps covering every image instead of snapping into whichever subfolder it happens to reach.

Everything else is inherited from the stock node, unchanged: decoding, EXIF handling, alpha→mask, the upload button, drag & drop, paste, mask editor, path-safety checks. Outputs are identical (IMAGE, MASK, STRING).

Install

cd ComfyUI/custom_nodes
git clone https://github.com/yadoo7/load_image_recursive.git

Restart ComfyUI. No dependencies, no patch scripts, nothing to configure.

Usage

Add Load Image (Recursive) from the image category (or search for recursive, subfolder, 子目录).

| Widget | What it does | |---|---| | folder | Filter only. (全部) / (All) shows everything. Does not affect the output. | | image | The actual pick. Paths include the sub-folder, e.g. scans/2026/roll-01.tif. | | (upload button) | Same as the stock node. Uploaded files land in the root of input/. |

Switching folders deliberately does not change your current image selection — it only narrows the list. You re-pick explicitly. Nothing silently swaps your input behind your back.

Keeping the list fresh

INPUT_TYPES is only fetched when the node is created, so a list built then goes stale as soon as you add a folder or drop a file into input/. Three paths rebuild it, and they all go through the same authoritative /object_info call:

  1. Automatically when ComfyUI rebuilds combo lists — via the official refreshComboInNodes extension hook. Without this the rebuild would wipe the folder filter, since ComfyUI assigns options.values wholesale.
  2. After using the upload button (the value lands a moment later, so the rebuild is deferred).
  3. Manually, right-click the node → ⟳ 刷新图片列表.

Refreshing keeps your folder selection — it only rebuilds the lists. If the folder you were on no longer exists, it falls back to (全部).

Why sub-directory images break (and how this fixes it)

Worth reading if you are curious why every other sub-folder loader shows a blank preview.

A note on dropdown thumbnails. Up to roughly ComfyUI 1.4x the combo dropdown rendered a thumbnail next to every entry, and that is what this middleware originally targeted. Frontend 1.5x replaced the combo dropdown with a plain text list, so no node shows thumbnails in the dropdown any more — including the stock Load Image. The underlying path problem is unchanged though: it still breaks the canvas preview, the image viewer and the mask editor, and that is what this node fixes.

ComfyUI's frontend builds combo thumbnail URLs with an internal getMediaUrl(filename, type, kind) helper, which emits:

/api/view?filename=<filename>&type=input

Two problems with that, for sub-folder-aware nodes:

  • No subfolder. Our values are scans/2026/roll-01.tif, so the server resolves the basename against the input root, finds nothing, and returns 404. No thumbnail.
  • No max. Even when it does resolve, the server ships the full-resolution original. Fine for a 200 KB PNG, fatal for a 52 MP camera scan.

Both are fixable by editing server.py and the frontend bundle — but that means patching ComfyUI core, which breaks on every update and can't be shipped as a custom node.

So this node installs an aiohttp middleware instead, and fixes both on the way in, before any handler sees the request. Zero files outside custom_nodes/load_image_recursive/ are modified.

The routing rule, in order:

| Request | Action | |---|---| | channel= present | MaskEditor needs real pixels → pass through, only inject subfolder | | max= present | Frontend asked for a specific size → serve that thumbnail ourselves | | preview= present | Canvas / image viewer → pass through, only inject subfolder | | filename contains / | Bare dropdown request → serve a 384 px WebP ourselves | | anything else | Untouched. Native ComfyUI behaviour. |

That fourth rule is the key one: filename containing / is provably broken natively (it can only ever 404), so taking it over can't regress anything. Requests that natively work are left alone.

Thumbnails are cached in-process, keyed on (path, mtime, size, limit), so re-opening the dropdown is free and editing a file invalidates its entry.

/view has supported subfolder= natively all along, with a directory-escape check — the frontend just never used it.

Optional: speed up the root folder too

The middleware only takes over requests that are broken natively. Images in the root of input/ resolve fine, so ComfyUI serves them at full resolution — that's native behaviour, and this node leaves it alone.

If your input/ root also holds big files and the dropdown feels slow, run:

python tools/patch_frontend_thumb.py            # patch
python tools/patch_frontend_thumb.py --check    # status only
python tools/patch_frontend_thumb.py --revert   # undo

It makes the frontend ask for max=384 on combo thumbnails; the middleware then honours it. This one does patch a file outside this folder (comfyui_frontend_package/static/assets/*.js) and it is reverted by a frontend update — hence optional, and with a --revert.

On frontend 1.5x and later the combo dropdown no longer renders thumbnails at all, so this patch has no visible effect there. It only matters on older frontends.

Compatibility

  • ComfyUI 0.39.x — tested against 0.39.1.
  • Needs aiohttp and Pillow, both already required by ComfyUI.
  • If the middleware fails to install for any reason, the node still works — it logs a warning and thumbnails fall back to native behaviour. Node loading is never blocked.

Development

python -m unittest discover -s tests -v

32 tests covering recursive scanning, directory-escape protection, thumbnail downscaling and LRU eviction, the middleware routing table, and the VALIDATE_INPUTS / INPUT_TYPES contract.

The suite works in two environments:

  • Inside ComfyUI — uses the real folder_paths and nodes.
  • Bare environment — detects that ComfyUI is missing and installs minimal stubs, so CI only needs pip install aiohttp pillow (no torch).

Both paths are verified to pass. tests/test_load_image_recursive.py picks automatically.

Frontend behaviour (the folder filter and the three refresh paths) is not covered by unit tests — it needs a live ComfyUI. Those were verified end-to-end with a headless browser instead.

Known limitations

  • The folder widget is a front-end filter. It is not stored as a meaningful value and does not participate in caching, so switching folders never re-triggers a run.
  • Folders are derived from the files inside them. An empty folder won't appear in the dropdown.
  • clipspace/ is skipped — that's MaskEditor scratch space, pure noise here.

Credits

  • Subclasses LoadImage from ComfyUI by comfyanonymous. All decoding, masking and validation behaviour is theirs.
  • The folder / image split is inspired by the folder-tree combos in ComfyUI-Custom-Scripts by pythongosssss — a different implementation for a different problem, but the "pick the folder first" interaction comes from there.

License

MIT — see LICENSE.

Note that this is a plugin for ComfyUI, which is licensed under GPL-3.0. This node imports and subclasses ComfyUI code at runtime; how that interacts with MIT for a plugin is a judgment call you should make for your own use. Every custom node in the ecosystem does the same thing.


中文说明

「加载图像(含子目录)」 —— 原生 加载图像 的替代品,把 ComfyUI/input 下 各级子目录里的图片一起列进下拉,并多加一个「目录」下拉做过滤。

中文名走 ComfyUI 的官方语言包机制(locales/zh/nodeDefs.json,经 /api/i18n 下发), 所以中文界面下它会和原生「加载图像」并排出现;英文界面下显示 Load Image (Recursive)。 搜索框里输 加载图像、加载图片、子目录、递归 都能搜到。

解决什么

原生节点只列 input 根目录里的文件。你按项目分好的文件夹(人像/、扫描/2026/) 它完全看不见,只能把所有图堆成一个平铺目录,或者每次从图库里往外拷。

装上就有

  1. 递归扫描任意层级,路径相对 input(如 扫描/2026/roll-01.tif)
  2. 「目录」下拉:选完目录,图片列表只列该目录下的文件。多一个「(根目录)」, 只列直接躺在 input/ 里的那些 —— 根目录的图不用再混在「(全部)」里翻 (根目录一张图都没有时,这个选项不出现)
  3. 子目录的图真的能加载 —— 画布预览、图片查看器、遮罩编辑器都能正常打开子目录里的图 (请求进来前先把真实路径解析好)。预览类的请求会先压到 384px 再发, 翻目录不会把每张原图整张拉下来
  4. 双向联动:选图 → 目录自动跳过去;选目录 → 列表收窄,并且画布上的预览跟着切到该目录的第一张 (原生节点的预览只认自己的 callback,光改候选列表不会刷新,所以这里手动触发了同一套回调)
  5. ◀ ▶ 箭头首尾相接 —— 最后一张再往右是回到第一张,第一张再往左是跳到最后一张 (原生 combo 走到头就变灰、点不动)。而且箭头循环时不会把目录下拉一起带走, 所以在「(全部)」里一路点下去,是真的把整库绕一圈,而不是半路被吸进某个子目录

解码、EXIF、alpha→mask、上传按钮、拖拽粘贴、遮罩编辑器、路径安全校验 全部继承原生实现,输出完全一致(IMAGE / MASK / STRING)。

安装

cd ComfyUI/custom_nodes
git clone https://github.com/yadoo7/load_image_recursive.git

重启 ComfyUI 即可。无依赖、无补丁脚本、无需配置。

列表怎么保持最新

INPUT_TYPES 只在建节点时取一次,所以之后往 input/ 里新建文件夹、拖图进去, 下拉里都看不到。三条路径都会重建,而且都走同一个权威来源(/object_info):

  1. 自动 —— ComfyUI 重建 combo 列表时,通过官方扩展钩子 refreshComboInNodes。 没有这个钩子的话,ComfyUI 会直接整体替换 options.values,把目录过滤冲掉
  2. 上传后自动 —— 值要过一会儿才落下来,所以是延时重建
  3. 手动 —— 右键节点 →「⟳ 刷新图片列表」

刷新会保留你当前选的目录,只重建列表;如果那个目录已经不存在了,才退回 (全部)。

为什么别人的子目录加载器预览是空白

ComfyUI 前端拼 /view 地址时只带 filename + type:不带 subfolder,也不带 max。 于是 扫描/2026/roll-01.tif 这种值被服务端当成根目录文件去找 → 404。 画布预览、图片查看器、遮罩编辑器统统打不开。

关于缩略图:ComfyUI 1.4x 前后,下拉里每个条目会渲染一张缩略图,当时的补丁就是冲这个去的。 前端 1.5x 把 combo 下拉改成了纯文字列表,现在任何节点(包括原生「加载图像」)下拉里都没有缩略图了。 但同一个路径问题依然会让画布预览、查看器、遮罩编辑器失效,本节点解决的就是这个。

这两点要么改 server.py + 前端打包产物(改核心,升级就冲突,没法作为 custom node 发布),要么在节点里挂一个 aiohttp middleware 在请求进入 handler 之前拦下来。本项目选后者:custom_nodes/load_image_recursive/ 以外的文件 一个都没改。

分流规则见上表。关键判据是第四条 —— filename 里带 / 的请求在原生实现下 必然 404,接管它不可能让情况变差;原生能正常处理的请求一律不碰。

可选:根目录大图也加速

middleware 只接管原生必然失败的请求。input 根目录的图原生能正常解析, 所以还是按原尺寸发。如果你的根目录也有大文件、下拉觉得卡,跑 tools/patch_frontend_thumb.py(带 --check / --revert)。这一步会改到 custom_nodes/ 以外的文件,所以标为可选。

开发 / 测试

python -m unittest discover -s tests -v

32 个测试,覆盖递归扫描、目录逃逸防护、缩略图降采样与 LRU 淘汰、 middleware 分流表、以及 INPUT_TYPES / VALIDATE_INPUTS 契约。 装了 ComfyUI 就用真模块,没装就自动装 stub(CI 里只要 aiohttp + Pillow, 不用拉 torch)。前端行为需要活的 ComfyUI,是用无头浏览器端到端验证的。

注意

  • 「目录」只是前端过滤器,不参与出图,也不参与缓存判定(切目录不会触发重跑)
  • 切目录不会自动改你已选的图 —— 需要重新选。这是故意的,不能悄悄换掉出图输入
  • 空目录不会出现在下拉里(目录是从文件反推的)
  • clipspace/ 会被跳过(那是 MaskEditor 的临时目录)