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
- Browser POSTs a workflow graph to
/prompt. PromptServer.post_prompt()validates the origin header, parses the JSON body, and callsqueue.put().PromptQueueassigns aprompt_idand enqueues.- The background
prompt_workerthread dequeues viaget()(blocking). PromptExecutor.execute()topologically sorts the graph (DynamicPrompt), resolves cache hits, and runs each node in dependency order.- After each node, a WebSocket
executingevent is broadcast. On output image nodes, aPREVIEW_IMAGEbinary event is sent. - On completion, the result is stored in history and an
executedevent 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 (
--cpuflag) 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 withpython -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.0without 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:
assets— initial asset tablemerge_to_asset_references— separate asset references tableadd_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
upstreamacross 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
/freeendpoint 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/seedafter adding new model files to themodels/directories. The scan runs in the background; poll/api/assets/seed/statusfor 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-noneto 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-nodesto 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/ortemp/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_TYPESspecification - 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
folder_paths.get_filename_list("checkpoints")enumerates available models.- The user selects a model in a loader node (e.g.,
CheckpointLoaderSimple). comfy/sd.py:load_checkpoint_guess_config()callsmodel_detection.pyto identify the architecture.supported_models.pyreturns the matching model class.comfy/model_management.pyloads weights onto the appropriate device.- A
ModelPatcherwraps 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:
- Prepares the noise tensor according to the selected scheduler.
- Calls the selected sampler's
sample()method. - Injects callbacks for per-step progress reporting.
- 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 locationTag: free-form tag stringAssetTag: 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 useswebsocket-clientfor synchronous WebSocket communication. Thetests-unit/suite usespytest-asynciofor async test support andpytest-aiohttpfor 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/logsendpoint. 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
--deterministicwhile using xformers will error — xformers attention is inherently non-deterministic. Pair--deterministicwith--use-pytorch-cross-attentionor--disable-xformers.
Caution
The
--fastflag 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