Citron Image Saver
The save node that makes CivitAI actually read your PNG
- images
- hashes
- a1111_params
ComfyUI's stock SaveImage already embeds your whole graph in the PNG - drag the file back onto the canvas and the workflow rebuilds. That part works. What it doesn't do is write the flat, A1111-flavoured settings block that CivitAI's uploader parses, which is why people upload a PNG and watch the "resources used" panel come up empty. Citron Image Saver exists to close that gap: one save node that writes both dialects - the ComfyUI workflow chunks and an A1111 parameters string with resource hashes.
It's a fork: the pack's own attribution points at giriss/comfy-image-saver, the node the "stop using the default Save Image node if you post to Civitai" crowd has recommended for years. Citron adds a workflow .json export, a custom field, chained hash passing between savers and an explicit show_preview. It also has zero Reddit footprint - not one thread mentions the pack - so treat it as a competent fork of a known-good node, not as something with a community behind it.
How it works
Under the hood it builds a metadata record, then serialises it two ways.
The record comes from whatever you wire into the node's optional inputs: model name, prompts, sampler and scheduler names, steps, CFG, seed, size, denoise, CLIP skip. From the prompt strings it regex-scrapes <lora:name:weight> and embedding:name, resolves each to a file under your loras/embeddings folders, and SHA-256s it down to the first 10 characters. Same for the checkpoint. Those short hashes are what CivitAI matches against its database, so this is the step that decides whether your upload gets auto-linked.
Two consequences worth knowing before you run it. First, the hash is cached in a .sha256 sidecar file written next to the model, so the very first save with a new checkpoint prints a progress bar and sits there hashing 6–12 GB. That's not a hang. Second, LoRAs are found from prompt text only - if your LoRA is applied by a loader node and the tag never appears in the positive string, it won't be hashed and won't show on the site.
Then it writes: PNG gets a parameters text chunk plus the workflow/prompt chunks; JPEG and WebP get the string stuffed into EXIF UserComment via piexif, the pack's one dependency.
The inputs that matter
Required: images (what you're saving), filename, path, extension (png, jpeg, jpg, webp).
filename is a template, and it's the most under-used part. The tooltip lists the variables: %date, %time, %model, %width, %height, %seed, %counter, %sampler_name, %steps, %cfg, %scheduler_name, %basemodelname, %denoise, %clip_skip. The default %time_%basemodelname_%seed gives you 2026-10-24-141530_myCheckpoint_123456789.png. path is a subdirectory under Comfy's output folder and takes the same variables; it gets created if missing.
Of the long optional list, the ones you actually wire: positive, negative, modelname, sampler_name, scheduler_name, steps, cfg, seed_value, width, height. modelname accepts a comma-separated list - first one becomes the primary model, the rest slide into the hash list. additional_hashes takes Name:HASH pairs (16-char max, 30 entries) for anything the scraper can't see. custom inserts a free-text fragment into the parameters string. save_workflow_as_json drops a sibling .json; embed_workflow and show_preview toggle the embedded graph and the UI thumbnail.
Outputs are hashes and a1111_params. hashes is a comma-separated Name:HASH string - feed it into another saver's additional_hashes when you save two variants in one graph. a1111_params is literally the text that got written, handy for eyeballing what CivitAI will see.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/citronlegacy/ComfyUI-CitronNodes
# then restart ComfyUI
The README lists ComfyUI Manager support as "coming soon" and the repo still carries placeholder publisher metadata, so Manager search may come up empty - the clone is the reliable path. piexif is the only pip dependency:
# from your ComfyUI venv
pip install piexif
No model downloads. This is pure plumbing. Nodes land under Citron_ImageSaver.
Common issues
- Every node in the pack vanished after install.
piexifis imported at module load in the saver code, so a missing dependency takes the whole pack down with an import error, not just the saver. Check the console for the traceback and install the package. - JPEG metadata silently missing. EXIF is capped at 65535 bytes. The node prints an error and retries without the workflow; if it's still too big it prints a second error and skips metadata entirely. PNG is the format that always works.
- The metadata is wrong, not missing. Nothing here reads your sampler. If you leave
stepsat its default 20 while the sampler ran 34, the file says 20. That's the whole reason the pack also ships selector and literal nodes plus a checkpoint loader that emits its own filename - wire them and the numbers stay honest. - "Model hash" is blank. The name didn't resolve to a file on disk - usually a typo, or a name that only exists in
diffusion_modelsand doesn't match exactly.
Inputs (26)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | image(s) to save | |
| filename | STRING | %time_%basemodelname_%seed | filename (available variables: %date, %time, %model, %width, %height, %seed, %counter, %sampler_name, %steps, %cfg, %scheduler_name, %basemodelname, %denoise, %clip_skip) |
| path | STRING | path to save the images (under Comfy's save directory) | |
| extension | COMBO | file extension/type to save image as | |
| stepsopt | INT | 201–10000 | number of steps |
| cfgopt | FLOAT | 7.000–100 | CFG value |
| modelnameopt | STRING | model name (can be multiple, separated by commas) | |
| sampler_nameopt | STRING | sampler name (as string) | |
| scheduler_nameopt | STRING | normal | scheduler name (as string) |
| positiveopt | STRING | unknown | positive prompt |
| negativeopt | STRING | unknown | negative prompt |
| seed_valueopt | INT | 00–18446744073709550000 | seed |
| widthopt | INT | 5120–16384 | image width |
| heightopt | INT | 5120–16384 | image height |
| lossless_webpopt | BOOLEAN | true | if True, saved WEBP files will be lossless |
| quality_jpeg_or_webpopt | INT | 1001–100 | quality setting of JPEG/WEBP |
| optimize_pngopt | BOOLEAN | false | if True, saved PNG files will be optimized (can reduce file size but is slower) |
| counteropt | INT | 00–18446744073709550000 | counter |
| denoiseopt | FLOAT | 1.000–1 | denoise value |
| clip_skipopt | INT | 0-24–24 | skip last CLIP layers (positive or negative value, 0 for no skip) |
| time_formatopt | STRING | %Y-%m-%d-%H%M%S | timestamp format |
| save_workflow_as_jsonopt | BOOLEAN | false | if True, also saves the workflow as a separate JSON file |
| embed_workflowopt | BOOLEAN | true | if True, embeds the workflow in the saved image files. Stable for PNG, experimental for WEBP. JPEG experimental and only if metadata size is below 65535 bytes |
| additional_hashesopt | STRING | hashes separated by commas, optionally with names. 'Name:HASH' (e.g., 'MyLoRA:FF735FF83F98') | |
| show_previewopt | BOOLEAN | true | if True, displays saved images in the UI preview |
| customopt | STRING | custom string to add to the metadata, inserted into the a111 string between clip skip and model hash |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| hashes | STRING | Comma-separated list of the hashes to chain with other Image Saver additional_hashes |
| a1111_params | STRING | Written parameters to the image metadata |