Save to S3
Skip the disk, get a public URL for every image
- images
- STRING
You know the drill: ComfyUI renders, you save the PNG to disk, then a separate script picks it up and uploads it somewhere so your bot, site, or API can actually use it. Save to S3 (class S3SaveNode, from the georgitsenov/ComfyUI-R2 pack) collapses that into one node. It takes the image tensor off the wire, encodes it to WebP in memory, uploads it straight to a Cloudflare R2 (or any S3-compatible) bucket, and hands you back a public URL as a JSON string. No local file ever touches your disk.
This is a niche thing, so let's be honest about when you want it: if you're saving images for yourself, the built-in Save Image node is fine and this is overkill. Reach for it when you're building automation - a Discord bot that answers with generated art, a headless ComfyUI box feeding a website, a batch job where each render needs to land at a URL. It's an output node in the truest sense: it never even shows you a preview in the queue. Everything goes up to the bucket.
How it works
The mechanism is small and easy to verify in the source. On node construction it creates a boto3 S3 client from six environment variables - no API key file, no node UI, no .env the node reads itself. At runtime it loops over the images batch, converts each tensor to a numpy array, encodes it as WebP at quality 95 via Pillow into a BytesIO buffer, generates a random 20-character alphanumeric filename with secrets, and calls put_object against your bucket with your configured prefix. Each image gets an entry in a JSON array with url and status.
The whole thing is OUTPUT_NODE=True, so it's the end of the line for your graph.
The inputs and outputs that matter
There is exactly one input: images (type IMAGE), the standard image tensor straight out of your sampler or VAE decode. Batch it - every image in the batch gets uploaded and gets its own URL.
The single output is a STRING that contains JSON like:
[{"url": "https://img-dev.fantasy.ai/vik_tests/ZFUSJbDRJb2d2ikevexX.webp", "status": "success"}]
Wire that output into a Show Text node to actually read the URLs in the UI, or leave it dangling - the upload happens regardless, since this is an output node.
Configuration, and where people get burned
All settings come from environment variables, read at node instantiation:
CLOUDFLARE_R2_ENDPOINT_URL=https://<account>.r2.cloudflarestorage.com
CLOUDFLARE_ACCESS_KEY_ID=
CLOUDFLARE_SECRET_ACCESS_KEY=
CLOUDFLARE_R2_URL=https://img-dev.fantasy.ai/ # public base URL
CLOUDFLARE_R2_BUCKET=fan-dev
CLOUDFLARE_R2_PREFIX=vik_tests/
The three things that bite beginners, all grounded in the shipped code:
- Set the env vars before ComfyUI starts, then restart. The client is built in
__init__, so the node only sees the environment that was present when ComfyUI launched. Export them in the same shell that runs ComfyUI (or a.envloader), not after it's already up. - Defaults are the author's own. If you leave
CLOUDFLARE_R2_ENDPOINT_URLempty, boto3 happily defaults to real AWS S3 - with your R2 keys - and fails. The bucket and public URL default tofan-devandimg-dev.fantasy.ai; change all of them or the upload goes to a bucket that isn't yours. - It's lossy WebP at q95 with a random filename. No PNG, no embedded workflow metadata, no way to name the file yourself, and a public URL only works if the bucket actually allows public read. If you need lossless output or provenance metadata, this node isn't it.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/georgitsenov/ComfyUI-R2
cd ComfyUI-R2
pip install -r requirements.txt
Then restart ComfyUI. The dependencies are light - boto3, Pillow, numpy, torch, requests - and there are no model downloads, so this is one of the friendlier installs in the ecosystem. You might find it via ComfyUI Manager by searching for "Save to S3"; if Manager can't find it (it's not on the official registry), the git clone above is the guaranteed path.
Troubleshooting
The README's advice is honest and it matches the code: check your env vars, your bucket permissions, and your endpoint. Upload failures print to the ComfyUI console rather than crashing the run - each image gets a "status": "failed" entry, so read the log. And if you've been wrestling with ComfyUI's dependency hell, take a second to audit any node that gets AWS-shaped credentials; the LLMVISION malware episode was a custom node too, so clone this one and read the 200 lines before trusting it with real keys. It's genuinely tiny, so the review is quick - and worth it for anything that touches your cloud account.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| STRING | STRING | — |