DS DevShelfHub Projects · AI tools
Cheatsheets / ComfyUI
Cheatsheet · AI frameworks

ComfyUI Cheatsheet: Nodes, Workflows and API Reference

By DevShelfHub

Node graph, core nodes, samplers, KSampler, ControlNet, LoRA, workflow JSON, REST/WebSocket API, custom nodes, Manager — the node-based SD app.

105 items 8 min Nodes Workflows API

Start hereQuick start · 6 you’ll reach for daily

Launchpython main.py
Add nodedouble-click canvas → search
Queue runQueue Prompt (Ctrl+Enter)
Save graphSave / Save (API Format)
Run via HTTPPOST /prompt
Install nodesManager → Install Custom Nodes

Target versions · paceVersions

Targets: ComfyUI (main, May 2026) torch ≥ 2.2 ComfyUI-Manager ≥ 3.0 python ≥ 3.10

ComfyUI is a node-graph runtime for diffusion models. Same models as Stable Diffusion via diffusers, but you wire components on a canvas instead of writing Python. The repo moves fast — expect breaking changes when you pull main. ComfyUI-Manager is the de-facto package manager; install it first, then add custom node packs from there. The server speaks plain HTTP + WebSocket — any language can drive it.

Install · launchSetup

bash
# Clone + Python venv install
git clone https://github.com/comfyanonymous/ComfyUI.git
cd ComfyUI
python -m venv venv && source venv/bin/activate

# NVIDIA (CUDA 12)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install -r requirements.txt

# Apple Silicon — Metal backend
pip install torch torchvision torchaudio          # nightly-MPS builds are usually fine
pip install -r requirements.txt

# Drop checkpoints / LoRAs / VAEs into:
#   models/checkpoints/   models/loras/   models/vae/   models/controlnet/

# Launch (web UI at http://127.0.0.1:8188)
python main.py
# Listen on LAN:        python main.py --listen 0.0.0.0
# Low VRAM (<6GB):      python main.py --lowvram
# CPU only (slow):      python main.py --cpu
# Auto-launch browser:  python main.py --auto-launch

Canvas controlsUI & hotkeys

Double-click canvasSearch and add a node.
Right-click nodeProperties, bypass, mute, pin, color.
Ctrl+Enter / Queue PromptRun the workflow.
Ctrl+drag selectionGroup multiselect.
Ctrl+GGroup selected nodes (reusable subgraph).
Ctrl+MMute / unmute a node — skip without disconnecting.
Ctrl+BBypass — pass input through as if the node weren’t there.
Ctrl+S / SaveSave the canvas as JSON.
Save (API Format)Different file. Use this for HTTP / scripting.
Drag a PNG onto the canvasLoads the workflow embedded in the image metadata.
Q / Queue panelSee pending and historical runs.

Where files liveFolders & models

models/checkpoints/.safetensors / .ckpt — SD, SDXL, SD3, Flux.
models/loras/LoRA / LoCon files. Picked up by LoraLoader nodes.
models/vae/Standalone VAE files.
models/clip/, models/clip_vision/Text encoders, vision encoders for IP-Adapter.
models/controlnet/ControlNet weights.
models/upscale_models/ESRGAN / Real-ESRGAN models for the Upscale nodes.
input/Files referenced by LoadImage nodes.
output/Where SaveImage writes results.
custom_nodes/Each subfolder is a node pack. Manager installs here.
extra_model_paths.yamlMap external model folders (Automatic1111 share, NAS) into ComfyUI.

The starter setCore nodes

Model + conditioning

CheckpointLoaderSimpleOutputs MODEL, CLIP, VAE from one .safetensors.
CLIPTextEncodePrompt → CONDITIONING. Use two: positive and negative.
CLIPTextEncodeSDXLDual-encoder version for SDXL with refiner-style params.
LoraLoaderStack on top of MODEL+CLIP. Chain for multi-LoRA.
ControlNetLoader, ControlNetApply, ControlNetApplyAdvancedAdd a control map. Advanced = strength + start/end %.

Latents + sampling

EmptyLatentImageBlank latent at WxH. Source for txt2img.
VAEEncodeImage → latent. Source for img2img.
VAEDecodeLatent → image. Almost always the last step.
KSamplerThe denoising loop. Takes model, positive, negative, latent.
KSamplerAdvancedSame but with start_at_step / end_at_step for base+refiner chains.
LatentUpscale, LatentUpscaleByResize a latent before further sampling. Hi-res fix.

Images + I/O

LoadImageRead from input/. Outputs IMAGE + MASK.
SaveImageWrite PNG to output/. Embeds the workflow in metadata.
PreviewImageShow in UI without writing to disk.
ImageUpscaleWithModelRun an ESRGAN-family upscaler.
ImageCompositeMasked, ImagePadForOutpaintCompositing helpers for inpaint / outpaint flows.

Inside KSamplerSamplers & schedulers

sampler_name="dpmpp_2m"Preferred DPM++ 2M. Strong default.
sampler_name="euler_ancestral"Adds noise each step. Stylistic, varied.
sampler_name="uni_pc"Great at low step counts.
sampler_name="lcm"Pair with LCM-LoRA + 4–8 steps.
scheduler="karras"Karras sigmas. Sharper outputs at low steps.
scheduler="exponential" / "normal" / "simple"Other schedule shapes. Karras is the safe default.
steps=20…30SDXL plateaus around 30. Distilled models work at 4–8.
cfg=5…8Classifier-free guidance. Higher = follow prompt more. SD3/Flux want lower (~3.5).
denoise=1.0Full denoise = txt2img. 0.4–0.7 = img2img strength.
seed=-1-1 = random each run. Fix for reproducibility.

Shape on the wireWorkflow JSON

Save (API Format) emits a flat dict keyed by node id. Each node has a class_type and an inputs map. Wires are encoded as ["from_node_id", output_slot_index].

"3": {"class_type":"KSampler","inputs":{…}}Node entry. Key = id, class_type = node name.
"seed": 42, "steps": 25, "cfg": 7.0Scalar inputs — same widgets you see in the UI.
"model": ["4", 0]Wire input. "Read output 0 of node 4." Order matters.
CheckpointLoader outputs: MODEL(0), CLIP(1), VAE(2)Use those indices when wiring elsewhere.
Different export: Save (Default)UI-only format with positions and links. Not accepted by the API.
Embed in PNGSaveImage writes the canvas JSON into the PNG’s parameters chunk.

Minimal txt2img workflow

json
{
  "3": {
    "class_type": "KSampler",
    "inputs": {
      "seed": 42,
      "steps": 25,
      "cfg": 7.0,
      "sampler_name": "dpmpp_2m",
      "scheduler": "karras",
      "denoise": 1.0,
      "model":           ["4", 0],
      "positive":        ["6", 0],
      "negative":        ["7", 0],
      "latent_image":    ["5", 0]
    }
  },
  "4": {
    "class_type": "CheckpointLoaderSimple",
    "inputs": { "ckpt_name": "sd_xl_base_1.0.safetensors" }
  },
  "5": {
    "class_type": "EmptyLatentImage",
    "inputs": { "width": 1024, "height": 1024, "batch_size": 1 }
  },
  "6": {
    "class_type": "CLIPTextEncode",
    "inputs": { "text": "cinematic photo of an old fisherman", "clip": ["4", 1] }
  },
  "7": {
    "class_type": "CLIPTextEncode",
    "inputs": { "text": "blurry, text, watermark",            "clip": ["4", 1] }
  },
  "8": {
    "class_type": "VAEDecode",
    "inputs": { "samples": ["3", 0], "vae": ["4", 2] }
  },
  "9": {
    "class_type": "SaveImage",
    "inputs": { "filename_prefix": "out", "images": ["8", 0] }
  }
}

Drive it from codeHTTP & WebSocket API

POST /promptQueue a workflow. Body: {"prompt": …, "client_id": …}. Returns prompt_id.
GET /history/{prompt_id}Status + outputs (filenames) of a run.
GET /historyFull history. Heavy — prefer per-id.
GET /queuePending + running prompts.
POST /interruptStop the current run.
POST /queue { "clear": true }Drain pending runs.
POST /upload/imageMultipart upload to input/. Returns name to pass into LoadImage.
GET /view?filename=&subfolder=&type=Fetch a generated image. type = output / temp / input.
GET /object_infoSchema of every installed node. Great for editor tooling.
GET /system_statsGPU / RAM utilization.
WS /ws?clientId=…Live events: progress, executing, executed, execution_error.

Queue + poll

python
import json, urllib.request, uuid

SERVER = "127.0.0.1:8188"
CLIENT_ID = str(uuid.uuid4())

# 1 · Load a workflow exported as API JSON (Settings → "Save (API Format)")
with open("workflow_api.json") as f:
    workflow = json.load(f)

# 2 · Override values just like editing nodes in the UI
workflow["6"]["inputs"]["text"] = "a wide-angle photo of a foggy harbour at dawn"
workflow["3"]["inputs"]["seed"] = 123_456

# 3 · Queue the prompt
req = urllib.request.Request(
    f"http://{SERVER}/prompt",
    data=json.dumps({"prompt": workflow, "client_id": CLIENT_ID}).encode(),
    headers={"Content-Type": "application/json"},
)
prompt_id = json.loads(urllib.request.urlopen(req).read())["prompt_id"]
print("queued:", prompt_id)

# 4 · Poll history → get the saved file
hist = json.loads(
    urllib.request.urlopen(f"http://{SERVER}/history/{prompt_id}").read()
)[prompt_id]
for node_out in hist["outputs"].values():
    for img in node_out.get("images", []):
        url = f"http://{SERVER}/view?filename={img['filename']}&subfolder={img['subfolder']}&type={img['type']}"
        print("image:", url)

Write · shareCustom nodes

custom_nodes/<pack>/__init__.pyEntry file. ComfyUI imports it on startup.
NODE_CLASS_MAPPINGS = {"NodeName": NodeClass}Register classes by their canonical name.
NODE_DISPLAY_NAME_MAPPINGSOptional. Pretty name in the search palette.
@classmethod INPUT_TYPES(cls)Declare typed inputs. Types: IMAGE, LATENT, MODEL, CLIP, VAE, INT, FLOAT, STRING, CONDITIONING, MASK…
RETURN_TYPES, RETURN_NAMESTuple of output types and labels.
FUNCTION = "run"Name of the instance method ComfyUI calls.
CATEGORY = "image/filters"Where it sits in the search tree.
IS_CHANGED = staticmethod(…)Override cache invalidation. Return changing value to force re-run.
requirements.txt next to __init__.pyManager installs deps from this on add.

Minimal node

python
# custom_nodes/my_pack/__init__.py
class GrayscaleNode:
    """Convert an image batch to grayscale."""

    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "image":    ("IMAGE",),
                "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
            },
        }

    RETURN_TYPES  = ("IMAGE",)
    RETURN_NAMES  = ("image",)
    FUNCTION      = "run"
    CATEGORY      = "image/filters"

    def run(self, image, strength):
        # image is a torch.Tensor of shape [B, H, W, C], values in 0..1
        gray = image.mean(dim=-1, keepdim=True).repeat(1, 1, 1, 3)
        return (gray * strength + image * (1 - strength),)

NODE_CLASS_MAPPINGS = {"Grayscale": GrayscaleNode}
NODE_DISPLAY_NAME_MAPPINGS = {"Grayscale": "Grayscale (mine)"}

Install · updateManager & ecosystem

ComfyUI-ManagerDrop into custom_nodes/ first. UI button installs everything else.
Manager → Install Custom NodesBrowse + install community packs without git clone.
Manager → Install MissingLoad a workflow with unknown nodes → auto-install them.
Manager → Model ManagerBrowse + download checkpoints / VAEs / LoRAs from Hub + Civitai.
ComfyUI-Impact-PackDetailer / face-fix / segmentation. Most workflows use it.
ComfyUI-AnimateDiff-EvolvedVideo / animation pipeline.
ComfyUI-IPAdapter-PlusReference-image conditioning (style + face).
rgthree-comfyPower-user nodes: muting groups, fast bypassing, image comparer.
was-node-suite-comfyuiHundreds of utility nodes — math, text, conditioning, debugging.

WebSocket-driven run · ~30 linesEnd-to-end · WebSocket client

Subscribe to progress, queue a workflow, stream node-by-node events until done, fetch the saved image URL. Same shape as any other event-driven backend — treat ComfyUI as a microservice.

python
import json, uuid, urllib.parse, urllib.request, websocket

SERVER = "127.0.0.1:8188"
CLIENT_ID = str(uuid.uuid4())

with open("workflow_api.json") as f:
    workflow = json.load(f)
workflow["6"]["inputs"]["text"] = "a tea stall in monsoon Mumbai, cinematic, 35mm"

# Subscribe to progress BEFORE queuing the prompt
ws = websocket.WebSocket()
ws.connect(f"ws://{SERVER}/ws?clientId={CLIENT_ID}")

req = urllib.request.Request(
    f"http://{SERVER}/prompt",
    data=json.dumps({"prompt": workflow, "client_id": CLIENT_ID}).encode(),
    headers={"Content-Type": "application/json"},
)
prompt_id = json.loads(urllib.request.urlopen(req).read())["prompt_id"]

# Stream events: progress %, current node, completion
while True:
    msg = ws.recv()
    if not isinstance(msg, str):
        continue
    event = json.loads(msg)
    data = event["data"]
    if event["type"] == "progress":
        print(f"step {data['value']} / {data['max']}")
    elif event["type"] == "executing" and data["node"] is None and data["prompt_id"] == prompt_id:
        break       # done

# Fetch the result
hist = json.loads(urllib.request.urlopen(f"http://{SERVER}/history/{prompt_id}").read())
img = next(iter(hist[prompt_id]["outputs"].values()))["images"][0]
q   = urllib.parse.urlencode(img)
print("done:", f"http://{SERVER}/view?{q}")

Best practiceGood to know

Bypass (Ctrl+B) is your A/B button. Toggle a LoRA, upscale, or refiner on and off without disconnecting wires. Faster than rewiring; preserves the rest of the graph.
SaveImage embeds the workflow in the PNG. Drag any output PNG back onto the canvas to restore the exact graph that made it. Best provenance any image tool ships.
Use Groups + Subgraphs for reuse. Wrap a stable section (load checkpoint, encode prompt, sample) into a group, then copy across workflows. Subgraphs (Ctrl+G) collapse the visual clutter.

Common trapsWatch out for

Default-format JSON ≠ API-format JSON. The HTTP /prompt endpoint only accepts the API export. Sending the UI save format returns 400. Pick "Save (API Format)" specifically when scripting.
Custom node packs break on git pull. Pulling main can land a torch upgrade that breaks a third-party pack. Check the Manager’s "needs update" badge after every pull — or pin ComfyUI to a known-good commit.
Cache reuse means changing nothing visible can still skip execution. ComfyUI hashes node inputs. If you mutate a file on disk without changing the node’s filename field, the run is cached and returns the old image. Toggle bypass or change seed to force re-run.

Go deeperSee also

ComfyUI FAQ

What is ComfyUI?

ComfyUI is a node-based graphical interface for running Stable Diffusion and other diffusion models. Instead of writing Python, you wire together nodes on a canvas — each node handles one step such as loading a model, encoding a prompt, sampling, or decoding an image. The same model weights used by the diffusers library work in ComfyUI.

What is KSampler in ComfyUI?

KSampler is the core denoising node. It accepts a MODEL, positive and negative CONDITIONING, a LATENT, and parameters like steps, cfg, sampler_name (e.g. dpmpp_2m), and scheduler (e.g. karras). KSamplerAdvanced adds start_at_step and end_at_step for multi-pass or refiner workflows.

How does ControlNet work in ComfyUI?

Connect a ControlNetLoader to a ControlNetApply node, supply a preprocessed hint image (depth, canny, pose, etc.) and a conditioning strength, and pipe the result into KSampler as the positive conditioning. Each control type needs its own matching ControlNet weight file.

How do I use the ComfyUI API?

Save your workflow in API Format (not the regular Save), then POST the JSON to http://localhost:8021/prompt. Poll /history/{prompt_id} to check status, or open a WebSocket on /ws?clientId=<id> to receive real-time progress events. Any language that can make HTTP requests can drive ComfyUI this way.

What is ComfyUI-Manager?

ComfyUI-Manager is the de-facto package manager for ComfyUI. It adds a Manager menu to the UI where you can install, update, and remove custom node packs, download missing models, and check for ComfyUI core updates. Install it by cloning it into the custom_nodes/ directory.

Is ComfyUI free and open source?

Yes. ComfyUI is MIT-licensed and free to use locally. You supply your own model weights (available from Hugging Face or Civitai). There is no cloud subscription required — it runs entirely on your hardware, though a CUDA-capable GPU is recommended for practical generation speeds.