diff --git a/development/samples/overview.mdx b/development/samples/overview.mdx
new file mode 100644
index 000000000..c8b24e8ab
--- /dev/null
+++ b/development/samples/overview.mdx
@@ -0,0 +1,133 @@
+---
+title: "Demo Apps"
+description: "Start from a working demo app built on the Comfy API."
+mode: "wide"
+hideFooterPagination: true
+---
+
+Start from a working demo app, then adapt the workflow and interface for your own project. Choose Comfy Cloud for the fastest path, or Deploy Your Own when you need your own models or nodes.
+
+
+
+
+
+
+
+
Image-to-image web app
+
Upload an image, run a Comfy workflow, and show the result in a Next.js app.
+
+ Use Comfy Cloud
+ Deploy Your Own
+
+
+
+
+
+
+
← Back to demo app
+
Comfy Cloud
Use Image-to-image web app with Comfy Cloud
+
Use Comfy Cloud as the managed endpoint. Create an API key, then deploy a copy of the app.
+
+
+
2 Deploy app to Vercel The provider creates a copy of this sample in your account.
Deploy app ↗
+
+
+
+
+
+
← Back to demo app
+
Deploy Your Own
Deploy your own endpoint for Image-to-image web app
+
Create a Comfy API serverless endpoint when you need your own models or custom nodes.
+
+
1 Download workflow Use this API-format workflow in the dedicated deployment flow.
Download workflow ↗
+
2 Create API key Create and save the key your deployed app will use.
Create API key ↗
+
3 Paste Workflow and Deploy In Builds, paste the workflow, configure the endpoint, and wait for its URL.
Open builds ↗
+
4 Paste endpoint URL and API key Set COMFY_BASE_URL and COMFY_API_KEY during app deployment.
Deploy app ↗
+
+
+
+
+
+
+
+
+
+
+
+
Sketch to image Draw or import a guide, then generate an image from the guide and a prompt.
+
Use Comfy Cloud Deploy Your Own
+
+
+
+
+
+
← Back to demo app
+
Comfy Cloud
+
Use Sketch to image with Comfy Cloud
+
Use Comfy Cloud as the managed endpoint. Create an API key, then deploy a copy of the app.
+
+
+
2 Deploy app to Vercel The provider creates a copy of this sample in your account.
Deploy app ↗
+
+
+
+
+
← Back to demo app
+
Deploy Your Own
+
Deploy your own endpoint for Sketch to image
+
Create a Comfy API serverless endpoint when you need your own models or custom nodes.
+
+
1 Download workflow Use this API-format workflow in the dedicated deployment flow.
Download workflow ↗
+
2 Create API key Create and save the key your deployed app will use.
Create API key ↗
+
3 Paste Workflow and Deploy In Builds, paste the workflow, configure the endpoint, and wait for its URL.
Open builds ↗
+
4 Paste endpoint URL and API key Set COMFY_BASE_URL and COMFY_API_KEY during app deployment.
Deploy app ↗
+
+
+
+
+
+
+
+
+
+
+
Discord image bot Add an `/imagine` command to a Discord server and run the bundled Comfy workflow.
+
Use Comfy Cloud Deploy Your Own
+
+
+
+
+
+
← Back to demo app
+
Comfy Cloud
+
Use Discord image bot with Comfy Cloud
+
Use Comfy Cloud as the managed endpoint. Create an API key, then deploy a copy of the app.
+
+
+
2 Deploy app to Render The provider creates a copy of this sample in your account.
Deploy app ↗
+
+
+
+
+
← Back to demo app
+
Deploy Your Own
+
Deploy your own endpoint for Discord image bot
+
Create a Comfy API serverless endpoint when you need your own models or custom nodes.
+
+
1 Download workflow Use this API-format workflow in the dedicated deployment flow.
Download workflow ↗
+
2 Create API key Create and save the key your deployed app will use.
Create API key ↗
+
3 Paste Workflow and Deploy In Builds, paste the workflow, configure the endpoint, and wait for its URL.
Open builds ↗
+
4 Paste endpoint URL and API key Set COMFY_BASE_URL and COMFY_API_KEY during app deployment.
Deploy app ↗
+
+
+
+
+
+
+
+Both paths need a [Comfy API key](https://platform.comfy.org/profile/api-keys). For Deploy Your Own, download the workflow, use Builds to paste it and configure the serverless endpoint, wait for its URL, then paste that URL and the API key into your Vercel or Render deployment.
+
+
+Want to add a demo app? See the [examples repository](https://github.com/Comfy-Org/comfy-examples).
diff --git a/docs.json b/docs.json
index 79f6017a8..a8f05c869 100644
--- a/docs.json
+++ b/docs.json
@@ -3028,6 +3028,12 @@
}
]
},
+ {
+ "group": "Demo Apps",
+ "pages": [
+ "development/samples/overview"
+ ]
+ },
{
"group": "Comfy Router",
"pages": [
@@ -13897,4 +13903,4 @@
"destination": "/tutorials/partner-nodes/wan/wan3-0"
}
]
-}
\ No newline at end of file
+}
diff --git a/images/samples/discord-image-bot.svg b/images/samples/discord-image-bot.svg
new file mode 100644
index 000000000..4a60c6991
--- /dev/null
+++ b/images/samples/discord-image-bot.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ # imagine
+
+
+ C
+ /imagine
+ a lavender cat in sunlight
+
+ C
+ Comfy bot
+ Your image is ready
+
+
+
+
+
diff --git a/images/samples/img2img-web-app.svg b/images/samples/img2img-web-app.svg
new file mode 100644
index 000000000..445abb678
--- /dev/null
+++ b/images/samples/img2img-web-app.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+ Upload an image
+
+
+
+
+
+
+ Generated image
+
+
diff --git a/images/samples/sketch-to-image.svg b/images/samples/sketch-to-image.svg
new file mode 100644
index 000000000..681fd26b0
--- /dev/null
+++ b/images/samples/sketch-to-image.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+ Draw a guide
+
+
+
+
+
+ Generate from sketch
+
+
diff --git a/samples-cards.css b/samples-cards.css
new file mode 100644
index 000000000..b857b168b
--- /dev/null
+++ b/samples-cards.css
@@ -0,0 +1,31 @@
+.sample-card-grid { display: grid; gap: 20px; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 28px; }
+.sample-card { min-width: 0; perspective: 1200px; }
+.sample-card-inner { height: 565px; position: relative; transform-style: preserve-3d; transition: transform 560ms cubic-bezier(.2, .75, .3, 1); }
+.sample-card[data-flipped="true"] .sample-card-inner { transform: rotateY(180deg); }
+.sample-card-face { backface-visibility: hidden; background: #fff; border: 1px solid #e5e1eb; border-radius: 12px; box-sizing: border-box; height: 100%; inset: 0; overflow: hidden; position: absolute; width: 100%; }
+.sample-card-front { display: flex; flex-direction: column; }
+.sample-card-back { opacity: 0; pointer-events: none; transform: rotateY(180deg); visibility: hidden; }
+.sample-card[data-flipped="true"] .sample-card-front { pointer-events: none; transition: visibility 0s linear 280ms; visibility: hidden; }
+.sample-card[data-flipped="true"][data-active-path="api"] .sample-card-back[data-sample-path="api"], .sample-card[data-flipped="true"][data-active-path="endpoint"] .sample-card-back[data-sample-path="endpoint"] { opacity: 1; pointer-events: auto; visibility: visible; }
+.sample-card-preview { border-bottom: 1px solid #e5e1eb; display: block; width: 100%; }
+.sample-card-body { display: flex; flex: 1; flex-direction: column; padding: 20px; }
+.sample-card-body h2, .sample-path-body h2 { color: #49378b; font-size: 18px; font-weight: 700; letter-spacing: -.02em; margin: 0 0 8px; }
+.sample-path-body h2 { text-align: center; }
+.sample-card-body > p { color: #5f5966; font-size: 14px; line-height: 1.55; margin: 0; min-height: 66px; }
+.sample-card-actions { display: flex; gap: 8px; margin-top: 20px; }
+.sample-card-actions button { background: #fff; border: 1px solid #cfc8de; border-radius: 6px; color: #49378b; cursor: pointer; flex: 1; font-size: 13px; font-weight: 700; padding: 10px 12px; }
+.sample-card-links { border-top: 1px solid #eeeaf1; display: flex; gap: 8px; margin-top: 16px; padding-top: 12px; }
+.sample-card-links a { color: #625b6c; font-size: 12px; font-weight: 650; text-decoration: none; }
+.sample-card-links span { color: #b3adba; }
+.sample-path-body { padding: 20px; }
+.sample-path-body > button { background: transparent; border: 0; color: #625b6c; cursor: pointer; font-size: 12px; font-weight: 700; padding: 0; }
+.sample-path-eyebrow { color: #857a9e; font-size: 10px; font-weight: 700; letter-spacing: .08em; margin: 19px 0 5px; text-align: center; text-transform: uppercase; }
+.sample-path-body > p:not(.sample-path-eyebrow) { color: #5f5966; font-size: 12px; line-height: 1.5; margin: 0 0 15px; }
+.sample-path-steps { display: grid; gap: 8px; }
+.sample-path-step { align-items: center; background: #faf9fc; border: 1px solid #e5e1ea; border-radius: 8px; display: flex; gap: 10px; padding: 10px 11px; }
+.sample-path-number { align-items: center; background: #49378b; border-radius: 999px; color: #fff; display: inline-flex; flex: 0 0 21px; font-size: 11px; font-weight: 700; height: 21px; justify-content: center; }
+.sample-path-copy { flex: 1; min-width: 0; }
+.sample-path-copy strong { color: #312c36; display: block; font-size: 12px; }
+.sample-path-copy span { color: #706a75; display: block; font-size: 11px; line-height: 1.35; margin-top: 2px; }
+.sample-path-step a { color: #49378b; flex: 0 0 auto; font-size: 11px; font-weight: 700; text-decoration: none; }
+@media (max-width: 768px) { .sample-card-grid { grid-template-columns: 1fr; } }
diff --git a/samples-cards.js b/samples-cards.js
new file mode 100644
index 000000000..650e97f62
--- /dev/null
+++ b/samples-cards.js
@@ -0,0 +1,34 @@
+document.addEventListener("click", (event) => {
+ const openButton = event.target.closest("[data-sample-open]");
+ const backButton = event.target.closest("[data-sample-back]");
+
+ if (openButton) {
+ const card = openButton.closest("[data-sample-card]");
+ const path = openButton.dataset.sampleOpen;
+ const front = card.querySelector("[data-sample-front]");
+ const selectedPath = card.querySelector(`[data-sample-path="${path}"]`);
+ event.preventDefault();
+ card.dataset.activePath = path;
+ card.dataset.flipped = "true";
+ front.inert = true;
+ front.setAttribute("aria-hidden", "true");
+ card.querySelectorAll("[data-sample-path]").forEach((panel) => {
+ panel.inert = panel !== selectedPath;
+ panel.setAttribute("aria-hidden", panel === selectedPath ? "false" : "true");
+ });
+ }
+
+ if (backButton) {
+ const card = backButton.closest("[data-sample-card]");
+ const front = card.querySelector("[data-sample-front]");
+ event.preventDefault();
+ card.removeAttribute("data-flipped");
+ card.removeAttribute("data-active-path");
+ front.inert = false;
+ front.setAttribute("aria-hidden", "false");
+ card.querySelectorAll("[data-sample-path]").forEach((panel) => {
+ panel.inert = true;
+ panel.setAttribute("aria-hidden", "true");
+ });
+ }
+});