Jesse A. Green / notebook

ComfyUI Administrator & Developer Guide

Generated May 2026 from Comfy-Org/ComfyUI at c033bbf

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Architecture Overview

ComfyUI is a modular, node-based generative AI platform. The server process is a single Python aiohttp application. A browser frontend communicates with it over HTTP and WebSocket. A background thread runs the execution queue. The entire system is stateless between runs — all persistence is file system (models, outputs) or SQLite (assets, user data).

Browser (Frontend)
    │
    ├── HTTP/REST  ──────────────────────────────┐
    └── WebSocket (real-time events) ────────────┤
                                                 ▼
                                      PromptServer (aiohttp, :8188)
                                        │
                                        ├── /prompt, /queue, /history
                                        ├── /ws  (WebSocket hub)
                                        ├── /settings, /users, /userdata
                                        ├── /models, /embeddings, /view
                                        ├── /object_info
                                        ├── /internal/   (InternalRoutes)
                                        ├── /api/assets/ (AssetRoutes, opt-in)
                                        ├── /api/jobs/   (Jobs API)
                                        └── FrontendManager (static assets)
                                                 │
                                        PromptQueue (asyncio + thread)
                                                 │
                                        prompt_worker (background thread)
                                                 │
                                        PromptExecutor
                                           ├── comfy_execution/graph.py
                                           ├── comfy_execution/caching.py
                                           └── comfy/ (ML engine)

Note

The queue worker runs in a dedicated background thread started at server boot. All ML compute happens in this thread; the aiohttp event loop remains free for HTTP/WebSocket traffic. This design allows real-time progress updates while inference runs without blocking the server.

Component Map

Component Path Files Responsibility
Entry points . 13 main.py, server.py, nodes.py, execution.py, folder_paths.py
ML engine comfy/ 48 Model loading, sampling, memory management, CLIP, VAE, LoRA, hooks, ops
Built-in nodes comfy_extras/ 99 Samplers, image processing, video, audio, 3D, IPAdapter, ControlNet, etc.
Execution engine comfy_execution/ 8 Graph traversal, caching, validation, job status, progress tracking
Versioned node API comfy_api/ 4+ Stable API surface for node authors (latest, v0_0_2, v0_0_1) + feature flags
External API nodes comfy_api_nodes/ 32+9 BFL, Gemini, OpenAI, Kling, and other cloud integrations
Application layer app/ 8 Settings, frontend, user, model, custom node, subgraph managers
Asset management app/assets/ ~20 SQLite-backed asset tracking, scanning, tagging (requires --enable-assets)
Internal API api_server/ 4 /internal/ routes, terminal service, file operations
Middleware middleware/ 2 Cache-control headers
Custom node config comfy_config/ 2 pyproject.toml parser for custom node packages
Utilities utils/ 4 Extra config loading, install helpers, JSON, MIME types
Database migrations alembic_db/ 4 SQLite schema (assets, asset references, metadata job ID)
Workflow templates blueprints/ 38 Packaged workflow JSON files
Tests tests/ 7 Integration tests (execution, inference, comparison)
Unit tests tests-unit/ ~50 Pytest unit test suite

Request Lifecycle

  1. Browser POSTs a workflow graph to /prompt.
  2. PromptServer.post_prompt() validates the origin header, parses the JSON body, and calls queue.put().
  3. PromptQueue assigns a prompt_id and enqueues.
  4. The background prompt_worker thread dequeues via get() (blocking).
  5. PromptExecutor.execute() topologically sorts the graph (DynamicPrompt), resolves cache hits, and runs each node in dependency order.
  6. After each node, a WebSocket executing event is broadcast. On output image nodes, a PREVIEW_IMAGE binary event is sent.
  7. On completion, the result is stored in history and an executed event is broadcast.

WebSocket Event Protocol

Clients connect to /ws?clientId=<uuid>. Feature flag negotiation happens on the first server→client message.

Event Type Direction Payload
status Server→Client Queue remaining count, exec info
executing Server→Client {"node": "<id>", "prompt_id": "<id>"}
progress Server→Client {"value": N, "max": M, "prompt_id": ...}
executed Server→Client {"node": "<id>", "output": {...}, "prompt_id": ...}
PREVIEW_IMAGE Server→Client Binary: 4-byte type prefix + JPEG/PNG bytes
PREVIEW_IMAGE_WITH_METADATA Server→Client Binary: preview + JSON metadata
TEXT Server→Client Binary text event

2. System Requirements

Minimum Hardware

Component Minimum Recommended
GPU NVIDIA GTX 10xx (8 GB VRAM) RTX 30xx / 40xx / 50xx (16+ GB VRAM)
CPU 4-core x86-64 8+ core
RAM 16 GB 32+ GB
Storage 20 GB free 200+ GB (models are large)

Note

ComfyUI runs on CPU (--cpu flag) but is extremely slow. AMD (ROCm), Intel Arc (IPEX), Apple Silicon (MPS), and Huawei Ascend (CANN) are all supported with their respective driver stacks.

GPU Platform Matrix

Platform Flag / Mechanism Notes
NVIDIA CUDA Default (no flag needed) Best support, all precision modes
NVIDIA CUDA (specific GPU) --cuda-device N Multi-GPU selection
AMD ROCm --default-device cuda with ROCm PyTorch Requires ROCm-enabled torch build
Intel Arc / Xe --oneapi-device-selector ... IPEX required; --disable-ipex-optimize to skip
DirectML (Windows) --directml Fallback for non-CUDA Windows GPUs
Apple MPS Auto-detected macOS with Apple Silicon
CPU fallback --cpu Slow; useful for debugging
Huawei Ascend NPU --cuda-device N + Ascend SDK ASCEND_RT_VISIBLE_DEVICES set automatically

Python & Package Requirements

Package Version Purpose
Python ≥ 3.10 (3.12+ recommended) Runtime
torch latest stable ML framework
torchvision matching torch Image ops
torchaudio matching torch Audio ops
torchsde latest SDE samplers
aiohttp ≥ 3.11.8 HTTP server + WebSocket
sqlalchemy latest ORM
alembic latest DB migrations
pillow latest Image I/O
transformers ≥ 4.50.3 HuggingFace model loading
safetensors ≥ 0.4.2 Safe model format
numpy ≥ 1.25.0 Numerical computing
einops latest Tensor manipulation
scipy latest Scientific computing
av ≥ 14.2.0 Video processing (FFmpeg wrapper)
blake3 latest Fast file hashing
spandrel latest Upscale/restoration model loader
kornia ≥ 0.7.1 Computer vision transforms
pydantic ~2.0 Data validation (API layer)
comfy-aimdo ≥ 0.2.12 Dynamic VRAM management
comfy-kitchen ≥ 0.2.8 Shared utility functions
simpleeval ≥ 1.0.0 Safe math expression evaluation

Important

PyTorch version must match your CUDA toolkit. Installing mismatched builds silently falls back to CPU. Always install from the official PyTorch matrix at pytorch.org/get-started/locally/. Verify GPU detection with python -c "import torch; print(torch.cuda.is_available())" before running the server.

Operating System Support

OS Status Notes
Windows 10/11 Fully supported --windows-standalone-build for packaged installs
Ubuntu 20.04+ / Debian Fully supported Primary development target
macOS 12+ (Apple Silicon) Supported MPS backend; some nodes CPU-only
macOS (Intel) Partial No GPU acceleration
Other Linux Best-effort ROCm distros may need custom PyTorch builds

3. Configuration Reference

Command-Line Interface (All 94 Flags)

Network

Flag Default Description
--listen [IP] 127.0.0.1 Bind address. Use 0.0.0.0 to listen on all interfaces
--port PORT 8188 HTTP port
--tls-keyfile PATH — Path to TLS private key (enables HTTPS)
--tls-certfile PATH — Path to TLS certificate
--enable-cors-header [ORIGIN] — Enable CORS header. Omit value for *
--max-upload-size MB 100 Maximum upload file size in megabytes
--enable-compress-response-body off Enable gzip response compression

Important

Setting --listen 0.0.0.0 without authentication exposes the server to your entire network. ComfyUI has no built-in authentication. Use a reverse proxy with auth or strict firewall rules before opening to a LAN.

Directories

Flag Default Description
--base-directory PATH CWD Root directory for all relative paths
--extra-model-paths-config PATH — YAML file mapping extra model directories
--output-directory PATH ./output Generated image/video output directory
--temp-directory PATH ./temp Temporary files (intermediate renders)
--input-directory PATH ./input Input file upload directory
--user-directory PATH ./user Per-user settings and workflow storage

Launch Behavior

Flag Default Description
--auto-launch off Open browser automatically on start
--disable-auto-launch — Suppress auto-launch (standalone builds)
--windows-standalone-build off Windows packaged build mode

GPU / Device Selection

Flag Default Description
--cuda-device N — Select CUDA device by index
--default-device DEVICE auto Set default PyTorch device
--cuda-malloc on Use CUDA memory allocator
--disable-cuda-malloc — Disable CUDA memory allocator
--directml [N] — Use DirectML device (Windows fallback)
--oneapi-device-selector FILTER — Intel OneAPI device filter string
--disable-ipex-optimize — Skip ipex.optimize() call
--supports-fp8-compute auto Declare FP8 compute capability

Precision Control

Flag Scope Description
--force-fp32 Global Force FP32 everywhere
--force-fp16 Global Force FP16 everywhere
--fp32-unet / --fp64-unet UNet UNet in FP32 / FP64
--bf16-unet / --fp16-unet UNet UNet in BF16 / FP16
--fp8_e4m3fn-unet / --fp8_e5m2-unet / --fp8_e8m0fnu-unet UNet UNet in FP8 variants
--fp16-vae / --fp32-vae / --bf16-vae VAE VAE precision
--cpu-vae VAE Run VAE decode on CPU
--fp8_e4m3fn-text-enc / --fp8_e5m2-text-enc Text encoder Text encoder FP8 variants
--fp16-text-enc / --fp32-text-enc / --bf16-text-enc Text encoder Text encoder precision
--fp16-intermediates Intermediate tensors Use FP16 for intermediate values
--force-channels-last Memory layout Force channels-last tensor layout

VRAM Management

Flag Description
--gpu-only Keep everything on GPU, never offload
--highvram Aggressive VRAM use; slower model transitions
--normalvram Default behavior
--lowvram Offload UNet activations to CPU when needed
--novram Offload everything; only run operations on GPU
--cpu No GPU; all compute on CPU
--reserve-vram GB Reserve this many GB for other applications
--async-offload Enable async CPU offload streams
--disable-async-offload Disable async offload
--disable-dynamic-vram Disable comfy-aimdo dynamic management
--enable-dynamic-vram Force enable dynamic VRAM
--force-non-blocking Force non-blocking CUDA transfers
--disable-smart-memory Disable intelligent model retention in VRAM

Attention Mechanism

Flag Description
--use-split-cross-attention Split cross-attention (memory savings)
--use-quad-cross-attention Quad split cross-attention
--use-pytorch-cross-attention Force PyTorch native attention
--use-sage-attention Use Sage Attention (if installed)
--use-flash-attention Use Flash Attention (if installed)
--disable-xformers Do not use xformers even if available
--force-upcast-attention Always upcast attention to FP32
--dont-upcast-attention Never upcast attention

Caching Strategy

Flag Description
--cache-classic Classic LRU-style cache (default)
--cache-lru N LRU cache keeping N entries per cache key
--cache-none Disable node output caching entirely
--cache-ram RAM-pressure-aware cache (evicts under memory pressure)

Preview Image

Flag Default Description
--preview-method METHOD auto Preview encoding: none, auto, latent2rgb, taesd
--preview-size PX 512 Maximum preview dimension in pixels

Performance Tuning

Flag Description
--fast Enable experimental speed optimizations
--deterministic Reproducible outputs (disables non-deterministic ops; slower)
--disable-pinned-memory Disable pinned (page-locked) CPU memory allocations
--mmap-torch-files Memory-map model files instead of loading fully
--disable-mmap Disable memory mapping
--default-hashing-function ALGO Hash function for file change detection (blake3, sha256, md5)

Custom Node Manager

Flag Description
--enable-manager Enable the custom node manager UI
--disable-manager-ui Disable manager UI but keep backend
--enable-manager-legacy-ui Use legacy manager interface

Custom Node Loading

Flag Description
--disable-all-custom-nodes Skip loading any custom node packages
--whitelist-custom-nodes NAME [NAME...] Load only listed custom node packages
--disable-api-nodes Disable external API node integrations

Miscellaneous

Flag Default Description
--multi-user off Enable per-user storage isolation
--verbose [LEVEL] INFO Log verbosity (DEBUG, INFO, WARNING, ERROR)
--log-stdout off Print logs to stdout in addition to the log buffer
--disable-metadata off Do not embed metadata in output PNG files
--front-end-version VER bundled Use a specific frontend version
--front-end-root PATH bundled Serve frontend from local path (dev override)
--comfy-api-base URL — Base URL for API node external calls
--database-url URL SQLite at user/comfy.db Database connection string
--enable-assets off Enable the asset management system
--dont-print-server off Suppress server start banner
--quick-test-for-ci off Start, validate nodes, and immediately exit (CI use)

extra_model_paths.yaml

Maps additional model directories from other UIs or shared network shares. Place at the project root or pass via --extra-model-paths-config.

# Map models from another WebUI install
automatic1111:
    base_path: /path/to/stable-diffusion-webui
    checkpoints: models/Stable-diffusion
    vae: models/VAE
    loras: models/Lora
    embeddings: embeddings
    hypernetworks: models/hypernetworks
    controlnet: models/ControlNet

# Map from a shared NAS
nas_models:
    base_path: /mnt/nas/ai-models
    checkpoints: checkpoints
    upscale_models: upscalers
    clip: clip

Supported folder keys: checkpoints, vae, loras, embeddings, hypernetworks, controlnet, clip, clip_vision, diffusers, gligen, upscale_models, style_models, photomaker, and any custom folder registered by node packages.

Model Directory Structure

models/
├── checkpoints/       Full model checkpoints (SD 1.x, SDXL, Flux, etc.)
├── clip/              CLIP text encoder weights
├── clip_vision/       CLIP vision encoder weights
├── configs/           Model architecture config YAMLs
├── controlnet/        ControlNet models
├── diffusers/         HuggingFace diffusers format models
├── embeddings/        Textual inversion embeddings
├── gligen/            GLIGEN spatial grounding models
├── hypernetworks/     Hypernetwork weights
├── loras/             LoRA and LyCORIS weights
├── photomaker/        PhotoMaker identity weights
├── style_models/      Style reference models
├── unet/              Standalone UNet weights (Flux, etc.)
├── upscale_models/    ESRGAN, RealESRGAN, SwinIR upscalers
├── vae/               VAE weights
├── vae_approx/        Approximate VAE for previews (TAESD)
└── ...                Additional categories registered by custom nodes

Environment Variables

Auto-Set by main.py

Variable Value Purpose
HF_HUB_DISABLE_TELEMETRY 1 Disable HuggingFace Hub telemetry
DO_NOT_TRACK 1 Global analytics opt-out
TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL 1 ROCm AoTriton experimental features
CUDA_VISIBLE_DEVICES From --cuda-device NVIDIA GPU selection
HIP_VISIBLE_DEVICES From --cuda-device AMD GPU selection
ASCEND_RT_VISIBLE_DEVICES From --cuda-device Huawei Ascend NPU selection
ONEAPI_DEVICE_SELECTOR From flag Intel OneAPI device filter
CUBLAS_WORKSPACE_CONFIG :4096:8 When --deterministic is set
OCL_SET_SVM_SIZE 262144 ROCm SVM heap size
MIMALLOC_PURGE_DELAY 0 Windows memory purge tuning

User-Configurable

Variable Default Purpose
PYTORCH_CUDA_ALLOC_CONF — Fine-tune PyTorch CUDA allocator (e.g., max_split_size_mb:512)
COMFYUI_SERVER http://localhost:8988 Server URL for integration tests
DISPLAY system X11 display for GLSL nodes
WAYLAND_DISPLAY system Wayland display for GLSL nodes

Database Configuration

By default, SQLite is created at <user-directory>/comfy.db. Override with --database-url:

# Default (SQLite, auto-created)
python main.py

# Custom SQLite path
python main.py --database-url sqlite:////data/comfyui.db

# In-memory (tests only — data lost on exit)
python main.py --database-url sqlite:///:memory:

The SQLite database operates in WAL (Write-Ahead Logging) mode. Foreign keys are enforced. Three Alembic migration versions are applied automatically on startup:

  1. assets — initial asset table
  2. merge_to_asset_references — separate asset references table
  3. add_metadata_job_id — metadata and job tracking columns

4. Deployment & Scaling

Bare-Metal Single-Node (Standard)

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

# Start server (local access only)
python main.py

# Start server (LAN access, custom port)
python main.py --listen 0.0.0.0 --port 8188

# High-VRAM workstation
python main.py --highvram --listen 0.0.0.0

# Low-VRAM machine (8 GB)
python main.py --lowvram --preview-method latent2rgb

Reverse Proxy (Nginx)

For production or shared access, front ComfyUI with nginx. WebSocket proxying is required.

server {
    listen 443 ssl;
    server_name comfy.example.com;

    ssl_certificate     /etc/ssl/certs/comfy.crt;
    ssl_certificate_key /etc/ssl/private/comfy.key;

    # Increase for model uploads
    client_max_body_size 2G;

    location / {
        proxy_pass http://127.0.0.1:8188;
        proxy_http_version 1.1;

        # WebSocket upgrade
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    }
}

Warning

Do not expose ComfyUI directly to the public internet without authentication middleware. The application has no built-in access control; any visitor can submit arbitrary generation jobs, read outputs, and access uploaded files.

Docker

ComfyUI has no official Docker image, but the community pattern is straightforward:

FROM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04

RUN apt-get update && apt-get install -y python3.12 python3-pip git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

RUN pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 \
 && pip install -r requirements.txt

EXPOSE 8188

CMD ["python", "main.py", "--listen", "0.0.0.0", \
     "--output-directory", "/data/output", \
     "--input-directory", "/data/input", \
     "--user-directory", "/data/user"]
# docker-compose.yml
services:
  comfyui:
    build: .
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=0
    ports:
      - "8188:8188"
    volumes:
      - ./models:/app/models
      - comfy_data:/data
    restart: unless-stopped

volumes:
  comfy_data:

Tip

Mount the models/ directory as a bind volume so model weights persist across container rebuilds. Model files are typically tens of gigabytes and must never be baked into the image layer.

Multi-GPU Setup

ComfyUI currently supports one GPU per server process. To utilize multiple GPUs:

  • Run one server process per GPU with different ports and --cuda-device N.
  • Load-balance with nginx upstream across the ports.
  • Each server has an independent queue and VRAM pool.
# Process for GPU 0
python main.py --cuda-device 0 --port 8188

# Process for GPU 1
python main.py --cuda-device 1 --port 8189

Windows Standalone Build

The official Windows release uses --windows-standalone-build. This flag adjusts path resolution and enables bundled Python. The run_nvidia_gpu.bat launcher sets this flag automatically.

Systemd Service (Linux)

[Unit]
Description=ComfyUI Generative AI Server
After=network.target

[Service]
Type=simple
User=comfy
WorkingDirectory=/opt/comfyui
ExecStart=/opt/comfyui/venv/bin/python main.py \
    --listen 127.0.0.1 \
    --port 8188 \
    --highvram \
    --output-directory /data/comfyui/output \
    --user-directory /data/comfyui/user
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

5. Operations

Starting and Stopping

# Standard start
python main.py [flags]

# Start and open browser
python main.py --auto-launch

# Quick CI validation (starts, validates nodes, exits)
python main.py --quick-test-for-ci

# Graceful stop: Ctrl+C
# The server finishes the current job before exiting.

Queue Management

# Get current queue state
curl http://localhost:8188/queue

# Clear all pending jobs
curl -X POST http://localhost:8188/queue \
  -H "Content-Type: application/json" \
  -d '{"clear": true}'

# Delete specific job
curl -X POST http://localhost:8188/queue \
  -H "Content-Type: application/json" \
  -d '{"delete": ["<prompt_id>"]}'

# Interrupt current execution
curl -X POST http://localhost:8188/interrupt

History Management

# Get full history
curl http://localhost:8188/history

# Get specific job
curl http://localhost:8188/history/<prompt_id>

# Clear history
curl -X POST http://localhost:8188/history \
  -H "Content-Type: application/json" \
  -d '{"clear": true}'

Memory and Model Management

# Free GPU memory (unload all cached models)
curl -X POST http://localhost:8188/free \
  -H "Content-Type: application/json" \
  -d '{"unload_models": true, "free_memory": true}'

# System stats (RAM, VRAM, GPU info, Python version)
curl http://localhost:8188/system_stats

Sample system_stats response:

{
  "system": {
    "os": "Linux-6.1.0-x86_64",
    "python_version": "3.12.3",
    "embedded_python": false
  },
  "devices": [
    {
      "name": "NVIDIA GeForce RTX 4090",
      "type": "cuda",
      "index": 0,
      "vram_total": 25769803776,
      "vram_free": 18274025472,
      "torch_vram_total": 25769803776,
      "torch_vram_free": 18274025472
    }
  ]
}

Note

The /free endpoint is the primary operational lever for releasing VRAM between large jobs. Call it before loading a significantly different model configuration to avoid OOM errors.

Log Access

# Get buffered logs (JSON array)
curl http://localhost:8188/internal/logs

# Get raw log text
curl http://localhost:8188/internal/logs/raw

# Subscribe to live log stream (SSE)
curl -X PATCH http://localhost:8188/internal/logs/subscribe

Asset System Operations (requires --enable-assets)

# Trigger background asset scan
curl -X POST http://localhost:8188/api/assets/seed

# Check scan status
curl http://localhost:8188/api/assets/seed/status

# Cancel running scan
curl -X POST http://localhost:8188/api/assets/seed/cancel

# Mark files that no longer exist on disk
curl -X POST http://localhost:8188/api/assets/prune

Tip

Run POST /api/assets/seed after adding new model files to the models/ directories. The scan runs in the background; poll /api/assets/seed/status for completion. Large output directories (thousands of files) can take several minutes to scan.

Folder Path Inspection

# See all configured folder paths (name → list of directories)
curl http://localhost:8188/internal/folder_paths

# List files in a directory type
curl "http://localhost:8188/internal/files/output"
curl "http://localhost:8188/internal/files/checkpoints"

User Management (Multi-User Mode)

Enable with --multi-user. Each user gets isolated storage under <user-directory>/<user-id>/.

# List users
curl http://localhost:8188/users

# Create user
curl -X POST http://localhost:8188/users \
  -H "Content-Type: application/json" \
  -d '{"username": "alice"}'

Monitoring Checklist

Note

ComfyUI emits no structured metrics by default. Monitoring relies on polling the REST endpoints and tailing logs.

Check Endpoint / Method Recommended Frequency
Queue depth GET /queue → queue_remaining Every 30s
VRAM headroom GET /system_stats → vram_free Every 60s
Error rate GET /internal/logs/raw → grep ERROR Continuous
Job history count GET /history → count keys Hourly
Disk usage (output/) du -sh output/ Daily
Stuck jobs GET /queue + compare timestamps Every 5 min

6. Performance

VRAM Tiering Strategy

Available VRAM Recommended Flags
24+ GB --gpu-only or default
16–24 GB default or --highvram
8–16 GB --normalvram (default)
6–8 GB --lowvram
< 6 GB --novram
< 4 GB or no GPU --cpu

Precision Tradeoffs

Precision VRAM Usage Speed Quality
FP32 4× baseline Slowest Reference
BF16 2× baseline Fast Near-identical to FP32
FP16 2× baseline Fast Good; rare artifacts on some models
FP8 e4m3fn 1× baseline Fastest Slight quality loss; requires hardware support
FP8 e5m2 1× baseline Fastest More dynamic range, less mantissa precision

Attention Implementation Performance

Implementation Speed VRAM Requirements
xformers Fast Low pip install xformers (auto-detected)
Flash Attention Fastest Lowest pip install flash-attn + CUDA 11.6+
Sage Attention Fast Low pip install sageattention
Split cross-attention Medium Low No extra install
PyTorch native Baseline Medium Always available

Caching Strategy Selection

Mode Flag Use Case
Classic --cache-classic Default; good for iterative refinement
LRU N --cache-lru N Explicit capacity; N=0 disables eviction
None --cache-none Maximum VRAM; no repeated-node benefit
RAM --cache-ram Evicts automatically under RAM pressure

Tip

For batch processing workflows where each job is unique (no repeated node inputs), use --cache-none to avoid wasting RAM on cache entries that will never hit again.

Memory Mapping

--mmap-torch-files uses mmap() to load model weights. This reduces peak RAM during model load on Linux but may slow the first inference pass (cold read from disk). Particularly useful when running multiple ComfyUI instances that share the same physical model files.

Preview Performance

Method CPU Cost Latency Visual Quality
none Zero None No preview
latent2rgb Near-zero Real-time Color-approximate
taesd Low ~50ms Good
auto Adaptive Adaptive Picks best available

Async Offloading

--async-offload enables CUDA streams for CPU↔GPU transfers that overlap with compute. Recommended for VRAM-constrained configurations where offloading is frequent. Use --disable-async-offload if you observe ordering artifacts on specific hardware.

Deterministic Mode

--deterministic sets CUBLAS_WORKSPACE_CONFIG=:4096:8 and disables non-deterministic PyTorch ops. Expect 10–30% latency increase. Use only when reproducibility is required (A/B comparisons, regression testing).


7. Security

Threat Model

ComfyUI's design assumes a trusted local or private network environment. There is no built-in authentication, no user authorization, and no sandboxing of workflow execution. Every client that can reach the HTTP port can:

  • Submit arbitrary workflows and consume GPU resources
  • Read all generated outputs
  • Access all uploaded input files
  • Execute system introspection via /system_stats
  • List and download model file metadata

Caution

Never expose ComfyUI directly to the internet without a hardened reverse proxy providing authentication (Basic Auth, OAuth, client certificates). The attack surface includes arbitrary file reads through path traversal vulnerabilities if not mitigated, and arbitrary code execution through custom node installation.

Built-In Protections

Origin Checking

PromptServer enforces Origin header validation for non-GET requests. This prevents cross-site request forgery (CSRF) where a malicious webpage triggers generation jobs on a locally-running instance. The check compares the incoming Origin against the server's configured listen address and port.

Path Traversal Mitigation

File upload and download handlers sanitize requested paths. The view_image endpoint (GET /view) restricts access to the configured output, input, and temp directories. Attempts to traverse outside these directories are rejected.

Dangerous MIME Type Forcing

File downloads for HTML, JavaScript, and CSS content are forced to application/octet-stream. This prevents stored XSS if a user uploads a file with a dangerous extension. MIME type handling lives in utils/mime_types.py.

Content-Security-Policy

When API nodes are disabled (--disable-api-nodes), the server sets a Content-Security-Policy header restricting script sources. When API nodes are enabled, CSP is relaxed to permit the external connections those nodes require.

CORS Control

CORS is disabled by default. Enable with --enable-cors-header [ORIGIN]. Setting to * allows all origins — appropriate only for local development.

TLS

Pass --tls-keyfile and --tls-certfile to enable HTTPS at the aiohttp layer. For production deployments, TLS termination at the reverse proxy is generally preferred (simpler certificate rotation, HTTP/2 support).

Custom Node Security Risk

Caution

Custom nodes are arbitrary Python code executed in the server process with full system access. Installing a custom node from an untrusted source is equivalent to running untrusted code on your machine. Vet custom node packages before installation. Use --whitelist-custom-nodes to restrict which packages load.

Custom nodes are loaded from custom_nodes/ at startup. Prestartup scripts (prestartup_script.py in a custom node package) run before the main server with no restrictions. --disable-all-custom-nodes prevents all custom node loading for hardened deployments.

API Node Security

comfy_api_nodes/ nodes make outbound HTTPS calls to external services (BFL, Gemini, OpenAI, Kling, etc.). API keys are stored in user settings JSON files on disk. Use --disable-api-nodes to block all outbound calls from the server process.

File Upload Security

  • Uploaded files are written to the configured input/ or temp/ directory only.
  • Hash comparison (BLAKE3/SHA256/MD5 per --default-hashing-function) detects and deduplicates re-uploads.
  • MIME type forcing protects against serving uploaded HTML/JS as browser-executable content.
  • Maximum upload size enforced by --max-upload-size (default 100 MB).

Authentication Guidance (Not Built-In)

For shared or internet-facing deployments, implement authentication at the reverse proxy layer:

# Nginx Basic Auth
location / {
    auth_basic "ComfyUI";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://127.0.0.1:8188;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

For device-level access control, use Tailscale or WireGuard. For single-developer use, an SSH tunnel (ssh -L 8188:localhost:8188 user@server) avoids any server-side auth configuration.

Warning

GitHub issue #10653 tracks the absence of built-in authentication. Until native auth lands, the reverse proxy approach is the only supported mitigation for shared deployments.


8. Developer Internals

Execution Engine (comfy_execution/)

graph.py — DynamicPrompt

DynamicPrompt is the runtime representation of a submitted workflow. It:

  • Wraps the raw prompt JSON (node ID → node definition mapping)
  • Resolves node output links to form a directed acyclic graph (DAG)
  • Supports dynamic node count (nodes can be added mid-execution by other nodes)
  • Provides get_node(id), get_nodes_by_type(class_type), has_node(id)

Graph traversal for execution ordering is a topological sort of the DAG. Cycles are rejected at validation time.

caching.py — Cache System

The cache stores node output tensors keyed by the node's effective inputs. On re-execution, if inputs haven't changed, the cached output is returned without re-running the node.

Class Strategy
CacheKeySet (Classic) Full output set; evicts least-recently-used
LRUCacheKeySet Bounded LRU with configurable capacity
RAMCacheKeySet Evicts under psutil RAM pressure threshold
NoCacheKeySet No caching; always recomputes

CacheType enum drives selection based on CLI flags.

cache_provider.py

Allows external plugins to register custom cache backends. The external provider is consulted before the built-in cache lookup.

validation.py

Validates node inputs before execution:

  • Type checking against node's INPUT_TYPES specification
  • Required vs optional input presence
  • Link target existence validation
  • Circular dependency detection

Validation runs on every POST /prompt call. Invalid prompts return HTTP 400 with a structured error body containing per-node error details.

jobs.py — JobStatus

Tracks each prompt's lifecycle:

class JobStatus(Enum):
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    COMPLETED = "completed"
    FAILED = "failed"

Job records are stored in the in-memory history dict and queryable via the Jobs API.

progress.py

Per-node progress state. Each executing node reports (value, max) progress. Aggregated and broadcast via WebSocket progress events to all connected clients.

utils.py — ExecutingContext

@dataclass
class ExecutingContext:
    prompt_id: str
    node_id: str

Thread-local context injected during node execution. Nodes can access the current prompt_id and node_id for logging or conditional behavior.

ML Engine (comfy/)

Model Loading Pipeline

  1. folder_paths.get_filename_list("checkpoints") enumerates available models.
  2. The user selects a model in a loader node (e.g., CheckpointLoaderSimple).
  3. comfy/sd.py:load_checkpoint_guess_config() calls model_detection.py to identify the architecture.
  4. supported_models.py returns the matching model class.
  5. comfy/model_management.py loads weights onto the appropriate device.
  6. A ModelPatcher wraps the model, tracking patches (LoRA, IP-Adapter, hooks).

model_detection.py

Heuristic detection based on weight key names and shapes. Detects: SD 1.x, SD 2.x, SDXL, Flux, AuraFlow, HunyuanDiT, Mochi, CogVideoX, StableAudio, and dozens more. Returns a ModelType enum value that drives class instantiation.

model_patcher.py — ModelPatcher / ModelPatcherDynamic

ModelPatcher is the central object passed between nodes. It wraps the raw PyTorch model and tracks:

  • Applied patches (LoRA, hooks, IP-Adapter weights)
  • Memory allocation state (which device holds the weights)
  • Inference device vs offload device assignment

ModelPatcherDynamic extends this for dynamic VRAM (comfy-aimdo): individual layers can be moved to GPU on demand and evicted back to CPU between operations.

memory_management.py

Low-level VRAM accounting. Tracks all loaded models, their VRAM footprint, and decides when to evict. Key functions:

Function Purpose
get_free_memory(device) Query available VRAM on device
load_models_gpu(models) Move models to GPU, evicting as needed
unload_model_clones(model) Remove duplicate model references
soft_empty_cache() Release unreferenced CUDA tensors
minimum_inference_memory() Minimum VRAM needed for an inference step

samplers.py / sample.py

sample.py orchestrates the full denoising loop:

  1. Prepares the noise tensor according to the selected scheduler.
  2. Calls the selected sampler's sample() method.
  3. Injects callbacks for per-step progress reporting.
  4. Handles conditioning signals (positive/negative prompts, ControlNet, etc.).

samplers.py maintains the sampler registry. The k-diffusion library (comfy/k_diffusion/) provides Euler, Euler Ancestral, Heun, DPM++ variants (2M, 2S, SDE, 3M), UniPC, SA-Solver, DEIS, LCM, and others.

lora.py / lora_convert.py

lora.py loads LoRA weights and applies them to the model via weight decomposition (A×B matrices added to base weights). Supports multiple simultaneous LoRAs with individual model_strength and clip_strength parameters.

lora_convert.py converts between LoRA format variants (Kohya SD, Diffusers, ComfyUI native) on load.

hooks.py

A hook system for intercepting and modifying model behavior during inference without subclassing. Used by ControlNet, IP-Adapter, and advanced conditioning nodes. Hooks attach to ModelPatcher and are invoked at specific points in the forward pass (pre-attention, post-attention, etc.).

ops.py

Custom PyTorch operation overrides for precision management. Key components: cast_bias_weight() for mixed-precision weight handling, quantized linear layer implementations, and manual cast modules. FP8 compute paths are implemented here.

Server Layer (server.py)

PromptServer is the single aiohttp application instance. Key responsibilities:

class PromptServer:
    def __init__(self, loop):
        self.app = web.Application()
        self.sockets = {}          # clientId → WebSocket
        self.prompt_queue = None   # set after queue initialization
        self.number = 0            # monotonic execution counter
        ...

Route registration happens in __init__ by assembling an aiohttp.web.Application. Managers (UserManager, ModelFileManager, etc.) register their routes via a module-level routes decorator that is merged into the server application on init.

Feature Flag Negotiation

On WebSocket connect, the server sends a features message listing server capabilities. The frontend uses this to enable/disable UI panels (asset panel, jobs API, etc.). Feature flags are also accessible via GET /features.

Security Middleware

async def origin_check_middleware(request, handler):
    if request.method != 'GET':
        origin = request.headers.get('Origin', '')
        if not is_safe_origin(origin, request.host):
            raise web.HTTPForbidden()
    return await handler(request)

Node System (nodes.py)

Node Registration

Nodes self-register using class-level attributes collected at import time:

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

    RETURN_TYPES = ("IMAGE",)
    RETURN_NAMES = ("result",)
    FUNCTION = "process"
    CATEGORY = "image/processing"
    OUTPUT_NODE = True  # True if this node produces a final output (images, video, etc.)

    def process(self, image, strength, mask=None):
        # ... processing logic ...
        return (result_image,)

NODE_CLASS_MAPPINGS = {"MyNode": MyNode}
NODE_DISPLAY_NAME_MAPPINGS = {"MyNode": "My Processing Node"}

Custom Node Loading

At startup, nodes.py scans custom_nodes/ for directories and .py files. Each is imported via importlib. If the module defines NODE_CLASS_MAPPINGS, those classes are merged into the global node registry. Name collisions are logged as warnings; the first registered class wins.

Prestartup scripts (custom_nodes/<package>/prestartup_script.py) execute before the main scan. Uses: dependency installation, compatibility patching, platform detection.

pyproject.toml Configuration (comfy_config/)

Custom node packages can include a pyproject.toml with a [tool.comfy] section:

[tool.comfy]
PublishedBy = "author_name"
Tags = ["image", "controlnet"]
Version = "1.2.3"
License = "MIT"

comfy_config/config_parser.py reads this and exposes metadata to the manager UI.

Application Managers (app/)

app/app_settings.py — SettingsManager

Stores user settings as JSON in <user-directory>/comfy.settings.json. Settings have a server-defined schema with defaults. Changes via POST /settings are immediately persisted to disk.

app/frontend_management.py — FrontendManager

In production, serves the bundled frontend from the package. With --front-end-root, serves from a local directory for frontend development. With --front-end-version, downloads and caches a specific frontend build from the release feed.

app/user_manager.py — UserManager

In single-user mode: all data goes to <user-directory>/. In multi-user mode (--multi-user): each user gets <user-directory>/<user-id>/ isolation. get_request_user_id(request) extracts the current user from the session.

app/model_manager.py — ModelFileManager

Wraps folder_paths to provide REST-friendly model enumeration. Handles model preview image serving — looks for <model_name>.preview.png or .preview.jpg alongside the model file.

app/custom_nodes/ — CustomNodeManager

Tracks installed custom node packages, their metadata (via pyproject.toml), and enabled/disabled state. Backs the --enable-manager UI. Provides install/uninstall operations.

app/subgraph_manager.py — SubgraphManager

Manages "global subgraphs" — reusable node groups saved server-side and available across all workflows. Backed by JSON files in the user directory.

Internal Routes (api_server/)

api_server/routes/internal/ provides the /internal/ prefix routes:

Route Handler Purpose
GET /internal/logs get_logs JSON log buffer
GET /internal/logs/raw get_raw_logs Plain text logs
PATCH /internal/logs/subscribe subscribe_logs SSE live log stream
GET /internal/folder_paths get_folder_paths All registered folder paths
GET /internal/files/{directory_type} get_files File listing

api_server/services/terminal_service.py captures stdout/stderr into a circular buffer and serves it to the log endpoints.

Versioned Node API (comfy_api/)

The comfy_api/ package provides a stable API surface for node authors, insulating them from internal changes:

Module Purpose
comfy_api/latest/ Current API version (mirrors internal classes)
comfy_api/v0_0_2/ Stable v0.0.2 surface
comfy_api/v0_0_1/ Legacy v0.0.1 surface
comfy_api/feature_flags.py Feature flag definitions

Node authors should import from comfy_api.latest for forward compatibility. Pinning to a versioned subpackage provides long-term stability guarantees.

Built-In Node Library (comfy_extras/)

The 99 modules in comfy_extras/ implement all built-in nodes:

Category Module Examples
Sampling nodes_custom_sampler.py, nodes_differential_diffusion.py, nodes_sd3.py, nodes_flux.py
Image processing nodes_images.py, nodes_compositing.py, nodes_upscale_model.py, nodes_rebatch.py
Video nodes_video_model.py, nodes_hunyuan_video.py, nodes_mochi.py, nodes_ltxv.py
Audio nodes_audio.py, nodes_stable_audio.py
3D nodes_3d.py, nodes_hunyuan3d.py
Conditioning nodes_cond.py, nodes_gligen.py, nodes_ip_adapter.py, nodes_photomaker.py
ControlNet nodes_controlnet.py
Masking nodes_mask.py, nodes_mask_sequence.py
Model loading nodes_model_advanced.py, nodes_hooks.py, nodes_loras.py
Latents nodes_latent.py, nodes_latent_noise.py
Primitives nodes_primitive.py, nodes_string.py
Advanced techniques nodes_cfg_rescale.py, nodes_freelance.py, nodes_pag.py

External API Nodes (comfy_api_nodes/)

32 node modules integrate cloud AI services:

Service Nodes Provided
Black Forest Labs (BFL/Flux API) Image generation, inpainting
Google Gemini Image understanding, multimodal generation
OpenAI (DALL-E, GPT-4V) Image generation, description
Kling (Kuaishou) Video generation
Luma Dream Machine Video generation
Runway Video generation
Pika Video generation
Stability AI Image generation
Ideogram Text-heavy image generation
Recraft Vector and image generation
fal.ai Hosted model inference
MiniMax Multimodal generation

The comfy_api_nodes/apis/ subdirectory contains typed HTTP client wrappers for each service. All connections are outbound HTTPS. Blocked entirely by --disable-api-nodes.

Asset Management System (app/assets/)

app/assets/
├── asset_service.py      Business logic layer
├── asset_api.py          Route handlers (/api/assets/*)
├── db_queries.py         SQLAlchemy query helpers
├── scan_service.py       Background directory scanner
├── metadata_service.py   Metadata extraction (EXIF, PNG chunks)
└── models.py             SQLAlchemy ORM models

Data model:

  • Asset: file record (path, hash, size, metadata)
  • AssetReference: link between asset hash and a specific file location
  • Tag: free-form tag string
  • AssetTag: many-to-many join table

Deduplication: BLAKE3 hash of file contents. If the same file exists in multiple locations, one Asset record links to multiple AssetReference rows. HEAD /api/assets/hash/{hash} checks existence without downloading.

Background scanning: POST /api/assets/seed starts scan_service.py in a background thread. Walks output/ and input/ directories, hashes new files, updates the database. Large output directories can take minutes.

Database Layer (app/database/)

# Connection setup (WAL mode, FK enforcement)
engine = create_engine(
    database_url,
    connect_args={"check_same_thread": False},
)

@event.listens_for(engine, "connect")
def set_sqlite_pragma(conn, record):
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA foreign_keys=ON")

Alembic migration chain applied automatically on startup:

base → assets → merge_to_asset_references → add_metadata_job_id

Code Patterns and Conventions

Async Node Execution

The execution engine supports both sync and async node FUNCTION methods. Async nodes are awaited via asyncio.run_coroutine_threadsafe() from the background worker thread.

Type System

Node I/O types are strings by convention. Standard types: IMAGE, LATENT, MODEL, VAE, CLIP, CONDITIONING, MASK, STRING, INT, FLOAT, BOOLEAN. Custom types are declared by returning any unique string not in the standard set.

Widget-to-Input Promotion

Any INPUT_TYPES entry can be forced to a connector (wire) by the user via right-click. The "forceInput": True hint makes connector mode the default for that input.

Hidden Inputs

Nodes may declare "hidden" inputs that receive server-injected values at runtime:

"hidden": {
    "prompt": "PROMPT",          # full prompt dict for current run
    "extra_pnginfo": "EXTRA_PNGINFO",  # metadata to embed in PNG outputs
    "unique_id": "UNIQUE_ID",    # this node's ID in the graph
}

Error Handling Pattern

Node execution errors are caught by PromptExecutor. A node raising an exception causes the entire prompt to fail with that node's error message. The error is stored in history and broadcast via WebSocket status event. Nodes should raise plain Exception for user-visible errors.


9. Integration Points

Complete API Endpoint Reference

Core Workflow

Method Path Description
GET /ws WebSocket connection (?clientId=<uuid>)
GET / Frontend index.html
POST /prompt Submit workflow graph for execution
GET /prompt Queue info (running + pending counts)
GET /queue Full queue state
POST /queue {"clear": true} or {"delete": ["id"]}
POST /interrupt Interrupt execution ({"prompt_id": "..."} optional)
POST /free Free GPU/memory ({"unload_models": true, "free_memory": true})
GET /history All execution history
GET /history/{prompt_id} Single job history entry
POST /history {"clear": true} or {"delete": ["id"]}

Resource Discovery

Method Path Description
GET /embeddings List available embedding files
GET /models List model type folder names
GET /models/{folder} List model files in folder
GET /extensions List loaded frontend extensions
GET /object_info All registered node class definitions
GET /object_info/{node_class} Single node definition (inputs, outputs, category)
GET /features Server feature flags
GET /system_stats System info (GPU, RAM, Python version)
GET /workflow_templates List blueprint workflow templates
GET /i18n Internationalization strings
GET /node_replacements Node class rename/alias mappings
GET /global_subgraphs List global subgraph definitions
GET /global_subgraphs/{id} Single subgraph definition

File Operations

Method Path Description
POST /upload/image Upload image (?type=input|temp|output&subfolder=...)
POST /upload/mask Upload mask image with composite
GET /view Download file (?filename=...&type=...&subfolder=...)
GET /view_metadata/{folder_name} Safetensors metadata (?filename=...)

Settings

Method Path Description
GET /settings All settings for current user
GET /settings/{id} Single setting by ID
POST /settings Update multiple settings (JSON body)
POST /settings/{id} Update single setting

User Management

Method Path Description
GET /users List users
POST /users Create user ({"username": "..."})
GET /userdata List user data files (?path=...)
GET /v2/userdata List user data (v2, with pagination)
GET /userdata/{file} Get user data file
POST /userdata/{file} Save user data file
DELETE /userdata/{file} Delete user data file
POST /userdata/{file}/move/{dest} Move/rename user data file

Jobs API

Method Path Description
GET /api/jobs List jobs with filtering, sorting, pagination
GET /api/jobs/{job_id} Get single job by ID

Experimental Model Management

Method Path Description
GET /experiment/models List all model folders
GET /experiment/models/{folder} List models in folder with metadata
GET /experiment/models/preview/... Get model preview image

Internal Routes (/internal/)

Method Path Description
GET /internal/logs Buffered application logs (JSON)
GET /internal/logs/raw Raw log text
PATCH /internal/logs/subscribe Subscribe to log stream (SSE)
GET /internal/folder_paths All registered folder path mappings
GET /internal/files/{directory_type} File listing for directory type

Asset Management (/api/assets/, requires --enable-assets)

Method Path Description
HEAD /api/assets/hash/{hash} Check if asset with hash exists
GET /api/assets List assets (filtering, pagination)
GET /api/assets/{id} Get asset details by ID
GET /api/assets/{id}/content Download asset file
POST /api/assets/from-hash Create asset reference from existing hash
POST /api/assets Upload new asset
PUT /api/assets/{id} Update asset metadata
DELETE /api/assets/{id} Delete asset record
GET /api/tags List all tags
POST /api/assets/{id}/tags Add tags to asset
DELETE /api/assets/{id}/tags Remove tags from asset
GET /api/assets/tags/refine Refined tag suggestions
POST /api/assets/seed Start background asset scan
GET /api/assets/seed/status Check scan progress
POST /api/assets/seed/cancel Cancel running scan
POST /api/assets/prune Mark missing files as absent

Programmatic API Client Pattern

import asyncio
import json
import uuid
import websockets
import aiohttp

SERVER = "http://localhost:8188"
WS_SERVER = "ws://localhost:8188"

async def submit_and_wait(workflow: dict) -> dict:
    """Submit a workflow and wait for completion via WebSocket."""
    client_id = str(uuid.uuid4())

    async with aiohttp.ClientSession() as session:
        # Submit workflow
        async with session.post(f"{SERVER}/prompt", json={
            "prompt": workflow,
            "client_id": client_id
        }) as resp:
            result = await resp.json()
            prompt_id = result["prompt_id"]

    # Wait for completion via WebSocket
    async with websockets.connect(f"{WS_SERVER}/ws?clientId={client_id}") as ws:
        async for message in ws:
            if isinstance(message, bytes):
                continue  # skip binary preview frames
            data = json.loads(message)
            if data["type"] == "executed":
                if data["data"]["prompt_id"] == prompt_id:
                    return data["data"]

Minimal Workflow API Payload

{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "prompt": {
    "1": {
      "class_type": "CheckpointLoaderSimple",
      "inputs": {
        "ckpt_name": "v1-5-pruned-emaonly.safetensors"
      }
    },
    "2": {
      "class_type": "CLIPTextEncode",
      "inputs": {
        "text": "a photo of a cat",
        "clip": ["1", 1]
      }
    },
    "3": {
      "class_type": "CLIPTextEncode",
      "inputs": {
        "text": "ugly, blurry",
        "clip": ["1", 1]
      }
    },
    "4": {
      "class_type": "EmptyLatentImage",
      "inputs": {
        "width": 512,
        "height": 512,
        "batch_size": 1
      }
    },
    "5": {
      "class_type": "KSampler",
      "inputs": {
        "model": ["1", 0],
        "positive": ["2", 0],
        "negative": ["3", 0],
        "latent_image": ["4", 0],
        "seed": 42,
        "steps": 20,
        "cfg": 7.0,
        "sampler_name": "euler_ancestral",
        "scheduler": "karras",
        "denoise": 1.0
      }
    },
    "6": {
      "class_type": "VAEDecode",
      "inputs": {
        "samples": ["5", 0],
        "vae": ["1", 2]
      }
    },
    "7": {
      "class_type": "SaveImage",
      "inputs": {
        "images": ["6", 0],
        "filename_prefix": "api_output"
      }
    }
  }
}

Link format: ["<source_node_id>", <output_index>]. CheckpointLoaderSimple outputs: index 0 = MODEL, index 1 = CLIP, index 2 = VAE.

Custom Node Development Reference

Minimal Node Template

# custom_nodes/my_package/my_node.py

class TextUpperNode:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "text": ("STRING", {"multiline": False}),
            },
            "optional": {
                "prefix": ("STRING", {"default": ""}),
            }
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("upper_text",)
    FUNCTION = "execute"
    CATEGORY = "utils/text"

    def execute(self, text: str, prefix: str = ""):
        return (prefix + text.upper(),)


NODE_CLASS_MAPPINGS = {
    "TextUpperNode": TextUpperNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
    "TextUpperNode": "Text to Uppercase",
}

Web Extension Registration

Place a JS file in <custom_node_package>/web/extensions/. The server auto-discovers and serves it.

// web/extensions/my_extension.js
import { app } from "../../scripts/app.js";

app.registerExtension({
    name: "my_package.my_extension",
    async setup() {
        // runs once on frontend load
    },
    async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
        // runs for each node type definition
    },
});

Testing Guide

Test Infrastructure

# Install test dependencies
pip install pytest pytest-asyncio pytest-aiohttp websocket-client

# Unit tests (no running server required)
pytest tests-unit/

# Integration tests (requires running server)
export COMFYUI_SERVER=http://localhost:8188
pytest tests/execution/

# Inference tests (runs actual generation — slow, requires models)
pytest tests/inference/

# Quality comparison tests
pytest tests/compare/

# CI quick validation
python main.py --quick-test-for-ci

Test Category Reference

Directory Type Requirements Notes
tests-unit/ Unit Python, pytest No server or models needed
tests/execution/ Integration Running ComfyUI Uses WebSocket + REST
tests/inference/ E2E generation Running + models Slow; validates actual outputs
tests/compare/ Quality diff Two instances + reference For regression detection

Note

The tests/ integration suite uses websocket-client for synchronous WebSocket communication. The tests-unit/ suite uses pytest-asyncio for async test support and pytest-aiohttp for aiohttp test client fixtures. Both must be installed for the full suite to run.

Unit Test Pattern

# tests-unit/test_my_node.py
import pytest
from custom_nodes.my_package.my_node import TextUpperNode

def test_basic_upper():
    node = TextUpperNode()
    result = node.execute("hello world")
    assert result == ("HELLO WORLD",)

def test_with_prefix():
    node = TextUpperNode()
    result = node.execute("hello", prefix=">> ")
    assert result == (">> HELLO",)

def test_empty_string():
    node = TextUpperNode()
    result = node.execute("")
    assert result == ("",)

Troubleshooting Reference

Note

Most runtime errors appear simultaneously in the browser console (F12) and the server terminal. Start there before querying the /internal/logs endpoint. Node-level errors also appear on the node itself in the workflow editor.

Symptom Likely Cause Resolution
CUDA out of memory VRAM exhausted Use --lowvram, reduce batch size, switch to FP8/BF16
RuntimeError: Expected all tensors on same device Mixed device tensors Usually a LoRA/model mismatch; check node connections
Node turns red in UI Node raised exception Hover for error; check server logs
Custom node not appearing Import error at load Check terminal for ImportError; install missing packages
WebSocket not connecting Port blocked or server crash curl http://localhost:8188/system_stats to test; inspect terminal
Prompt outputs failed validation Invalid node wiring Check GET /object_info/{class} for correct input types
Models not listed Path not configured Verify extra_model_paths.yaml; check GET /internal/folder_paths
Slow preview generation taesd decode overhead Switch to --preview-method latent2rgb
Upload rejected with 413 File exceeds size limit Increase --max-upload-size
SQLite "database is locked" Concurrent write access Check no other process holds comfy.db; WAL mode normally prevents this
Black/grey output images VAE precision mismatch Try --fp32-vae; some models are VAE-sensitive
Sampler not found Typo or custom sampler not loaded Check GET /object_info/KSampler for available sampler names

Tip

For persistent "model not found" errors: curl http://localhost:8188/models/<folder> shows exactly what the server has indexed. The folder scan runs once at startup — add models, then restart the server.

Warning

Setting --deterministic while using xformers will error — xformers attention is inherently non-deterministic. Pair --deterministic with --use-pytorch-cross-attention or --disable-xformers.

Caution

The --fast flag enables experimental optimizations that may produce incorrect outputs on some model+hardware combinations. Do not use in production or for reproducibility-sensitive workflows.


Quick Reference: Common Startup Commands

# Development (local only, verbose)
python main.py --verbose DEBUG

# LAN sharing, NVIDIA GPU, 16 GB VRAM
python main.py --listen 0.0.0.0 --normalvram --preview-method taesd

# Server with asset tracking and multi-user
python main.py --listen 127.0.0.1 --enable-assets --multi-user --log-stdout

# Headless batch processing (maximum throughput)
python main.py --dont-print-server --cache-classic \
    --disable-all-custom-nodes --disable-manager-ui

# Reproducible output (for A/B testing)
python main.py --deterministic --use-pytorch-cross-attention --disable-xformers

# Memory-constrained workstation (6 GB VRAM)
python main.py --lowvram --fp8_e4m3fn-unet --bf16-vae \
    --preview-method latent2rgb

# CI validation (start, check, exit)
python main.py --quick-test-for-ci