
Create Comfyui Node
- 2 installs
- Updated April 19, 2026
- melonask/create-comfyui-node-skills
Teaches building and debugging modern ComfyUI custom nodes and frontend extensions using the V3 Python schema and JavaScript extension APIs.
About
Guides creating ComfyUI custom nodes with the V3 io.ComfyNode schema and frontend JavaScript extensions, preferring modern patterns over legacy V1. A developer uses it to build, modify, or debug ComfyUI nodes, extensions, or plugins.
- Modern V3 Python schema with io.ComfyNode, io.Schema, io.NodeOutput
- Covers frontend JavaScript extensions via registerExtension
Create Comfyui Node by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,958 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/melonask/create-comfyui-node-skills --skill create-comfyui-nodeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| Last updated | April 19, 2026 |
| Repository | melonask/create-comfyui-node-skills ↗ |
What it does
Teaches building and debugging modern ComfyUI custom nodes and frontend extensions using the V3 Python schema and JavaScript extension APIs.
Files
ComfyUI Custom Node Development Guide
This skill teaches you how to build modern (V3 Schema) custom nodes and frontend JavaScript extensions for ComfyUI. Always prefer V3 patterns over legacy V1 unless maintaining existing code.
1. The Modern V3 Python Schema
Use comfy_api.latest for all new nodes. The V3 API uses object-oriented definitions with io.ComfyNode, io.Schema, and io.NodeOutput.
Basic Node Anatomy
from comfy_api.latest import ComfyExtension, io, ui
class MyCustomNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="MyPrefix_MyCustomNode",
display_name="My Custom Node",
category="my_category/sub_category",
description="What this node does.",
inputs=[
io.Image.Input("image", tooltip="Input image"),
io.Int.Input("strength", default=1, min=0, max=10),
io.String.Input("text_prompt", multiline=True)
],
outputs=[
io.Image.Output(display_name="processed_image")
]
)
@classmethod
def execute(cls, image, strength, text_prompt) -> io.NodeOutput:
processed = image * strength
return io.NodeOutput(processed, ui=ui.PreviewImage(processed, cls=cls))Key differences from V1:
- Inherit from
io.ComfyNodeinstead of a plain class - Use
define_schema()instead ofINPUT_TYPESclassmethod executeis always a@classmethod(noself, usecls)- Return
io.NodeOutput(...)instead of bare tuples - Node IDs should be globally unique — prefix them (e.g.,
AuthorName_NodeAction)
Extension Entrypoint
Replace NODE_CLASS_MAPPINGS / NODE_DISPLAY_NAME_MAPPINGS with ComfyExtension and comfy_entrypoint:
class MyExtension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [MyCustomNode]
async def comfy_entrypoint() -> MyExtension:
return MyExtension()V1 to V3 Property Mapping
| V1 Property | V3 Schema Field | Notes |
|---|---|---|
RETURN_TYPES | outputs in Schema | List of Output objects |
RETURN_NAMES | display_name in Output | Per-output display names |
FUNCTION | Always execute | Method name is standardized |
CATEGORY | category in Schema | String value |
OUTPUT_NODE | is_output_node in Schema | Boolean flag |
DEPRECATED | is_deprecated in Schema | Boolean flag |
IS_CHANGED | fingerprint_inputs() | Renamed for clarity |
2. Data Types and Tensors
ComfyUI relies on PyTorch tensors. Shape mismatches are the most common bug source.
Critical tensor shapes:
- IMAGE:
torch.Tensorshape[B, H, W, C]— channel-last (C=3 for RGB) - MASK:
torch.Tensorshape[B, H, W]— often needsunsqueeze(-1)to[B, H, W, 1] - LATENT:
dictwith key"samples", tensor shape[B, C, H, W]— channel-first (C=4)
V3 Input Types Reference:
| Type | Example |
|---|---|
io.Int.Input("count", default=1, min=0, max=100) | Integer with optional min/max/step |
io.Float.Input("strength", default=1.0, min=0.0, max=10.0) | Float with optional bounds |
io.String.Input("text", default="", multiline=True) | String; multiline for text areas |
io.Boolean.Input("enabled", default=True) | Boolean toggle |
io.Combo.Input("mode", options=["A", "B", "C"]) | Dropdown selector |
io.Image.Input("image") | ComfyUI IMAGE tensor |
io.Mask.Input("mask", optional=True) | ComfyUI MASK tensor |
io.Latent.Input("latent") | ComfyUI LATENT dict |
io.Model.Input("model") | Model object |
io.CLIP.Input("clip") | CLIP object |
io.VAE.Input("vae") | VAE object |
io.Conditioning.Input("positive") | Conditioning object |
io.Custom("MY_TYPE").Input("data") | Custom datatype |
All input types share these parameters: id (required), display_name, optional, tooltip, lazy, raw_link, advanced.
3. Advanced Node Features
Hidden Inputs
Access execution context like node ID and prompt metadata. In V3, use io.Hidden in the schema and cls.hidden in execute:
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="MyNode",
inputs=[...],
hidden=[io.Hidden.unique_id, io.Hidden.prompt],
)
@classmethod
def execute(cls, ...) -> io.NodeOutput:
node_id = cls.hidden.unique_id
prompt_data = cls.hidden.prompt
return io.NodeOutput(result)Available hidden values: unique_id, prompt, extra_pnginfo, dynprompt, auth_token_comfy_org, api_key_comfy_org.
Lazy Evaluation
Skip evaluating inputs that aren't needed. Mark inputs as lazy=True and implement check_lazy_status:
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="MySwitch",
inputs=[
io.Boolean.Input("switch"),
io.Image.Input("on_false", lazy=True),
io.Image.Input("on_true", lazy=True),
],
outputs=[io.Image.Output()],
)
@classmethod
def check_lazy_status(cls, switch, on_false=None, on_true=None):
if switch and on_true is None:
return ["on_true"]
if not switch and on_false is None:
return ["on_false"]
return []
@classmethod
def execute(cls, switch, on_false, on_true) -> io.NodeOutput:
return io.NodeOutput(on_true if switch else on_false)Output Nodes
Mark nodes that save files or trigger execution:
io.Schema(
node_id="MySaveNode",
is_output_node=True,
...
)DynamicCombo (Dropdown-Dependent Inputs)
Show/hide inputs based on a dropdown selection:
io.DynamicCombo.Input("resize_type", options=[
io.DynamicCombo.Option("scale by dimensions", [
io.Int.Input("width", default=512, min=0, max=8192),
io.Int.Input("height", default=512, min=0, max=8192),
]),
io.DynamicCombo.Option("scale by multiplier", [
io.Float.Input("multiplier", default=1.0, min=0.01, max=8.0),
]),
]),In execute, the DynamicCombo parameter becomes a dict containing the selected key and its sub-inputs.
MatchType (Generic Type Matching)
Create inputs/outputs that dynamically snap to the same connected type:
template = io.MatchType.Template("switch")
io.MatchType.Input("on_false", template=template, lazy=True),
io.MatchType.Input("on_true", template=template, lazy=True),
# ...
io.MatchType.Output(template=template, display_name="output"),Autogrow (Variable Number of Inputs)
Automatically add input sockets as the user connects nodes:
template = io.Autogrow.TemplatePrefix(
input=io.Image.Input("image"),
prefix="image",
min=2,
)
io.Schema(
inputs=[io.Autogrow.Input("images", template=template)],
outputs=[io.Image.Output()],
)
# In execute, images is a dict mapping input names to their values
@classmethod
def execute(cls, images: io.Autogrow.Type) -> io.NodeOutput:
image_list = list(images.values())
return io.NodeOutput(torch.cat(image_list, dim=0))Async Execution and Progress Reporting
from comfy_api.latest import ComfyAPI
api = ComfyAPI()
class AsyncNode(io.ComfyNode):
@classmethod
async def execute(cls, images) -> io.NodeOutput:
total = len(images)
for i, img in enumerate(images):
# process img...
await api.execution.set_progress(
value=i + 1,
max_value=total,
preview_image=img,
)
return io.NodeOutput(result)4. UI Helpers (Preview and Save)
V3 provides built-in helpers in the ui module for common output patterns:
from comfy_api.latest import ui
# Preview image in node
return io.NodeOutput(images, ui=ui.PreviewImage(images, cls=cls))
# Preview mask (auto-converts to 3-channel)
return io.NodeOutput(mask, ui=ui.PreviewMask(mask, cls=cls))
# Preview audio
return io.NodeOutput(audio, ui=ui.PreviewAudio(audio, cls=cls))
# Preview text
return io.NodeOutput(ui=ui.PreviewText("Result text"))
# Save image with metadata
return io.NodeOutput(ui=ui.ImageSaveHelper.get_save_images_ui(
images=images, filename_prefix=prefix, cls=cls
))
# Pass cls=cls to embed workflow metadata when using hidden prompt/extra_pnginfo5. Node Replacement (Migration)
If renaming or restructuring nodes, register replacements so existing workflows auto-upgrade. Use ComfyAPI in your extension's on_load:
from comfy_api.latest import ComfyAPI, ComfyExtension, io
api = ComfyAPI()
class MyExtension(ComfyExtension):
async def on_load(self) -> None:
await api.node_replacement.register(io.NodeReplace(
new_node_id="MyPrefix_NewNode",
old_node_id="OldNodeName",
old_widget_ids=["param1", "param2"],
input_mapping=[
{"new_id": "image", "old_id": "input_image"},
{"new_id": "method", "set_value": "lanczos"},
],
output_mapping=[
{"new_idx": 0, "old_idx": 0},
],
))6. Frontend JavaScript Extensions
Declare the web directory in your Python module:
WEB_DIRECTORY = "./js"Place .js files in that directory. Use modern extension hooks, not legacy prototype monkey-patching.
Basic Extension
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "MyNamespace.MyExtension",
async setup() {
console.log("Extension loaded");
},
});Context Menus
app.registerExtension({
name: "MyNamespace.MyMenus",
getCanvasMenuItems(canvas) {
return [
null,
{
content: "My Canvas Action",
callback: () => {
/* ... */
},
},
];
},
getNodeMenuItems(node) {
if (node.comfyClass === "MyPrefix_MyNode") {
return [
{
content: "Do Something",
callback: () => {
/* ... */
},
},
];
}
return [];
},
});Commands, Keybindings, and Selection Toolbox
app.registerExtension({
name: "MyNamespace.Commands",
commands: [
{
id: "myExt.doAction",
label: "Do Action",
function: () => {
/* ... */
},
},
],
keybindings: [
{
combo: { key: "k", ctrl: true, shift: true },
commandId: "myExt.doAction",
},
],
getSelectionToolboxCommands: (selectedItem) => {
const count = app.canvas.selectedItems?.size || 0;
if (count > 1) return ["myExt.doAction"];
return [];
},
});Sidebar and Bottom Panel Tabs
app.extensionManager.registerSidebarTab({
id: "mySidebar",
icon: "pi pi-list",
title: "My Tools",
type: "custom",
render: (el) => {
el.innerHTML = "<div>Content</div>";
},
});
app.registerExtension({
name: "MyNamespace.BottomTab",
bottomPanelTabs: [
{
id: "myLogs",
title: "Custom Logs",
type: "custom",
render: (el) => {
el.innerHTML = "<div>Log data</div>";
},
},
],
});Dialogs, Toasts, Settings
// Prompt dialog
app.extensionManager.dialog
.prompt({ title: "Input", message: "Enter value:" })
.then((result) => {
/* result is string or null */
});
// Confirm dialog
app.extensionManager.dialog
.confirm({ title: "Confirm", message: "Continue?" })
.then((result) => {
/* result is boolean or null */
});
// Toast notification
app.extensionManager.toast.add({
severity: "success",
summary: "Done!",
life: 3000,
});
// Settings
app.registerExtension({
name: "MyNamespace.Settings",
settings: [
{
id: "MyExt.enableDebug",
name: "Enable Debug Mode",
type: "boolean",
defaultValue: false,
onChange: (newVal) => {
console.log("Debug:", newVal);
},
},
],
});About Page Badges and Top Bar Menus
app.registerExtension({
name: "MyNamespace.Badges",
aboutPageBadges: [
{
label: "GitHub",
url: "https://github.com/my/repo",
icon: "pi pi-github",
},
],
menuCommands: [
{ path: ["Extensions", "My Extension"], commands: ["myExt.doAction"] },
],
});For deeper frontend APIs (subgraphs, i18n, node docs, detailed JS object reference), see references/advanced_frontend_and_i18n.md.
7. Directory Structure
Use this standard layout for ComfyUI Manager compatibility:
my-custom-nodes/
├── __init__.py # Exports WEB_DIRECTORY and comfy_entrypoint
├── nodes.py # V3 ComfyNode definitions
├── requirements.txt # Pip dependencies (Manager auto-installs)
├── install.py # Optional: run by Manager on install
├── js/ # WEB_DIRECTORY for frontend extensions
│ └── my_extension.js
├── subgraphs/ # .json files become available as subgraph blueprints
└── example_workflows/ # .json + .jpg files appear in Workflow Templates browser8. Checklist: AI Instructions
When building ComfyUI custom nodes:
1. Ask clarifying questions if data types or tensor shapes are ambiguous 2. Always use V3 API (from comfy_api.latest import io). Only generate V1 INPUT_TYPES dicts if maintaining existing legacy code 3. Prefix node IDs to ensure global uniqueness (e.g., AuthorName_NodeAction) 4. Remember: Images are [B, H, W, 3] (channel-last), Latents are {"samples": [B, 4, H, W]} (channel-first), Masks are [B, H, W] 5. For UI modifications, use modern extension hooks (getCanvasMenuItems, getNodeMenuItems, commands, settings) rather than LGraphCanvas.prototype monkey-patching 6. When scaffolding a new package, include example_workflows/ and requirements.txt for Manager compatibility 7. Return io.NodeOutput(...) from execute, always — even io.NodeOutput() for no-output nodes 8. Use ui.PreviewImage / ui.ImageSaveHelper for common output patterns instead of manual file handling 9. For lazy evaluation, add lazy=True to input definitions and implement check_lazy_status 10. Use io.DynamicCombo instead of manual JS widget toggling for mode-dependent inputs
create-comfyui-node
A skill for building modern ComfyUI custom nodes and frontend extensions.
Structure
create-comfyui-node/
├── SKILL.md # Core skill (448 lines)
└── references/
└── advanced_frontend_and_i18n.md # Frontend, i18n, object model referenceWhat It Covers
SKILL.md — Primary skill loaded on trigger. Contains:
- V3 Python Schema —
io.ComfyNode,io.Schema,io.NodeOutput, extension entrypoints - Data Types & Tensors — Shape conventions (IMAGE
[B,H,W,C], MASK[B,H,W], LATENT{"samples": [B,C,H,W]}), complete V3 input type reference - Advanced Node Features — Hidden inputs, lazy evaluation, DynamicCombo, MatchType, Autogrow, async execution, progress reporting
- UI Helpers —
PreviewImage,PreviewMask,ImageSaveHelper,PreviewAudio,PreviewText - Node Replacement — Migration API for renaming/restructuring nodes
- Frontend JS Extensions — Context menus, commands, keybindings, sidebar/bottom panel tabs, dialogs, toasts, settings, badges
- Directory Structure — Standard layout for ComfyUI Manager compatibility
- AI Instruction Checklist — 10 rules for consistent node generation
references/advanced_frontend_and_i18n.md — Loaded on demand for deeper frontend work:
- Node documentation (help pages with markdown)
- Internationalization (i18n directory structure, JSON formats)
- Full extension hooks reference table with call sequences
- ComfyUI object model (
app,ComfyNode,LGraph,LLink) - Subgraph development (identifiers, traversal, events, blueprints)
- Server-client communication (
PromptServer.send_sync, API events) - Common JS patterns (React in tabs, AbortController cleanup, submenu menus)
Triggering
The skill activates when a user mentions ComfyUI custom nodes, comfy_entrypoint, ComfyExtension, io.ComfyNode, registerExtension, or any task involving building functionality for the ComfyUI node-based workflow editor.
Key Principles
- Always generates V3 schema code by default (uses
from comfy_api.latest import io) - Only produces V1
INPUT_TYPEScode when explicitly maintaining legacy code - Node IDs are prefixed for global uniqueness (e.g.,
AuthorName_NodeAction) - Frontend code uses modern extension hooks, not deprecated prototype monkey-patching
- Returns
io.NodeOutput(...)from all execute methods
Sources
Extracted and distilled from the official ComfyUI custom-nodes documentation covering:
- V3 migration guide and schema reference
- Backend datatypes, tensors, lazy evaluation, node expansion
- JavaScript hooks, context menus, settings, sidebar/bottom panel APIs
- i18n support, help pages, subgraph blueprints
- ComfyUI Manager integration and lifecycle
Advanced ComfyUI Frontend, i18n, and Object Reference
This reference covers advanced frontend JavaScript APIs, internationalization, node documentation, and the ComfyUI object model. Read this when working on menus, sidebars, keybindings, subgraphs, multi-language support, or deep DOM/graph manipulation.
Table of Contents
1. Node Documentation (Help Pages) 2. Internationalization (i18n) 3. Extension Hooks Reference 4. ComfyUI Object Model 5. Subgraphs 6. Server-Client Communication 7. Common JS Patterns
---
1. Node Documentation (Help Pages)
Custom nodes can include rich markdown documentation that appears in the UI.
Structure
Place .md files in a docs/ folder inside your WEB_DIRECTORY:
my_extension/
└── js/ # WEB_DIRECTORY
└── docs/
├── MyPrefix_MyNode.md # Default documentation
└── MyPrefix_MyNode/
├── en.md # English version
└── zh.md # Chinese versionThe filename must exactly match the Node ID (the node_id in the V3 Schema, or the dictionary key in NODE_CLASS_MAPPINGS for V1).
Supported Content
Standard markdown syntax plus <video> tags with controls, autoplay, loop, muted, preload, and poster attributes. Images use .
---
2. Internationalization (i18n)
Directory Structure
Create a locales folder at your custom node root:
my_extension/
├── __init__.py
├── nodes.py
└── locales/
├── en/
│ ├── main.json # General translations, settingsCategories
│ ├── nodeDefs.json # Node definition translations
│ ├── settings.json # Settings UI translations
│ └── commands.json # Command translations (optional)
└── zh/
├── main.json
├── nodeDefs.json
└── settings.jsonnodeDefs.json Format
{
"MyPrefix_MyNode": {
"display_name": "My Custom Node",
"description": "What this node does.",
"inputs": {
"image": {
"name": "Input Image",
"tooltip": "The image to process"
}
},
"outputs": {
"0": {
"name": "Processed Image",
"tooltip": "The result"
}
}
}
}Output keys use indices ("0", "1") not names. For combo options, add an "options" object mapping option values to localized labels.
settings.json Format
Keys replace . with _ in the setting ID:
{
"MyExt_EnableDebugMode": {
"name": "Enable Debug Mode",
"tooltip": "Show debug information"
},
"MyExt_DefaultOperation": {
"name": "Default Operation",
"options": {
"uppercase": "To Uppercase",
"lowercase": "To Lowercase"
}
}
}main.json Format
Include settingsCategories to map category IDs to display names:
{
"settingsCategories": {
"MyExt": "My Extension",
"DebugMode": "Debug Mode"
}
}---
3. Extension Hooks Reference
Available hooks in app.registerExtension({...}):
| Hook | When | Description |
|---|---|---|
init() | Page load, before graph created | Modify core Comfy behavior |
setup() | End of startup | Add event listeners, DOM manipulation |
beforeRegisterNodeDef(nodeType, nodeData, app) | For each node type | Modify node class prototypes |
nodeCreated(node) | When a node instance is created | Modify individual node instances |
beforeConfigureGraph() | Before workflow is loaded | Pre-load setup |
afterConfigureGraph() | After workflow is loaded | Post-load actions |
loadedGraphNode(node) | For each loaded node | Per-node post-load |
getCanvasMenuItems(canvas) | Canvas right-click menu | Return array of menu items |
getNodeMenuItems(node) | Node right-click menu | Return array of menu items |
commands | On registration | Array of command definitions |
keybindings | On registration | Array of keybinding definitions |
menuCommands | On registration | Array of menu command mappings |
bottomPanelTabs | On registration | Array of bottom panel tab definitions |
aboutPageBadges | On registration | Array of badge definitions for About page |
settings | On registration | Array of setting definitions |
getSelectionToolboxCommands(item) | On selection change | Return command IDs for selection toolbox |
Hook Call Sequence (Page Load)
init → addCustomNodeDefs → getCustomWidgets → beforeRegisterNodeDef (×N)
→ registerCustomNodes → beforeConfigureGraph → nodeCreated (×N)
→ loadedGraphNode (×N) → afterConfigureGraph → setup---
4. ComfyUI Object Model
ComfyApp (app)
Import: import { app } from "../../scripts/app.js";
Key properties: canvas (LGraphCanvas), canvasEl (DOM canvas), graph (LGraph), runningNodeId, ui
Key functions: graphToPrompt(), loadGraphData(), queuePrompt(), registerExtension()
Extension manager: app.extensionManager provides:
app.extensionManager.dialog.prompt({title, message, defaultValue})→ Promiseapp.extensionManager.dialog.confirm({title, message, type})→ Promiseapp.extensionManager.toast.add({severity, summary, detail, life})→ voidapp.extensionManager.toast.addAlert(message)→ voidapp.extensionManager.setting.get(id)/.set(id, value)→ value / Promiseapp.extensionManager.registerSidebarTab({id, icon, title, type, render})app.extensionManager.registerBottomPanelTab({id, title, type, render})
ComfyNode (extends LGraphNode)
Properties: comfyClass, id, inputs, outputs, widgets, widgets_values, mode, pos, size, title, type, flags, properties
Widget properties: .name, .type, .value, .options, .callback, .last_y
LGraph (app.graph)
graph._nodes— array of all nodesgraph._nodes_by_id(id)— get node by IDgraph.links— dictionary of LLink objects
LLink (connection)
link.origin_id,link.origin_slot,link.target_id,link.target_slot,link.type
---
5. Subgraphs
Subgraphs allow nested, reusable node groups. Key concerns for extensions:
Node Identifiers
| Type | Format | Used For |
|---|---|---|
node.id | Number (e.g., 42) | Local to graph level |
| Execution ID | "1:2:3" (string) | Backend progress, UNIQUE_ID |
| Locator ID | "<uuid>:<localId>" or "<localId>" | UI badges, errors |
Traversing All Nodes (Including Nested Subgraphs)
function walkGraph(graph, callback) {
for (const node of graph.nodes ?? []) {
callback(node, graph);
if (node.subgraph) walkGraph(node.subgraph, callback);
}
}Subgraph Events
// On subgraph.events
("widget-promoted",
"widget-demoted",
"input-added",
"removing-input",
"output-added",
"removing-output",
"renaming-input",
"renaming-output");
// On app.canvas.canvas
("subgraph-opened", "subgraph-converted");Subgraph Blueprints
Place .json workflow files in a subgraphs/ folder in your custom node directory. They become globally available blueprint entries.
---
6. Server-Client Communication
Sending Messages from Python
from server import PromptServer
PromptServer.instance.send_sync("my_extension.event_name", {"key": "value"})Receiving Messages in JavaScript
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "MyExtension.Events",
async setup() {
api.addEventListener("my_extension.event_name", (event) => {
console.log(event.detail); // { key: "value" }
});
},
});Common API Events
"execution_start"— workflow execution begins"execution_cached"— nodes using cached results"executing"—{ node: id }/{ node: null }when done"executed"—{ node: id, output: {...} }"execution_error"— error details"progress"—{ value, max }progress updates"status"— queue status
---
7. Common JS Patterns
Detecting Workflow Start
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "MyExtension.WorkflowStart",
async setup() {
api.addEventListener("execution_start", () => {
console.log("Workflow started");
});
},
});Accessing Selected Nodes
const selectedItems = app.canvas.selectedItems; // Set of items
const selectedNodes = app.canvas.selected_nodes; // Object of nodes (legacy)Submenu Context Menu Items
getNodeMenuItems(node) {
return [{
content: "Advanced Options",
submenu: {
options: [
{ content: "Option A", callback: () => {} },
{ content: "Option B", callback: () => {} }
]
}
}];
}React Components in Tabs
import React from "react";
import ReactDOM from "react-dom/client";
app.extensionManager.registerSidebarTab({
id: "myReactTab",
icon: "mdi mdi-react",
title: "React Tab",
type: "custom",
render: (el) => {
const container = document.createElement("div");
el.appendChild(container);
ReactDOM.createRoot(container).render(
<React.StrictMode>
<MyComponent />
</React.StrictMode>,
);
},
});Clean Event Listener Management
Use AbortController for cleanup:
const controller = new AbortController();
const { signal } = controller;
api.addEventListener("my_event", handler, { signal });
// Later: controller.abort() removes all listenersTop Bar Menu Integration
app.registerExtension({
name: "MyExtension.TopBar",
commands: [
{
id: "myExt.help",
label: "Help",
function: () => {
/* ... */
},
},
],
menuCommands: [
{ path: ["Extensions", "My Extension"], commands: ["myExt.help"] },
],
});---
For ComfyUI Manager integration, node lifecycle, and validation patterns, consult the backend documentation in ComfyUI's official docs.