Pure Python automation for ComfyUI: convert workflows, submit jobs, fetch images—no GUI required
Project description
$$\ $$\
$$ | $$ |
$$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$\
\____$$\ $$ | $$ |\_$$ _| $$ __$$\ $$ __$$\ $$ __$$\ \____$$\ $$ __$$\ $$ __$$\
$$$$$$$ |$$ | $$ | $$ | $$ / $$ |$$ / $$ |$$ | \__|$$$$$$$ |$$ / $$ |$$ | $$ |
$$ __$$ |$$ | $$ | $$ |$$\ $$ | $$ |$$ | $$ |$$ | $$ __$$ |$$ | $$ |$$ | $$ |
\$$$$$$$ |\$$$$$$ | \$$$$ |\$$$$$$ |\$$$$$$$ |$$ | \$$$$$$$ |$$$$$$$ |$$ | $$ |
\_______| \______/ \____/ \______/ \____$$ |\__| \_______|$$ ____/ \__| \__|
$$\ $$ | $$ |
\$$$$$$ | $$ |
\______/ \__|
ComfyUI version: 2.2.0
flowchart LR
workflowJson["workflow.json"] --> autograph["autograph"]
--> apiFlow[workflow-api.json]
Imagine...
What if you could load, edit, and submit ComfyUI workflows without ever exporting an API workflow from the GUI?
What if you could batch-convert and patch workflows offline No running ComfyUI instance required?
What if you could attach studio metadata to your workflow and have it carry through the entire production lifecycle?
What if you could render comfyui node workflows with all of the above features, without ever launching the comfyui server?
-
Let me introduce
comfyui-autograph
autograph
Skip the GUI. autograph handles the backend so you can automate/pipeline your ComfyUI renderables with full control through the entire conversion and submission process.
autograph is a small and efficient, pure Python package (stdlib-only +extendable) for ComfyUI automation that gives you access to renderable conversion with or without ComfyUI.
Features
| Feature | Description |
|---|---|
| Convert workflow.json | workflow.json → renderable API payload, skip the GUI export entirely |
| Offline / Online | Convert without a running ComfyUI server, or fetch live from one |
| Submit + Images | Send to ComfyUI, wait for completion, fetch and save output images |
| Progress | Hook into WebSocket render events for real-time progress control |
| Edit + Introspect | api.KSampler.seed = 42, .find(...), .choices(), .tooltip(). OOP access to every node and widget |
| Metadata | Attach studio metadata to workflows that carries through the entire production lifecycle |
| Serverless Execute | .execute() runs ComfyUI nodes in-process, no HTTP server required |
| Map | Sweep seeds, prompts, paths across nodes for batch pipelines |
| Save + Load | .save() / .load() on Flow, ApiFlow, and NodeInfo for simple serialization of any object |
| Extract | Load workflows directly from ComfyUI PNG outputs (embedded metadata) |
| Stdlib-only | Zero dependencies by default; optional Pillow, ImageMagick, ffmpeg |
| Subgraphs | Flattens nested definitions.subgraphs into a normal API payload |
Requirements
- Python 3.7+ (dict insertion order preserved)
- ComfyUI server (optional, for API mode)
- No additional Python packages required
Tested ComfyUI Versions:
- ComfyUI
0.8.2 - ComfyUI_frontend
v1.35.9
The Two ComfyUI Formats you should know about
ComfyUI uses two JSON formats:
| Format | File | Description |
|---|---|---|
| Workspace | workflow.json |
The UI-editable graph with node positions, colors, widgets. What you save from ComfyUI. |
| API Payload | workflow-api.json |
The renderable blueprint—just nodes + inputs, ready for POST /prompt. This is what ApiFlow represents. |
autograph converts Workspace → API Payload (or loads an existing API Payload directly).
flowchart LR
workspace["workflow.json (workspace)"] --> convert["autograph"]
convert --> payload["workflow-api.json (API payload / ApiFlow)"]
payload --> submit["POST /prompt"]
submit --> comfy["ComfyUI renders"]
Installation
pip install comfyui-autograph
Then use with python -m autograph ... or import autograph from Python.
- Optional: set
AUTOGRAPH_COMFYUI_SERVER_URLonce (thenserver_url/--server-urlbecome optional):- Linux/macOS:
export AUTOGRAPH_COMFYUI_SERVER_URL="http://localhost:8188" - Windows PowerShell:
$env:AUTOGRAPH_COMFYUI_SERVER_URL = "http://localhost:8188" - Windows CMD:
set AUTOGRAPH_COMFYUI_SERVER_URL=http://localhost:8188 - Python:
import os; os.environ["AUTOGRAPH_COMFYUI_SERVER_URL"] = "http://localhost:8188"
- Linux/macOS:
- Optional: set
AUTOGRAPH_NODE_INFO_SOURCE=modules|fetch|server|/path/to/node-info.jsonto auto-resolvenode_info.
autograph - Quick Start
Get node-info.json (optional, one-time)
Save node-info.json so you can convert offline. You can also convert against a running ComfyUI instance, but for efficiency we recommend pulling a new node-info.json per instance (reproducible, no server needed).
flowchart LR
comfy["ComfyUI server"] --> obj["/object_info"]
obj --> file["node-info.json"]
# api — set AUTOGRAPH_COMFYUI_SERVER_URL first (see Installation above)
from autograph import NodeInfo
node_info = NodeInfo('fetch')
node_info.save('node-info.json')
# cli
python -m autograph --download-node-info-path node-info.json
-
Direct modules (no server):
NodeInfo.from_comfyui_modules()buildsnode_infofrom local ComfyUI nodes. -
Env source (optional): set
AUTOGRAPH_NODE_INFO_SOURCE=modules|fetch|server|/path/to/node-info.json.
Convert live (using running ComfyUI)
Convert workflow.json by fetching /object_info from your running ComfyUI server.
flowchart LR
env["AUTOGRAPH_COMFYUI_SERVER_URL"] --> wf["Flow(...)"]
wf --> flow["Flow"]
comfy["ComfyUI server"] --> obj["/object_info"]
obj --> wf
If environment variable AUTOGRAPH_COMFYUI_SERVER_URL is set, server_url becomes optional.
If AUTOGRAPH_NODE_INFO_SOURCE is set, Flow(...) / ApiFlow(...) will auto-resolve node_info when none is provided.
# api — use Flow to work with workflow.json natively
from autograph import Flow
flow = Flow("workflow.json") # uses AUTOGRAPH_COMFYUI_SERVER_URL
flow.save("workflow-out.json") # stays in workspace format
# or convert to API payload
api = flow.convert()
api.save("workflow-api.json")
Note:
ApiFlow("workflow.json")also works and always converts to the API format.Flowkeeps the workspace format and converts on demand.
# cli
python -m autograph --input-path workflow.json --output-path workflow-api.json
Convert workflow to workflow-api (offline)
Convert using your saved node-info.json (no server needed).
flowchart LR
workflowJson["workflow.json"] --> wf["Flow(...)"]
objectInfo["node-info.json"] --> wf
wf --> flow["Flow"]
flow --> convert["flow.convert()"]
convert --> apiFlow["ApiFlow"]
apiFlow --> saveApi["save(workflow-api.json)"]
# api — Flow-first: load, optionally edit, then convert
from autograph import Flow
flow = Flow("workflow.json", node_info="node-info.json")
flow.save("workflow-out.json") # save workspace format
api = flow.convert() # convert to API payload
api.save("workflow-api.json") # save API format
# cli
# Offline mode (saved node_info)
python -m autograph --input-path workflow.json --output-path workflow-api.json --node-info-path node-info.json
# Short form (flags)
python -m autograph -i workflow.json -o workflow-api.json -f node-info.json
- More:
docs/convert.md
Build Workflows from Scratch
Create and wire ComfyUI workflows entirely from Python — full tab completion, Pythonic dict-like views, and multiple connection syntaxes.
from autograph import Flow, NodeInfo
flow = Flow(node_info="node-info.json")
# Add nodes
ckpt = flow.add_node("CheckpointLoaderSimple")
pos = flow.add_node("CLIPTextEncode", text="a beautiful landscape")
neg = flow.add_node("CLIPTextEncode", text="ugly, blurry")
lat = flow.add_node("EmptyLatentImage", width=1024, height=1024)
ks = flow.add_node("KSampler", seed=42, steps=20, cfg=7.0)
vae = flow.add_node("VAEDecode")
save = flow.add_node("SaveImage", filename_prefix="test")
# Wire connections — all equivalent styles:
ckpt.outputs.MODEL >> ks.inputs.model # explicit push
ks.inputs.positive << pos # pull (auto-resolve output)
ckpt.outputs.CLIP >> [pos.inputs.clip, neg.inputs.clip] # fan-out
ckpt >> vae.vae # shorthand (auto-resolve)
# Inspect connections
ks.inputs # {'model': 'CheckpointLoaderSimple.MODEL', 'positive': None, ...}
ckpt.outputs # {'MODEL': 'KSampler.model', 'CLIP': '...', 'VAE': None}
print(ks.inputs.status()) # full ANSI-colored status table
# Disconnect
ks.inputs.model << None # operator
ckpt.outputs.CLIP.disconnect(pos.inputs.clip) # targeted
# Save
flow.auto_layout()
flow.save("my-workflow.json")
node.inputs/node.outputs— dict-like views:[],pop(),del,keys/values/itemsflow.nodes.KSamplerreturns the sameNodeRefasflow.add_node().status()on any view gives a full ANSI-colored connection table
Load from PNG (extract embedded workflow)
ComfyUI embeds workflow metadata in PNG outputs. Extract it directly—no external dependencies needed.
# api
from autograph import Flow, ApiFlow
# From PNG file
api_flow = ApiFlow.load("ComfyUI_00001_.png") # extracts API payload
flow = Flow.load("ComfyUI_00001_.png") # extracts workspace
# From bytes (e.g., HTTP upload, database blob)
with open("output.png", "rb") as f:
api_flow = ApiFlow.load(f.read())
All .load() methods accept: dict, bytes, str (JSON or path), Path
Submit + images (optional)
Submit your ApiFlow directly to ComfyUI and get files back
flowchart LR
apiFlow["ApiFlow"] ==> submit["submit(wait=True)"]
submit ==> comfy["ComfyUI server"]
comfy --> |job handle|apiFlow
apiFlow ---> |job handle| images["fetch_images().save(...)"]
# api
from autograph import ApiFlow
api = ApiFlow("workflow.json")
api.saveimage.inputs.filename_prefix='autograph'
res = api.submit(server_url="http://localhost:8188", wait=True)
images = res.fetch_images()
images.save("outputs/frame.###.png")
Upload input assets with friendly templates instead of raw MIME strings:
# api
from autograph import upload_file
uploaded = upload_file("src.jpeg", server_url="http://localhost:8188", accept="image")
api.LoadImage.image = uploaded.path
upload_file("voice.wav", server_url="http://localhost:8188", accept="audio")
upload_file("clip.mp4", server_url="http://localhost:8188", accept="video")
You can also set an output default with env AUTOGRAPH_OUTPUT_PATH and then just provide a filename= template:
# api
images.save(filename="frame.{src_frame}.png") # or "frame.###.png" for zero-indexed numbering
# cli
# Prints prompt_id first, then (if saving) the written file paths. Progress logs go to stderr.
python -m autograph --submit --input-path workflow.json --server-url http://localhost:8188 \
--save-images outputs --filepattern "frame.###.png" --index-offset 1001
Serverless execute (no ComfyUI HTTP server)
If you're running inside a ComfyUI environment (repo + venv), you can run workflows serverlessly:
- Details:
docs/execute.md
Optional Functionality
- Polymorphic loading (dict, bytes, JSON string, file path, PNG):
- all
.load()methods auto-detect input type:docs/load-vs-convert.md - extract workflows from ComfyUI PNG outputs (no dependencies)
- all
- OOP node access:
api.KSampler.seed = 42— attribute-style access by class_typeapi.find(class_type="KSampler")[0].seed = 42— search + then edit (returns NodeProxy objects)api.KSampler._meta/.meta— access node metadataapi["ksampler/seed"]— path-style accessapi["18:17:3/seed"] = 42— edit nodes inside flattened subgraph exports (ComfyUI-style path IDs)flow.nodes.KSampler.type— explicit via.nodesfor workspace flowsflow.nodes.find(title="NewSubgraphName")[0].path()— find renamed subgraph instances; prints a stable path like18:17:3flow.extra.ds.scale— drill into nested dicts withDictViewnode.properties.models.url— single-item list-of-dicts drill viaListView(otherwise index first)- Widget-value repr:
NodeRef/NodeSetdisplay widget values as dicts —f.nodes.CheckpointLoaderSimple→{'nodes.CheckpointLoaderSimple[0]': {'ckpt_name': '...'}} - Widget introspection:
.choices()returns valid combo options,.tooltip()shows help text,.spec()gives the rawnode_infospec - Tab completion: curated
__dir__onApiFlow,NodeSet,FlowTreeNodesView, andWidgetValue— only shows user-facing attrs - Indexed nodes: standard Python REPL can't tab-complete
api.KSampler[0].<tab>— assign to a variable first:k = api.KSampler[0]thenk.<tab>
- Mapping (seeds/paths/prompts):
- typed callback mapping:
docs/mapping.md - declarative string/path mapping:
docs/map-strings-and-paths.md - cache-busting repeat runs:
docs/force-recompute.md
- typed callback mapping:
- Filename pattern saving:
ImagesResult.save("outputs/frame.###.png"):docs/submit-and-images.md
- Service patterns:
- FastAPI integration:
docs/fastapi.md - structured errors:
docs/error-handling.md
- FastAPI integration:
- When things break:
- troubleshooting:
docs/troubleshooting.md - deeper options:
docs/advanced.md
- troubleshooting:
CLI Reference
| Argument | Short | Description |
|---|---|---|
--input-path |
-i |
Input workflow JSON file path |
--output-path |
-o |
Output API format JSON file path |
--server-url |
ComfyUI server URL (or set AUTOGRAPH_COMFYUI_SERVER_URL) |
|
--node-info-path |
-f |
Path to saved node_info.json file |
--download-node-info-path |
Download /object_info and save to file |
|
--submit |
Submit converted API payload to ComfyUI | |
--no-wait |
Submit without waiting for completion (prints prompt_id and exits) |
|
--no-progress |
Disable progress output during --submit when waiting |
|
--save-images |
Directory to save fetched images (requires waiting) | |
--filepattern |
Filename pattern used when saving images (default: frame.###.png) |
|
--index-offset |
Index offset for # patterns (default: 0) |
|
--save-files |
Directory to save fetched registered files (requires waiting) | |
--output-types |
Comma-separated registered output types when saving files (e.g. images,files) |
Note: The CLI supports submission and saving registered outputs via --submit (see docs/submit-and-images.md and docs/progress-events.md).
Contributing
This script is designed to be production-ready and maintainable. Key design principles:
- Minimal Dependencies: Uses only Python standard library
- Cross-Platform Compatibility: Works on Linux, Windows, and macOS
- Robust Error Handling: Graceful degradation and detailed error reporting
- Exact Replication: Matches ComfyUI's internal conversion exactly
For development setup, running tests, and code style guidelines, see CONTRIBUTING.md.
License
Related
- ComfyUI - The main ComfyUI project
- ComfyUI API Documentation - API format specification
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file comfyui_autograph-2.2.0.tar.gz.
File metadata
- Download URL: comfyui_autograph-2.2.0.tar.gz
- Upload date:
- Size: 120.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
093e49783bcf62b1d99e94c756bfabde3253e14c923bdfeb7f723d352d97de9c
|
|
| MD5 |
3dfc1282753fb8bfee4f989357259a73
|
|
| BLAKE2b-256 |
510c29259503618b1f8f60b4288bdb668c9d5e2c0993d888f8d16b9ab158768d
|
File details
Details for the file comfyui_autograph-2.2.0-py3-none-any.whl.
File metadata
- Download URL: comfyui_autograph-2.2.0-py3-none-any.whl
- Upload date:
- Size: 123.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0ce26c258dced9952ecda1ece01d3553911ea9cf047ee84b1597d1a54aee4b8
|
|
| MD5 |
78c0c97c0a674860e1d45617a4dfc6c9
|
|
| BLAKE2b-256 |
2c9e2287ee179c5b728ca70500a64286e9b92ebfc4eb6c8221a5cf0ee53c3f5a
|