🖥️🛠TechsToolz: Save Image w/Metadata
Stamp your whole generation recipe onto the file, not just a workflow JSON
- images
ComfyUI's built-in Save Image already embeds the full workflow JSON in the PNG - drag the file back in and the graph reconstructs. So why would you reach for a third-party saver? Because that JSON is for the machine. This node stamps the human-readable recipe on the file too: the prompt, negative prompt, steps, sampler, scheduler, CFG, seed, model name and model hash, all written out in the same parameters format AUTOMATIC1111 used. That's the version you can read at a glance in any PNG metadata viewer, paste into a Civitai description, or spot-check when someone asks "wait, which sampler did you actually use?"
It ships in ComfyUI-TechsToolz, a small personal pack by Robin Collins - pyproject calls it "a collection of what I wanted!" - that at 0.0.2 has exactly two nodes. Don't expect a big community around it; expect a focused utility that does one thing properly.
How it works
For PNG files it does two writes in one save. It builds a single parameters text block - prompt, negative, steps, sampler, CFG, seed, size, model hash, model - and tucks it into the PNG's tEXt chunk alongside the prompt and workflow JSON that ComfyUI supplies automatically. So you get the drag-back-into-ComfyUI workflow and the readable recipe. For JPEG and WebP it takes a different route: the comment goes into the EXIF UserComment field via piexif, since those formats have no PNG-style text chunks.
That distinction matters more than it looks. The workflow JSON only goes into PNGs. Save a JPEG and you get the recipe but not the reconstructable graph, so don't rely on drag-back for your webp output.
The inputs that matter
You set most of these once and forget them:
- images - your IMAGE tensor, the only true wiring.
- filename - a pattern, default
%time_%seed. Placeholders are%time,%seed,%model,%counter,%date. - path - relative subfolder under ComfyUI's
output/. Empty = root ofoutput/. It creates the directory if missing. - extension -
png,jpeg, orwebp. - modelname - type the exact checkpoint filename. This one is mandatory, see below.
- The metadata group: steps, cfg, sampler_name, scheduler, seed_value, width, height, positive, negative. These are the values stamped on the file, so keep them in sync with what you actually ran.
This is an output node - no outputs to wire, it just saves and shows the files in the UI.
Installing
Via ComfyUI Manager, search "ComfyUI-TechsToolz" and install, or:
cd ComfyUI/custom_nodes
git clone https://github.com/robin-collins/ComfyUI-TechsToolz.git
cd ComfyUI-TechsToolz
python install.py
Then restart ComfyUI. Dependencies are light - piexif, numpy, Pillow - nothing model-sized to download.
Where people get burned
The modelname field will crash the node if you leave it empty. It's not optional in practice: the code resolves the checkpoint path from it, then hashes the file to build Model hash:. Empty string means no path, and the hash call dies. Type the exact filename - juggernautXL_v9.safetensors, not the folder path - and you're fine. (Wiring the Model Name Extractor from the same pack into this field is the clean fix.)
Also: counter isn't automatic. It's a manual number you bump yourself, only used if %counter is in your filename. And every startup, the pack's dependency scanner prints a big report to the console - harmless, just noisy. If the output looks blank, check you didn't point path somewhere odd and that ComfyUI was restarted after install.
Inputs (18)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| filename | STRING | %time_%seed | — |
| path | STRING | — | |
| extension | COMBO | 3 options: png, jpeg, webp | |
| steps | INT | 201–10000 | — |
| cfg | FLOAT | 8.000–100 | — |
| modelname | STRING | — | |
| sampler_name | COMBO | 44 options: euler, euler_cfg_pp, euler_ancestral, euler_ancestral_cfg_pp, heun, heunpp2, +38 | |
| scheduler | COMBO | 9 options: simple, sgm_uniform, karras, exponential, ddim_uniform, beta, +3 | |
| positiveopt | STRING | unknown | — |
| negativeopt | STRING | unknown | — |
| seed_valueopt | INT | 00–18446744073709550000 | — |
| widthopt | INT | 5121–16384 | — |
| heightopt | INT | 5121–16384 | — |
| lossless_webpopt | BOOLEAN | true | — |
| quality_jpeg_or_webpopt | INT | 1001–100 | — |
| counteropt | INT | 00–18446744073709550000 | — |
| time_formatopt | STRING | %Y-%m-%d-%H%M%S | — |
Outputs (0)
No outputs