DHan-API-Image-gen
Nano Banana and GPT Image Without Leaving ComfyUI
- image
- references
- ref_image_1
- ref_image_2
- ref_image_3
- ref_image_4
- ref_image_5
- ref_image_6
- ref_image_7
- ref_image_8
- ref_image_9
- ref_image_10
- ref_image_11
- ref_image_12
- ref_image_13
- ref_image_14
- images
- log
Here's what the name hides: DHan-API-Image-gen doesn't generate anything on your GPU. It's a wrapper. You hand it a prompt and some images, it fires an HTTP request at Google or OpenAI, and the picture that comes back is a normal IMAGE tensor you can wire into your graph like any sampler's output. No checkpoint, no download, no VRAM - just a key and a network call.
Why this instead of Comfy's own official API Nodes? You bill Google or OpenAI directly with your own key rather than buying prepaid Comfy credits, both providers sit behind one node with one prompt field, and the match_input sizing exists to hand your exact dimensions back for downstream compositing.
The caveat up front: API-wrapper nodes are the category that already shipped credential-stealing malware once (ComfyUI_LLMVISION), and a node whose job is to hold your key and phone home is exactly where a malicious phone-home looks normal. This pack is tiny - four Python files, one commit - so read it before you give it a key, and never save a workflow with a key in the node field.
How it works
Pick a provider and the request splits two ways. Gemini models post to generativelanguage.googleapis.com/v1beta/interactions with the key in an x-goog-api-key header. OpenAI goes to api.openai.com/v1/images/edits as a multipart upload when any image is connected, or /v1/images/generations as JSON when you're doing plain text-to-image.
The Gemini path is the interesting one. It builds a deliberately ordered request: each reference gets a text block labelling it REFERENCE IMAGE n - DONOR REFERENCE ONLY, the primary image goes in last as PRIMARY IMAGE - EDIT TARGET / CANVAS, and your instruction follows. That ordering is why the model edits your canvas instead of redrawing a donor. Shorthand like "ref 2" gets normalised to "Reference Image 2", so referring to donors by number works.
The inputs that matter
provider, model, api_key and prompt are the required ones. Then the optional image - your canvas, the thing being edited - plus fourteen ref_image_N sockets, or a single references input if you're using the pack's RefStacker. Donors go in refs, the edit target goes in image.
Past that, the settings you'll actually touch:
aspect_ratio/resolution-match_inputis the default and the one worth understanding. On Gemini, the node edge-pads the primary image out to the nearest ratio the model supports (pixel replication, no scaling), requests that ratio explicitly, then strips the padding on the way back. On OpenAI it computes a custom GPT size near your aspect ratio - clamped to 3:1 and 3840 px on the long edge - then LANCZOS-resizes the result to your exact input dimensions. Compositing-friendly, but it is a resample.quality,background,output_format,output_compression- GPT Image only; the front-end hides them on Gemini.xhighandmaxbelong to the 2.5 models, so pair them withgpt-image-2.5-sunburstor-flare.num_images(1–10) andseed- the seed is real on Gemini. GPT Image has no seed parameter at all, so there it only exists to make ComfyUI re-run the node.thinking_modeandsearch_grounding- thinking is transmitted only forgemini-3.1-flash-image; grounding adds Google's web/image search tool to the request.debug_modewrites a sanitised report of what was actually sent, so reach for it before filing a bug.
Outputs are images (an IMAGE batch, one frame per returned image) and log (a STRING). Wire the log into a Show Text node; it names the model that ran and the size requested.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/DHan315/Comfyui-DHan-API-Image-gen
# restart ComfyUI
ComfyUI Manager finds it under "Comfyui-DHan-API-Image-gen". No models, no build step. Then paste a key into the node, or export GOOGLE_API_KEY / OPENAI_API_KEY before launching ComfyUI - a process that's already running won't see a new export.
Where people get burned
Wrong provider, wrong model, silent fallback. If the front-end JS hasn't loaded (stale browser cache) or you reopen a workflow saved on the other provider, the node doesn't error - it swaps in the first model of the selected provider and runs. Check the Model: line in the log.
negative_prompt isn't a negative prompt. There's no CFG here, so the text gets glued onto your prompt as Avoid: … (OpenAI) or AVOID: … (Gemini). A thirty-word negative stack steals attention from the real instruction.
Only the first frame of a batch is used. Primary and references alike are converted with tensor[0]. Wiring a 4-frame batch in sends one image, quietly.
Small sizing surprises. 0.5K silently becomes 1K on gemini-3-pro-image and gemini-2.5-flash-image, the panoramic ratios (4:1, 8:1 and friends) only exist on gemini-3.1-flash-image, and transparent plus jpeg gets you no alpha.
The key field clears itself. Switch provider and the node drops the key the first time you move to one with no saved value. Environment variables skip that dance entirely.
You can't loosen the filter. Whatever Google or OpenAI refuses stays refused - no weights to abliterate, no safety knob on the node.
And cost: ten images is ten billed images. The log reports usage when the endpoint returns it, but keeps no running total.
Inputs (32)
| Name | Type | Default | Description |
|---|---|---|---|
| provider | COMBO | 2 options: Nano Banana (Gemini), GPT Image (OpenAI) | |
| model | COMBO | 6 options: gemini-3.1-flash-image, gemini-3-pro-image, gemini-2.5-flash-image, gpt-image-2.5-sunburst, gpt-image-2.5-flare, gpt-image-2 | |
| api_key | STRING | — | |
| prompt | STRING | Professional photo edit | — |
| negative_prompt | STRING | — | |
| seed | INT | 00–18446744073709550000 | — |
| aspect_ratio | COMBO | 16 options: match_input, auto, 1:1, 16:9, 9:16, 4:3, +10 | |
| resolution | COMBO | 4 options: 0.5K, 1K, 2K, 4K | |
| num_images | INT | 11–10 | — |
| quality | COMBO | 6 options: auto, low, medium, high, xhigh, max | |
| background | COMBO | 3 options: auto, opaque, transparent | |
| output_format | COMBO | 3 options: png, jpeg, webp | |
| output_compression | INT | 900–100 | — |
| thinking_mode | COMBO | 2 options: Minimal, High | |
| search_grounding | COMBO | 2 options: Disabled, Enabled | |
| debug_mode | COMBO | 3 options: Off, Summary, Full Request | |
| imageopt | IMAGE | — | |
| referencesopt | API_IMAGE_REFS | — | |
| ref_image_1opt | IMAGE | — | |
| ref_image_2opt | IMAGE | — | |
| ref_image_3opt | IMAGE | — | |
| ref_image_4opt | IMAGE | — | |
| ref_image_5opt | IMAGE | — | |
| ref_image_6opt | IMAGE | — | |
| ref_image_7opt | IMAGE | — | |
| ref_image_8opt | IMAGE | — | |
| ref_image_9opt | IMAGE | — | |
| ref_image_10opt | IMAGE | — | |
| ref_image_11opt | IMAGE | — | |
| ref_image_12opt | IMAGE | — | |
| ref_image_13opt | IMAGE | — | |
| ref_image_14opt | IMAGE | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| log | STRING | — |