
Product
Rust Support in Socket Is Now Generally Available
Socket’s Rust and Cargo support is now generally available, providing dependency analysis and supply chain visibility for Rust projects.
comfyui-sdk
Advanced tools
A battle-tested, TypeScript-first client for interacting with ComfyUI. Designed for large-scale Node.js applications, comfyui-sdk offers:
🔄 Type-safe Workflow Execution Fully typed payloads and results, ensuring end-to-end type safety when invoking ComfyUI workflows.
📁 Streamlined File Management Upload, overwrite, and organize files on the ComfyUI server. Automatic format inference, custom subfolders, and overwrite flags.
🏊 High-throughput Connection Pooling Distribute requests across multiple ComfyUI instances with configurable concurrency limits and built-in load balancing.
📝 Flexible, Configurable Logging Built-in logging framework with adjustable log levels (NONE, ERROR, WARN, INFO, DEBUG).
⚡ Robust Retry & Polling Exponential backoff, configurable retry caps, and efficient polling loops to handle long-running workflows.
🔐 Built-in Authentication Simple API-key support for secure communication.
📦 Advanced Pipeline Support Define reusable, type-safe input/output mappings for complex pipelines; integrate processors for artifact transformation.
🌐 Cloud Storage Integrations Native AWS S3 and Tencent COS uploaders, complete with signing and URL generation.
🎯 Session & Resource Management Automatic session pooling, resource cleanup, and artifact pipelines that handle uploads, transformations, and metadata collection.
🔧 Modular Artifact Processors
Build custom processors with fine-grained shouldRun logic and type-safe state propagation via PipelineBus.
Install via your preferred package manager:
npm install comfyui-sdk
yarn add comfyui-sdk
pnpm add comfyui-sdk
import { ComfyUIClient, LogLevel } from 'comfyui-sdk'
const client = new ComfyUIClient({
baseUrl: 'http://localhost:8188', // ComfyUI server URL
apiKey: 'your-api-key-here', // Optional API key
timeout: 90_000, // Request timeout (ms)
logging: true, // Enable logging
logLevel: LogLevel.INFO // Adjust log verbosity
})
// A minimal workflow: encode text and load a checkpoint
const simpleWorkflow = {
1: {
inputs: { text: 'A serene mountain landscape' },
class_type: 'CLIPTextEncode'
},
2: {
inputs: { ckpt_name: 'sd_base_1.0.safetensors' },
class_type: 'CheckpointLoaderSimple'
}
}
async function runSimple() {
try {
const artifacts = await client.run(simpleWorkflow)
console.log('Artifacts:', artifacts)
}
catch (err) {
console.error('Workflow execution failed:', err)
}
}
runSimple()
import fs from 'node:fs'
// Read a local image into a Buffer
const buffer = fs.readFileSync('input.jpg')
async function uploadExample() {
const result = await client.uploadFile(buffer, {
filename: 'input.jpg', // Custom name on server
subfolder: 'images', // Place under `uploaded/images/`
override: true // Overwrite if file exists
})
console.log('Uploaded as:', result.name)
}
uploadExample()
The primary class to interface with ComfyUI. All network requests, logging, and pooling are managed here.
new ComfyUIClient(options: ClientOptions)interface ClientOptions {
baseUrl: string // E.g., 'http://localhost:8188'
apiKey?: string // Optional API key
timeout?: number // Request timeout in milliseconds (default: 90_000)
poll?: {
interval?: number // Polling interval in ms (default: 4_000)
backoffBase?: number // Exponential backoff base in ms (default: 2_000)
backoffCap?: number // Max backoff per retry in ms (default: 15_000)
}
logging?: boolean // Enable or disable logging (default: false)
logLevel?: LogLevel // One of NONE, ERROR, WARN, INFO, DEBUG (default: INFO)
}
run<TNode>(workflow: WorkflowPayload, options?: RunOptions<TNode>): Promise<Artifact[]>Execute a ComfyUI workflow. Returns an array of Artifact objects (binary, text, or JSON). Optionally, specify a type-safe mapping using defineConfig.
Parameters
workflow: An object whose keys are node IDs (strings or numbers) and whose values define class_type and inputs.
options (optional):
interface RunOptions<TNode> {
node?: TNode // A typed pipeline/node definition (from defineConfig)
inputs?: Record<string, unknown> // Values for required/optional pipeline inputs
}
Returns
Promise<Artifact[]>
defineConfig, you’ll receive a strongly typed result object (with named outputs).Example: Basic Execution
const artifacts = await client.run({
1: { class_type: 'CLIPTextEncode', inputs: { text: 'hello world' } }
})
console.log(artifacts)
Example: Type-safe Pipeline
import { defineConfig } from 'comfyui-sdk'
const textToImageConfig = defineConfig({
inputs: [
{ from: 'prompt', to: '1.inputs.text', required: true },
{ from: 'seed', to: '2.inputs.seed', defaultValue: 42 }
] as const,
outputs: [
{ from: '5', to: 'image' }
] as const
})
// Workflow must match the node mapping above
const workflow = {
1: { class_type: 'CLIPTextEncode', inputs: { text: 'A cosmic vista' } },
2: { class_type: 'RandomSeed', inputs: { seed: 123 } },
// ... other nodes up to node 5 that produce an image
}
const { image } = await client.run(workflow, {
node: textToImageConfig,
inputs: {
prompt: 'A vibrant galaxy',
seed: 2025
}
})
console.log('Generated image artifact:', image)
uploadFile(file: Buffer | Blob, options?: UploadOptions): Promise<UploadOutput>Upload a binary to ComfyUI’s file repository. Automatically handles file naming, subfolders, and overwrites.
Parameters
file: A Node.js Buffer or a browser Blob.
options:
interface UploadOptions {
override?: boolean // Overwrite if a file with the same name already exists (default: false)
subfolder?: string // Subfolder inside `uploaded/`; auto-creates directories (default: 'uploaded')
filename?: string // Custom filename; if omitted, a UUID-based name is generated
}
Returns
Promise<UploadOutput>
interface UploadOutput {
name: string // Full path on server, e.g., 'uploaded/images/foo.jpg'
manifest: {
contentType: string // MIME type (e.g., 'image/jpeg')
filename: string // The final filename on server
size: number // File size in bytes
// ... other manifest fields
}
}
Example
import fs from 'node:fs'
const imageBuffer = fs.readFileSync('avatar.png')
const result = await client.uploadFile(imageBuffer, {
subfolder: 'avatars',
filename: 'user123.png',
override: true
})
console.log('File URL/name:', result.name)
getHistory(promptId: string): Promise<Histories>Fetch the full execution history for a given prompt UUID.
Parameters
promptId: The string identifier returned by a previous workflow execution.Returns
Promise<Histories>
Example
const history = await client.getHistory('c2f9a8d2-1b3e-4e7c-9a11-abcdef123456')
console.log('Status:', history['c2f9a8d2-1b3e-4e7c-9a11-abcdef123456'].status)
A pool of ComfyUIClient instances for load balancing across multiple ComfyUI servers.
new ComfyUIPool(instances: InstanceConfig[], options?: PoolOptions)Parameters
interface InstanceConfig {
baseUrl: string // ComfyUI server URL, e.g. 'https://comfyui-1.example.com'
apiKey?: string // Optional API key for that instance
maxConcurrency: number // Maximum concurrent workflows on this instance
timeout?: number // Override default timeout (ms)
}
interface PoolOptions {
logging?: boolean // Enable logging for pool activities
logLevel?: LogLevel // Log level for pooled clients
}
Example
const pool = new ComfyUIPool([
{ baseUrl: 'http://server1:8188', maxConcurrency: 2 },
{ baseUrl: 'http://server2:8188', maxConcurrency: 3 },
{ baseUrl: 'http://server3:8188', maxConcurrency: 1 }
], {
logging: true,
logLevel: LogLevel.INFO
})
lease(): Promise<ClientLease>Acquire a leased client from the pool.
Returns
Promise<ClientLease>
interface ClientLease {
client: ComfyUIClient
release: () => void // Must be called when done, to return the client to the pool
}
Example
async function useLease() {
const lease = await pool.lease()
try {
const results = await lease.client.run(workflow)
console.log('Results:', results)
}
finally {
lease.release()
}
}
createSession(pipeline: ArtifactPipeline): ComfyUISession | nullStart a session that ties together a leased client + artifact pipeline. Allows you to run multiple workflows in sequence, automatically cleaning up resources and running the pipeline on each artifact.
Parameters
pipeline: An ArtifactPipeline containing one or more ArtifactProcessors.Returns
ComfyUISession | null if no clients are currently available.
Example
import { ArtifactPipeline, CosUploader } from 'comfyui-sdk'
const pipeline = new ArtifactPipeline([
new CosUploader({
secretId: process.env.COS_SECRET_ID!,
secretKey: process.env.COS_SECRET_KEY!,
bucket: 'my-bucket',
region: 'ap-shanghai',
prefix: 'comfyui-exports/',
domain: 'cdn.example.com'
})
])
const session = pool.createSession(pipeline)
if (!session)
throw new Error('No available ComfyUI clients')
try {
const artifacts = await session.run(workflow)
console.log('Session artifacts:', artifacts)
}
finally {
session.close()
}
withSession<T>(fn: (session: ComfyUISession) => Promise<T>, pipeline: ArtifactPipeline): Promise<T>Convenience wrapper: automatically acquires a session, runs your callback, then closes the session.
Example
import { ArtifactPipeline, CosUploader } from 'comfyui-sdk'
const pipeline = new ArtifactPipeline([/* processors */])
const result = await pool.withSession(async (session) => {
return session.run(workflow, { node: myConfig, inputs: { prompt: 'Auto' } })
}, pipeline)
console.log('Result from withSession:', result)
The ArtifactPipeline enables you to chain multiple ArtifactProcessor instances. Each processor can:
shouldRun logic to skip irrelevant artifactsPipelineBusimport { ArtifactPipeline, CosUploader, defineConfig } from 'comfyui-sdk'
// 1. Define custom or built-in processors
const uploader = new CosUploader({
secretId: 'AKID...',
secretKey: 'SECRET...',
bucket: 'comfyui-bucket',
region: 'ap-guangzhou',
prefix: 'outputs/',
signExpires: 0,
domain: 'cdn.example.com'
})
// 2. Instantiate the pipeline with an ordered list of processors
const pipeline = new ArtifactPipeline([
uploader,
// ... you can add more custom processors here
])
CosUploader (Tencent Cloud Object Storage)
Uploads binary artifacts to COS and attaches artifact.pipeline.cosUploader.url.
S3Uploader (AWS S3)
Equivalent functionality for AWS: configure bucket, region, optional prefix, ACL, etc.
(Custom Processors)
Extend ArtifactProcessor to build your own. See “📜 Custom Processors” below.
ProcessorOutput<M> Interface
Each processor now returns:
interface ProcessorOutput<M = unknown> {
output: M // Metadata or intermediate data
next?: Pick<Artifact, 'kind' | 'payload' | 'manifest'> // Optional transformed artifact
}
This allows you to both store processing data and replace the artifact payload/manifest in one step.
Artifact Transformation
Use the next property to pass a new Artifact shape downstream:
class WebPConverter extends ArtifactProcessor {
readonly name = 'webpConverter'
async run(artifact: Artifact): Promise<ProcessorOutput<{ originalFormat: string, newFormat: string }>> {
if (artifact.kind !== 'binary') {
return { output: { originalFormat: 'n/a', newFormat: 'n/a' } }
}
// Imagine convertToWebP returns a Buffer
const convertedBuffer = await this.convertToWebP(artifact.payload as Buffer)
return {
output: {
originalFormat: artifact.manifest.contentType,
newFormat: 'image/webp'
},
next: {
kind: 'binary',
payload: convertedBuffer,
manifest: {
...artifact.manifest,
filename: artifact.manifest.filename.replace(/\.[^.]+$/, '.webp'),
contentType: 'image/webp'
}
}
}
}
private async convertToWebP(buffer: Buffer): Promise<Buffer> {
// Conversion logic...
return buffer
}
}
shouldRun Hooks
Each processor can override shouldRun(artifact: Artifact): Promise<boolean> to decide if it should process that artifact:
class LargeFileProcessor extends ArtifactProcessor {
readonly name = 'largeFileProcessor'
async shouldRun(artifact: Artifact): Promise<boolean> {
return artifact.kind === 'binary' && (artifact.payload as Buffer).byteLength > 1_000_000
}
async run(artifact: Artifact): Promise<ProcessorOutput<Record<string, unknown>>> {
// Process large binaries only...
return { output: { processed: true } }
}
}
Pipeline State Management
Processors can share state via the artifact.pipeline object (typed by extending the PipelineBus interface).
// Extend PipelineBus in a declaration file or at top of your code
declare module 'comfyui-sdk/types' {
interface PipelineBus {
customProcessor?: CustomProcessorOutput
}
}
class CustomProcessor extends ArtifactProcessor {
readonly name = 'customProcessor'
async run(artifact: Artifact): Promise<ProcessorOutput<CustomProcessorOutput>> {
const start = Date.now()
// ...process artifact.payload
const duration = Date.now() - start
return {
output: { duration, size: (artifact.payload as Buffer).byteLength }
}
}
}
All key types are exported for maximum TypeScript support:
// Artifact can be a binary (Buffer/Blob), text, or JSON result
type Artifact =
| { kind: 'binary', payload: ArrayBuffer | Uint8Array | Buffer | Blob, manifest: BinaryManifest, pipeline?: PipelineState }
| { kind: 'text', payload: string, manifest: TextManifest, pipeline?: PipelineState }
| { kind: 'json', payload: unknown, manifest: JsonManifest, pipeline?: PipelineState }
interface BinaryManifest {
filename: string
contentType: string
size: number
// Additional fields: promptId, nodeId, etc.
}
interface TextManifest {
filename: string
contentType: string
size: number
// Additional metadata
}
interface JsonManifest {
filename: string
contentType: string
// Additional metadata
}
interface PipelineBus {}
type PipelineState = Record<string, unknown> & Partial<PipelineBus>
interface ProcessorOutput<M = unknown> {
output: M
next?: Pick<Artifact, 'kind' | 'payload' | 'manifest'>
}
abstract class ArtifactProcessor {
abstract readonly name: string
async shouldRun(artifact: Artifact): Promise<boolean>
abstract run(artifact: Artifact): Promise<ProcessorOutput>
}
interface RunOptions<TNode> {
node?: TNode
inputs?: Record<string, unknown>
}
interface ClientOptions { /* ... as above ... */ }
interface UploadOptions { /* ... as above ... */ }
interface UploadOutput { /* ... as above ... */ }
interface ComfyUISession {
run: <TNode>(workflow: WorkflowPayload, options?: RunOptions<TNode>) => Promise<Artifact[]>
uploadFile: (data: Buffer | Blob, options?: UploadOptions) => Promise<UploadOutput>
getHistory: (promptId: string) => Promise<Histories>
close: () => void
}
class ArtifactPipeline {
constructor(processors: ArtifactProcessor[])
run(artifacts: Artifact[] | Record<string, Artifact>): Promise<Artifact[] | Record<string, Artifact>>
}
import { ComfyUIClient, LogLevel } from 'comfyui-sdk'
const devClient = new ComfyUIClient({
baseUrl: 'http://localhost:8188',
logging: true,
logLevel: LogLevel.DEBUG,
timeout: 60_000,
poll: { interval: 1_000 }
})
import { ComfyUIPool, LogLevel } from 'comfyui-sdk'
const prodPool = new ComfyUIPool([
{
baseUrl: 'https://comfyui-1.example.com',
apiKey: process.env.COMFYUI_API_KEY,
maxConcurrency: 5,
timeout: 180_000
},
{
baseUrl: 'https://comfyui-2.example.com',
apiKey: process.env.COMFYUI_API_KEY,
maxConcurrency: 3,
timeout: 180_000
}
], {
logging: true,
logLevel: LogLevel.INFO
})
import {
ArtifactPipeline,
ComfyUIClient,
ComfyUIPool,
CosUploader,
defineConfig,
LogLevel
} from 'comfyui-sdk'
// 1. Configure pool of ComfyUI servers
const pool = new ComfyUIPool([
{ baseUrl: 'http://localhost:8188', maxConcurrency: 2 }
], {
logging: true,
logLevel: LogLevel.INFO
})
// 2. Build an artifact pipeline: convert to WebP → upload to COS → collect metadata
class WebPConverter extends ArtifactProcessor {
readonly name = 'webpConverter'
async run(artifact: Artifact) {
if (artifact.kind !== 'binary')
return { output: {} }
const converted = artifact.payload as Buffer // pretend conversion
return {
output: { original: artifact.manifest.contentType, converted: 'image/webp' },
next: {
kind: 'binary',
payload: converted,
manifest: {
...artifact.manifest,
contentType: 'image/webp',
filename: artifact.manifest.filename.replace(/\.\w+$/, '.webp')
}
}
}
}
}
// Tencent COS uploader
const cosUploader = new CosUploader({
secretId: process.env.COS_SECRET_ID!,
secretKey: process.env.COS_SECRET_KEY!,
bucket: 'comfyui-bucket',
region: 'ap-guangzhou',
prefix: 'outputs/',
signExpires: 0,
domain: 'cdn.example.com'
})
// Metadata collector
class MetadataCollector extends ArtifactProcessor {
readonly name = 'metadataCollector'
async run(artifact: Artifact) {
const uploadInfo = artifact.pipeline?.cosUploader
const webpInfo = artifact.pipeline?.webpConverter
const metadata = {
timestamp: new Date().toISOString(),
filename: artifact.manifest.filename,
size: artifact.kind === 'binary' ? (artifact.payload as Buffer).byteLength : 0,
uploadUrl: uploadInfo?.url || null,
conversion: webpInfo || null
}
return {
output: metadata,
next: {
kind: 'json',
payload: metadata,
manifest: {
from: artifact.manifest.from,
promptId: artifact.manifest.promptId,
filename: `metadata_${artifact.manifest.filename}.json`,
contentType: 'application/json'
}
}
}
}
}
// Assemble the pipeline
const pipeline = new ArtifactPipeline([new WebPConverter(), cosUploader, new MetadataCollector()])
// 3. Define a type-safe text-to-image pipeline
const textToImageConfig = defineConfig({
inputs: [
{ from: 'prompt', to: '6.inputs.text', required: true },
{ from: 'negative_prompt', to: '7.inputs.text', defaultValue: '' },
{ from: 'width', to: '5.inputs.width', defaultValue: 1024 },
{ from: 'height', to: '5.inputs.height', defaultValue: 1024 },
{ from: 'steps', to: '3.inputs.steps', defaultValue: 20 },
{ from: 'cfg', to: '3.inputs.cfg', defaultValue: 7 },
{ from: 'seed', to: '3.inputs.seed', defaultValue: 42 }
] as const,
outputs: [
{ from: '9', to: 'image' },
{ from: '12', to: 'metadata' }
] as const
} as const)
// 4. The SDXL text-to-image workflow
const sdxlWorkflow = {
4: { class_type: 'CheckpointLoaderSimple', inputs: { ckpt_name: 'sd_xl_base_1.0.safetensors' } },
5: { class_type: 'EmptyLatentImage', inputs: { width: 1024, height: 1024, batch_size: 1 } },
6: { class_type: 'CLIPTextEncode', inputs: { text: 'sunset over ocean', clip: ['4', 1] } },
7: { class_type: 'CLIPTextEncode', inputs: { text: '', clip: ['4', 1] } },
3: {
class_type: 'KSampler',
inputs: {
seed: 42,
steps: 20,
cfg: 7,
sampler_name: 'euler',
scheduler: 'normal',
denoise: 1,
model: ['4', 0],
positive: ['6', 0],
negative: ['7', 0],
latent_image: ['5', 0]
}
},
8: { class_type: 'VAEDecode', inputs: { samples: ['3', 0], vae: ['4', 2] } },
9: { class_type: 'SaveImage', inputs: { filename_prefix: 'ComfyUI', images: ['8', 0] } },
12: { class_type: 'SaveJSON', inputs: { data: ['8', 0], filename: 'metadata.json' } }
}
// 5. Execute within a session so artifacts flow through the pipeline
async function generateImage() {
const session = pool.createSession(pipeline)
if (!session)
throw new Error('No available clients')
try {
const { image, metadata } = await session.run(sdxlWorkflow, {
node: textToImageConfig,
inputs: {
prompt: 'A majestic dragon soaring above clouds',
width: 1920,
height: 1080,
steps: 30
}
})
console.log('Final Image Artifact:', image)
console.log('Metadata JSON:', metadata)
}
catch (err) {
console.error('Generation error:', err)
}
finally {
session.close()
}
}
generateImage()
Extend the base ArtifactProcessor to implement bespoke processing logic:
import { ArtifactProcessor, ProcessorOutput } from 'comfyui-sdk'
interface CustomProcessorOutput {
processed: boolean
originalSize: number
processedSize: number
timestamp: number
durationMs: number
}
class CustomImageProcessor extends ArtifactProcessor {
readonly name = 'customImageProcessor'
async shouldRun(artifact: Artifact): Promise<boolean> {
return artifact.kind === 'binary'
}
async run(artifact: Artifact): Promise<ProcessorOutput<CustomProcessorOutput>> {
const startTime = Date.now()
const buffer = artifact.payload as Buffer
// Placeholder for real image processing
const processedBuffer = await this.processImage(buffer)
return {
output: {
processed: true,
originalSize: buffer.byteLength,
processedSize: processedBuffer.byteLength,
timestamp: Date.now(),
durationMs: Date.now() - startTime
},
next: {
kind: 'binary',
payload: processedBuffer,
manifest: {
...artifact.manifest,
filename: `processed_${artifact.manifest.filename}`,
contentType: 'image/png'
}
}
}
}
private async processImage(buffer: Buffer): Promise<Buffer> {
// Insert actual processing here (e.g., resizing, filtering)
return buffer
}
}
// To enable full type safety on pipeline state:
declare module 'comfyui-sdk/types' {
interface PipelineBus {
customImageProcessor: CustomProcessorOutput
}
}
// Usage
const pipeline = new ArtifactPipeline([
new CustomImageProcessor(),
new CosUploader({ /* ...config */ })
])
We welcome contributions! To get started:
npm installnpm run lint && npm run buildnpm testPlease review our CONTRIBUTING.md before opening issues or PRs.
This project is licensed under the MIT License. See the LICENSE file for full details.
Crafted with care by Zane Node.js 18+ | TypeScript 4.5+
FAQs
ComfyUI SDK
We found that comfyui-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket’s Rust and Cargo support is now generally available, providing dependency analysis and supply chain visibility for Rust projects.

Security News
Chrome 144 introduces the Temporal API, a modern approach to date and time handling designed to fix long-standing issues with JavaScript’s Date object.

Research
Five coordinated Chrome extensions enable session hijacking and block security controls across enterprise HR and ERP platforms.