<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Website Development on KnightLi Blog</title>
        <link>https://knightli.com/en/tags/website-development/</link>
        <description>Recent content in Website Development on KnightLi Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Fri, 10 Jul 2026 06:24:49 +0800</lastBuildDate><atom:link href="https://knightli.com/en/tags/website-development/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>How to integrate a ComfyUI workflow API into a website: frontend, backend, queue, and results</title>
        <link>https://knightli.com/en/2026/07/10/comfyui-workflow-api-website-integration/</link>
        <pubDate>Fri, 10 Jul 2026 06:24:49 +0800</pubDate>
        
        <guid>https://knightli.com/en/2026/07/10/comfyui-workflow-api-website-integration/</guid>
        <description>&lt;p&gt;Integrating a ComfyUI workflow into a website is not just about calling one API endpoint. A production-ready flow needs a frontend form, a backend task API, parameter mapping, file upload, queue control, status polling, result storage, and user-friendly errors.&lt;/p&gt;
&lt;p&gt;The safest pattern is: the website never lets users submit arbitrary workflow JSON. The backend owns the workflow template, exposes only safe parameters, submits jobs to ComfyUI, then stores and returns results.&lt;/p&gt;
&lt;h2 id=&#34;separate-the-two-api-layers&#34;&gt;Separate the Two API Layers
&lt;/h2&gt;&lt;p&gt;There are usually two layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your website API: what the browser calls.&lt;/li&gt;
&lt;li&gt;The ComfyUI API: what your backend calls.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do not expose the ComfyUI API directly to the public internet. It is an internal rendering service, not a user-facing API gateway.&lt;/p&gt;
&lt;h2 id=&#34;minimal-website-loop&#34;&gt;Minimal Website Loop
&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User fills form
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; backend creates task
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; backend maps parameters into workflow JSON
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; backend submits to ComfyUI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; frontend polls task status
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; backend fetches generated file
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; result is displayed or stored
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This loop is enough for a first version.&lt;/p&gt;
&lt;h2 id=&#34;backend-api-design&#34;&gt;Backend API Design
&lt;/h2&gt;&lt;p&gt;A simple backend can provide:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;POST /api/generate
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GET  /api/tasks/{taskId}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GET  /api/tasks/{taskId}/result
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;POST /api/generate&lt;/code&gt; creates a task and returns &lt;code&gt;taskId&lt;/code&gt;. The frontend should not wait on this request until image generation finishes.&lt;/p&gt;
&lt;h2 id=&#34;maintain-task-status-yourself&#34;&gt;Maintain Task Status Yourself
&lt;/h2&gt;&lt;p&gt;Track states such as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;queued
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;running
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;succeeded
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;failed
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;expired
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;ComfyUI may expose queue and history data, but your product still needs its own task state so the frontend has stable semantics.&lt;/p&gt;
&lt;h2 id=&#34;do-not-let-users-submit-workflow-json&#34;&gt;Do Not Let Users Submit Workflow JSON
&lt;/h2&gt;&lt;p&gt;Treat workflow JSON as backend-owned configuration. Users should only control safe fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prompt;&lt;/li&gt;
&lt;li&gt;negative prompt;&lt;/li&gt;
&lt;li&gt;style;&lt;/li&gt;
&lt;li&gt;size;&lt;/li&gt;
&lt;li&gt;seed;&lt;/li&gt;
&lt;li&gt;uploaded image;&lt;/li&gt;
&lt;li&gt;strength or steps within limits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Letting users upload arbitrary workflow JSON can expose models, nodes, file paths, or expensive operations.&lt;/p&gt;
&lt;h2 id=&#34;keep-parameter-mapping-separate&#34;&gt;Keep Parameter Mapping Separate
&lt;/h2&gt;&lt;p&gt;Maintain a mapping layer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;form.prompt -&amp;gt; workflow node text
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;form.seed -&amp;gt; sampler seed
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;form.width -&amp;gt; latent width
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;form.height -&amp;gt; latent height
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This makes it easier to change the workflow without rewriting the frontend.&lt;/p&gt;
&lt;h2 id=&#34;image-upload-handling&#34;&gt;Image Upload Handling
&lt;/h2&gt;&lt;p&gt;For image-to-image or reference-image workflows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upload the image to your backend.&lt;/li&gt;
&lt;li&gt;Validate type and size.&lt;/li&gt;
&lt;li&gt;Store it temporarily.&lt;/li&gt;
&lt;li&gt;Pass the file to ComfyUI or place it where ComfyUI can read it.&lt;/li&gt;
&lt;li&gt;Delete temporary files after expiration.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do not trust the browser-provided filename or MIME type alone.&lt;/p&gt;
&lt;h2 id=&#34;frontend-interaction&#34;&gt;Frontend Interaction
&lt;/h2&gt;&lt;p&gt;The frontend should show:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;form validation;&lt;/li&gt;
&lt;li&gt;upload progress;&lt;/li&gt;
&lt;li&gt;queued or running state;&lt;/li&gt;
&lt;li&gt;estimated wait message;&lt;/li&gt;
&lt;li&gt;result preview;&lt;/li&gt;
&lt;li&gt;retry button;&lt;/li&gt;
&lt;li&gt;clear failure reason.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not leave the user staring at a spinner with no task state.&lt;/p&gt;
&lt;h2 id=&#34;backend-pseudocode-for-submitting-to-comfyui&#34;&gt;Backend Pseudocode for Submitting to ComfyUI
&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;receive request
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;validate user input
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;create task record
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;load workflow template
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;patch safe parameters into workflow
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;submit prompt to ComfyUI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;save prompt_id and task status
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;return taskId
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then a worker or polling process updates the task until the output is ready.&lt;/p&gt;
&lt;h2 id=&#34;where-to-store-result-files&#34;&gt;Where to Store Result Files
&lt;/h2&gt;&lt;h3 id=&#34;option-1-proxy-downloads-through-the-backend&#34;&gt;Option 1: Proxy Downloads Through the Backend
&lt;/h3&gt;&lt;p&gt;This is simple for small internal apps. The backend downloads from ComfyUI and returns the file to the browser.&lt;/p&gt;
&lt;h3 id=&#34;option-2-upload-to-object-storage&#34;&gt;Option 2: Upload to Object Storage
&lt;/h3&gt;&lt;p&gt;For public websites, upload results to S3, R2, OSS, or another object storage service. Store only the result URL and metadata in your database.&lt;/p&gt;
&lt;h2 id=&#34;why-you-need-a-queue&#34;&gt;Why You Need a Queue
&lt;/h2&gt;&lt;p&gt;Image workflows are slow and GPU resources are limited. Without a queue, users can overload the service with repeated clicks.&lt;/p&gt;
&lt;p&gt;Use a queue to control:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;concurrency;&lt;/li&gt;
&lt;li&gt;per-user rate limits;&lt;/li&gt;
&lt;li&gt;retry behavior;&lt;/li&gt;
&lt;li&gt;timeout;&lt;/li&gt;
&lt;li&gt;priority;&lt;/li&gt;
&lt;li&gt;GPU utilization.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;user-friendly-error-handling&#34;&gt;User-Friendly Error Handling
&lt;/h2&gt;&lt;p&gt;Translate internal failures into messages users can understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;invalid prompt;&lt;/li&gt;
&lt;li&gt;uploaded file too large;&lt;/li&gt;
&lt;li&gt;generation timed out;&lt;/li&gt;
&lt;li&gt;model temporarily unavailable;&lt;/li&gt;
&lt;li&gt;queue is full;&lt;/li&gt;
&lt;li&gt;result expired.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep detailed stack traces in logs, not in the browser.&lt;/p&gt;
&lt;h2 id=&#34;production-safeguards&#34;&gt;Production Safeguards
&lt;/h2&gt;&lt;p&gt;Before launch, add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;authentication or quota;&lt;/li&gt;
&lt;li&gt;upload limits;&lt;/li&gt;
&lt;li&gt;prompt and content policy checks;&lt;/li&gt;
&lt;li&gt;workflow parameter limits;&lt;/li&gt;
&lt;li&gt;queue concurrency control;&lt;/li&gt;
&lt;li&gt;timeout and cancellation;&lt;/li&gt;
&lt;li&gt;result expiration;&lt;/li&gt;
&lt;li&gt;cost monitoring;&lt;/li&gt;
&lt;li&gt;model and node allowlist;&lt;/li&gt;
&lt;li&gt;internal API protection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;runninghub-or-self-hosted-comfyui&#34;&gt;RunningHub or Self-Hosted ComfyUI?
&lt;/h2&gt;&lt;p&gt;Hosted platforms such as RunningHub reduce infrastructure work and are easier for quick product validation. Self-hosted ComfyUI gives more control over models, nodes, GPU cost, data handling, and custom workflows.&lt;/p&gt;
&lt;p&gt;Use hosted services for fast trials. Use self-hosting when you need strict control, private models, or deep customization.&lt;/p&gt;
&lt;h2 id=&#34;recommended-rollout-order&#34;&gt;Recommended Rollout Order
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Run the workflow manually in ComfyUI.&lt;/li&gt;
&lt;li&gt;Freeze one workflow template.&lt;/li&gt;
&lt;li&gt;Expose only a few safe parameters.&lt;/li&gt;
&lt;li&gt;Build backend task API.&lt;/li&gt;
&lt;li&gt;Add frontend polling.&lt;/li&gt;
&lt;li&gt;Store results.&lt;/li&gt;
&lt;li&gt;Add queue and limits.&lt;/li&gt;
&lt;li&gt;Add monitoring and cleanup.&lt;/li&gt;
&lt;li&gt;Expand to more workflows.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;faq&#34;&gt;FAQ
&lt;/h2&gt;&lt;h3 id=&#34;can-the-frontend-call-comfyui-directly&#34;&gt;Can the frontend call ComfyUI directly?
&lt;/h3&gt;&lt;p&gt;It is not recommended for public sites. Use your backend as the boundary.&lt;/p&gt;
&lt;h3 id=&#34;does-the-website-need-websocket&#34;&gt;Does the website need WebSocket?
&lt;/h3&gt;&lt;p&gt;Not necessarily. Polling is enough for a first version. WebSocket can improve real-time progress later.&lt;/p&gt;
&lt;h3 id=&#34;can-users-upload-workflow-json&#34;&gt;Can users upload workflow JSON?
&lt;/h3&gt;&lt;p&gt;Usually no. Keep workflows on the backend and expose safe parameters only.&lt;/p&gt;
&lt;h3 id=&#34;where-should-generated-results-be-stored&#34;&gt;Where should generated results be stored?
&lt;/h3&gt;&lt;p&gt;For production, object storage is usually better than keeping files on the ComfyUI machine.&lt;/p&gt;
&lt;h3 id=&#34;can-one-website-connect-multiple-workflows&#34;&gt;Can one website connect multiple workflows?
&lt;/h3&gt;&lt;p&gt;Yes. Use workflow IDs and separate parameter mappings.&lt;/p&gt;
&lt;h3 id=&#34;are-video-workflows-the-same-as-image-workflows&#34;&gt;Are video workflows the same as image workflows?
&lt;/h3&gt;&lt;p&gt;The structure is similar, but video jobs need longer timeouts, larger storage, stricter queues, and better progress feedback.&lt;/p&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary
&lt;/h2&gt;&lt;p&gt;The reliable way to integrate ComfyUI into a website is to treat it as an internal rendering engine. Your backend owns the workflow, queue, task state, storage, and safety limits. The frontend only collects safe inputs and displays progress and results.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
