Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion .github/scripts/i18n/sync-docs-json.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export function normalizeNavTree(nodes, options = {}) {
const pages = normalizeNavTree(node.pages, options);
if (pages.length === 0 && pruneMissing) continue;
out.push({ ...node, pages });
continue;
}
// Groups that render an OpenAPI spec have no `pages` of their own.
if (node && typeof node === "object" && node.openapi != null) {
out.push(node);
}
}
return out;
Expand Down Expand Up @@ -162,6 +167,9 @@ export function localizeNavTree(nodes, langDir, langDirs) {
pages: localizeNavTree(node.pages, langDir, langDirs),
};
}
if (node && typeof node === "object" && node.openapi != null) {
return { ...node, openapi: localizeOpenApi(node.openapi, langDir) };
}
return node;
});
}
Expand Down Expand Up @@ -242,6 +250,27 @@ function findGroupMatch(existingPages, newChild, langDirs) {
return best;
}

/** @param {unknown} openapi */
function openApiSpecKey(openapi) {
if (typeof openapi === "string") return openapi;
if (openapi && typeof openapi === "object" && typeof openapi.source === "string") {
return openapi.source;
}
return null;
}

/** @param {unknown[]} existingPages @param {object} newChild */
function findOpenApiMatch(existingPages, newChild) {
if (!Array.isArray(existingPages)) return null;
const key = openApiSpecKey(newChild.openapi);
if (!key) return null;
for (const node of existingPages) {
if (typeof node === "string" || !node) continue;
if (openApiSpecKey(node.openapi) === key) return node;
}
return null;
}

/**
* @param {unknown[]} newPages
* @param {unknown[]} existingPages
Expand All @@ -251,7 +280,15 @@ export function mergeNavPages(newPages, existingPages, langDirs) {
if (!Array.isArray(newPages)) return [];
return newPages.map((newChild) => {
if (typeof newChild === "string") return newChild;
if (!newChild?.pages) return newChild;
if (!newChild?.pages) {
// OpenAPI groups have no pages to match on; key them by spec instead so a
// localized title survives the sync.
if (newChild?.openapi != null) {
const match = findOpenApiMatch(existingPages, newChild);
if (match?.group) return { ...newChild, group: match.group };
}
return newChild;
}

const match = findGroupMatch(existingPages, newChild, langDirs);
const merged = { ...newChild };
Expand Down
44 changes: 44 additions & 0 deletions custom-nodes/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: "Custom Nodes"
description: "What custom nodes are, where they come from, and how to install, manage, and build them."
sidebarTitle: "Overview"
---

Custom nodes are packages built by the community that add functionality ComfyUI does not ship with. They are ordinary ComfyUI nodes in every respect: they declare inputs and outputs, run on your machine, and connect to core nodes freely. The difference is that you install them yourself and a third-party author maintains them.

For how custom nodes compare to core and partner nodes, see the [nodes overview](/nodes).

## Where they come from

Most custom nodes are published to the [Comfy Registry](https://registry.comfy.org), a public catalog of versioned node packages. The registry is what powers search and installation inside ComfyUI Manager, and it is where you publish your own package when it is ready to share.

A package can contribute any combination of Python nodes that run on the server, frontend extensions that change the UI, workflow templates, and subgraph blueprints.

Check warning on line 15 in custom-nodes/index.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

custom-nodes/index.mdx#L15

Did you really mean 'subgraph'?

## Installing and managing

**ComfyUI Manager** is the recommended way to work with custom nodes. It searches the registry, installs packages along with their Python dependencies, updates or pins versions, disables packages without deleting them, and detects nodes missing from an imported workflow. Desktop builds include it; portable and manual installs may need it enabled first.

<Card title="Install Custom Nodes" icon="download" href="/installation/install_custom_node">
Installing via ComfyUI Manager, Git, or a ZIP download, plus dependency installation.
</Card>

<Card title="ComfyUI Manager" icon="puzzle-piece" href="/manager/overview">
Searching, updating, disabling, and troubleshooting node packages.
</Card>

Two things to keep in mind, since custom nodes execute arbitrary Python inside your ComfyUI process:

- **Trust matters.** Install packages you have reason to trust, and prefer ones that are actively maintained. The registry scans published nodes for malicious behavior such as arbitrary system calls, and marks the ones that pass with a verification flag.
- **Dependencies can conflict.** Two packages may want incompatible versions of the same Python library. If ComfyUI stops starting after an install, disable the newest package first. See [custom node issues](/troubleshooting/custom-node-issues).

## Building your own

If nothing in the registry does what you need, write it. A minimal node is a Python class with an input specification and a function, and you can extend from there into custom datatypes, lazy evaluation, node expansion, and frontend extensions.

Check warning on line 36 in custom-nodes/index.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

custom-nodes/index.mdx#L36

Did you really mean 'datatypes'?

<Card title="Develop Custom Nodes" icon="code" href="/custom-nodes/overview">
Walkthrough, backend and UI APIs, datatypes, and packaging.

Check warning on line 39 in custom-nodes/index.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

custom-nodes/index.mdx#L39

Did you really mean 'Walkthrough'?

Check warning on line 39 in custom-nodes/index.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

custom-nodes/index.mdx#L39

Did you really mean 'datatypes'?
</Card>

<Card title="Publish to the Registry" icon="upload" href="/registry/overview">
Versioning, `pyproject.toml` standards, CI/CD, and claiming an existing node.
</Card>
Loading
Loading