ComfyUI/custom_nodes/frontend_extension.js.example
Abhijeet Dhope 42a42623e4
Add comprehensive frontend extension example
This adds a heavily commented JavaScript example file (frontend_extension.js.example) to the custom_nodes directory. The example demonstrates:

- Extension registration and lifecycle hooks
- Working with node properties including node.imgs
- Adding custom widgets and UI elements
- API communication and WebSocket messaging
- Workflow execution hooks
- Best practices and debugging tips

This addresses issue #3603 by providing developers with a complete, well-documented example similar to the existing example_node.py.example file.
2025-11-22 13:27:00 +05:30

341 lines
12 KiB
Plaintext

/**
* ComfyUI Frontend Extension Example
*
* This file demonstrates how to create a frontend extension for ComfyUI.
* Frontend extensions allow you to customize and extend the ComfyUI web interface
* by adding new functionality, modifying node behavior, or creating custom UI elements.
*
* IMPORTANT: This is an EXAMPLE file. To use it:
* 1. Remove the .example extension (rename to frontend_extension.js)
* 2. Place it in a subdirectory of your custom node (conventionally called 'js' or 'web/js')
* 3. Export WEB_DIRECTORY in your Python module's __init__.py:
* ```
* WEB_DIRECTORY = "./js" # or "./web/js"
* __all__ = ["NODE_CLASS_MAPPINGS", "WEB_DIRECTORY"]
* ```
*
* @see https://docs.comfy.org/custom-nodes/js/javascript_overview
* @see custom_nodes/example_node.py.example for the Python counterpart
*/
// Import the main ComfyUI app object
// The app object provides access to the ComfyUI API and allows you to register extensions
import { app } from "../../scripts/app.js";
/**
* EXTENSION REGISTRATION
* ======================
*
* To create a frontend extension, you need to:
* 1. Import the app object from ComfyUI
* 2. Call app.registerExtension() with an extension configuration object
* 3. The configuration object must have a unique 'name' property
* 4. Add one or more lifecycle hooks or event handlers
*/
app.registerExtension({
// REQUIRED: Unique name for your extension
// Use a namespace to avoid conflicts (e.g., "company.project.feature")
name: "example.frontend.extension",
/**
* LIFECYCLE HOOKS
* ===============
* These methods are called at specific points in the ComfyUI lifecycle.
* All hooks are optional - only implement the ones you need.
*/
/**
* setup() - Called once when the extension is first loaded
*
* This is typically used for:
* - Registering event listeners
* - Adding global UI elements
* - Initializing extension state
* - Setting up API communications
*
* @returns {Promise<void>} Can return a promise for async operations
*/
async setup() {
console.log("[Example Extension] Setup complete!");
// Example: Listen for custom events from the server
// When your Python node sends a message via PromptServer.instance.send_sync(),
// you can listen for it here
app.api.addEventListener("example.custom.event", (event) => {
console.log("Received custom event:", event.detail);
});
},
/**
* async beforeRegisterNodeDef(nodeType, nodeData, app)
*
* Called BEFORE each node type is registered with LiteGraph.
* This allows you to modify node definitions before they're used.
*
* Use cases:
* - Add custom widgets to nodes
* - Modify node appearance
* - Add custom node behaviors
* - Override node methods
*
* @param {Function} nodeType - The node class constructor
* @param {Object} nodeData - The node's metadata from the server
* @param {Object} app - The ComfyUI app instance
*/
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// Example: Only modify nodes with a specific class name
if (nodeData.name === "ExampleNode") {
console.log("Modifying ExampleNode before registration");
// You can modify the prototype to change node behavior
// For example, override the onExecuted method to handle results
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function(message) {
// Call the original method first
onExecuted?.apply(this, arguments);
// Add your custom logic
console.log("ExampleNode executed with output:", message);
};
}
},
/**
* async nodeCreated(node)
*
* Called when a new node instance is created.
* This happens when:
* - A node is added to the canvas
* - A workflow is loaded
*
* Use cases:
* - Initialize node-specific state
* - Add custom properties to node instances
* - Set up node-level event handlers
*
* @param {LGraphNode} node - The newly created node instance
*/
async nodeCreated(node) {
if (node.comfyClass === "ExampleNode") {
console.log("ExampleNode instance created:", node);
// Example: Add a custom property
node.customProperty = "custom value";
// Example: Add a custom callback that runs when widgets change
const onWidgetChange = node.onWidgetChanged;
node.onWidgetChanged = function(name, value, oldValue, widget) {
onWidgetChange?.apply(this, arguments);
console.log(`Widget '${name}' changed from ${oldValue} to ${value}`);
};
}
},
/**
* async loadedGraphNode(node, app)
*
* Called when a node is loaded from a saved workflow.
* Similar to nodeCreated but specifically for loaded workflows.
*
* @param {LGraphNode} node - The loaded node
* @param {Object} app - The ComfyUI app instance
*/
async loadedGraphNode(node, app) {
if (node.comfyClass === "ExampleNode") {
console.log("ExampleNode loaded from workflow");
}
},
/**
* IMPORTANT NODE FIELDS AND PROPERTIES
* ====================================
*
* When working with nodes, these are key properties you'll use:
*
* node.comfyClass - String identifying the node type (e.g., "ExampleNode")
* node.type - The LiteGraph node type
* node.id - Unique numeric ID for this node instance
* node.title - Display title of the node
* node.properties - Object containing node properties
* node.widgets - Array of UI widgets (inputs, sliders, etc.)
* node.imgs - Array of image data for nodes that output images
* Format: [{src: "data:image/png;base64,...", type: "output"}]
* This is commonly used for preview nodes
* node.size - [width, height] array for node dimensions
* node.pos - [x, y] array for node position on canvas
*/
/**
* WORKING WITH IMAGES
* ===================
*
* The node.imgs field is particularly important for image-handling nodes.
* It's used by ComfyUI to display image previews on nodes.
*
* Example: Accessing images from a node
*/
async nodeCreated(node) {
if (node.comfyClass === "PreviewImage" || node.comfyClass === "SaveImage") {
// Images will be populated in node.imgs after execution
// Each image object has: {src: base64Data, type: "output" or "temp"}
// You can add a callback to detect when images are updated
const originalCallback = node.callback;
node.callback = function() {
originalCallback?.apply(this, arguments);
if (this.imgs && this.imgs.length > 0) {
console.log(`Node has ${this.imgs.length} image(s)`);
// Access image data: this.imgs[0].src
}
};
}
},
/**
* ADDING CUSTOM WIDGETS
* =====================
*
* Widgets are UI controls on nodes (text inputs, sliders, dropdowns, etc.)
* You can add custom widgets in beforeRegisterNodeDef:
*/
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "ExampleNode") {
// Add a custom button widget
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
onNodeCreated?.apply(this, arguments);
// Add a button widget
this.addWidget("button", "Click Me", null, () => {
console.log("Button clicked on node:", this.id);
alert("Custom button clicked!");
});
};
}
},
/**
* CUSTOM UI ELEMENTS
* ==================
*
* You can add custom UI elements to the ComfyUI interface.
* This is typically done in the setup() method.
*/
async setup() {
// Example: Add a custom button to the menu
const menu = document.querySelector(".comfy-menu");
if (menu) {
const button = document.createElement("button");
button.textContent = "Custom Action";
button.onclick = () => {
console.log("Custom menu button clicked");
};
menu.appendChild(button);
}
},
/**
* API COMMUNICATION
* =================
*
* Extensions can communicate with the server through the API.
* The app.api object provides methods for HTTP requests and WebSocket messaging.
*/
async setup() {
// Example: Send an API request to your custom endpoint
// (You would define this endpoint in your Python module)
try {
const response = await fetch("/api/example/endpoint", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ data: "example" }),
});
const result = await response.json();
console.log("API response:", result);
} catch (error) {
console.error("API request failed:", error);
}
// Example: Send a message through WebSocket
// This is useful for real-time communication
app.api.addEventListener("example.message", (event) => {
console.log("Received WebSocket message:", event.detail);
});
},
/**
* HOOKING INTO WORKFLOW EXECUTION
* ===============================
*
* You can monitor when workflows are queued, executing, or completed.
*/
async setup() {
// Listen for when prompts are executed
app.api.addEventListener("executed", (event) => {
const data = event.detail;
console.log("Node executed:", data.node);
console.log("Output:", data.output);
});
// Listen for execution progress
app.api.addEventListener("execution_start", (event) => {
console.log("Workflow execution started");
});
app.api.addEventListener("execution_cached", (event) => {
console.log("Some nodes were cached:", event.detail);
});
},
/**
* BEST PRACTICES
* ==============
*
* 1. Use unique, namespaced extension names
* 2. Always check if a method exists before calling it (use optional chaining)
* 3. Preserve existing functionality when overriding methods
* 4. Use console.log for debugging, but remove or disable in production
* 5. Handle errors gracefully
* 6. Document your code thoroughly
* 7. Test with different node types and workflows
*/
/**
* DEBUGGING TIPS
* ==============
*
* - Use browser DevTools (F12) to inspect console logs
* - Set breakpoints in your extension code
* - Use console.log() to track extension lifecycle
* - Inspect the `app` object in console to explore the API
* - Check Network tab for API requests
* - Use `node.serialize()` to see the full node state
*/
});
/**
* ADDITIONAL RESOURCES
* ====================
*
* - ComfyUI Documentation: https://docs.comfy.org
* - LiteGraph Documentation: https://github.com/jagenjo/litegraph.js
* - Example Extensions: Browse other custom nodes with frontend code
* - ComfyUI Source: https://github.com/comfyanonymous/ComfyUI
*
* COMMON USE CASES
* ================
*
* 1. Custom Node UI: Add special widgets or controls to your nodes
* 2. Image Processing: Display or manipulate images in the frontend
* 3. Real-time Preview: Show live previews during generation
* 4. Workflow Automation: Add shortcuts or automated actions
* 5. Custom Menus: Add new menu items or panels
* 6. Node Validation: Add client-side validation for node inputs
* 7. Analytics: Track usage or performance metrics
* 8. Integration: Connect to external services or APIs
*/