ComfyUIのカスタムノードは、INPUT_TYPES・RETURN_TYPES・FUNCTIONを持つPythonクラスをcustom_nodes配下に置き、NODE_CLASS_MAPPINGSで登録すれば動きます。最初は文字列を受け取って文字列を返す、副作用のない最小ノードから始めると、配置・再起動・型の仕組みを一度で確認できます。
情報確認日:2026年8月22日(日本時間)
結論:文字列→文字列の最小ノードで「登録が通る」ことを先に確かめる
責任範囲
- 既存nodeやSubgraphで足りるなら作らない、という判断基準を示す
ComfyUI/custom_nodes/<フォルダ>/__init__.pyに最小クラスを置き、再起動して読み込みを確認するINPUT_TYPESの書式とComfyUIのData Typeを一致させる- logging・例外・version・README・requirementsを最低限そろえる
- ComfyUIの基本操作はComfyUIの使い方、workflow JSONの構造はComfyUIのGUI用JSONとAPI用JSONの違い|変換できない原因を解説に任せる
- JavaScriptによるUI拡張とRegistryへの公開は別記事で扱う
Custom Nodeは本当に必要か
作らずに済むなら作らない方が、更新のたびに壊れる箇所が減ります。次の順に確認し、全部に当てはまらないときだけPythonを書きます。
| やりたいこと | 先に試す手段 | Custom Nodeが要る目安 |
|---|---|---|
| 複数nodeの定型の組み合わせを1つにまとめたい | Subgraph(選択したnodeを1つのnodeに束ねる機能) | 処理自体が標準nodeで表現できないとき |
| 文字列やseedを決まった規則で加工したい | 標準のprimitive node・文字列系node・既存のcustom node | 規則が自前のコードでしか書けないとき |
| 外部サービスやローカルのPythonライブラリを呼びたい | ComfyUIの外で前処理し、結果をファイルで渡す | workflowの途中で毎回呼ぶ必要があるとき |
| 画像tensorを独自に変換したい | 既存のimage処理node | 同じ計算をするnodeが見つからないとき |
Subgraphは、現行UI(2026年8月時点)でnodeを選択し、ツールバーのSubgraphアイコンから作成できます。frontend 1.27.7以降ではnode libraryに登録して、通常のnodeのように再利用できます。「まとめたいだけ」ならここで終わりです。標準nodeの役割と接続の読み方はComfyUIの使い方を参照してください。
最小Nodeの入出力をどう決めるか
最初の例は「文字列を受け取り、前後の空白を取り除いて、接頭辞を付けた文字列を返す」だけにします。画像やmodelを扱わないので、GPUもcheckpointも不要で、失敗の原因を登録まわりに絞れます。
| 向き | 名前 | ComfyUIの型 | widget設定 |
|---|---|---|---|
| 入力(required) | text |
STRING |
multiline: True、default: "" |
| 入力(required) | prefix |
STRING |
default: "[prefix] " |
| 出力 | text |
STRING |
— |
出力は下流のCLIPTextEncodeのtext入力にそのままつなげる想定です。最小例で型をSTRINGに限定しているのは、次の章で型の一致を確認しやすくするためでもあります。
Python処理をどう実装するか
最初の処理関数は副作用なしにします。ファイルを書かない、ネットワークを呼ばない、グローバル変数を変えない。入力だけから出力が決まる関数は、ComfyUIの外でも普通のPythonとして試せます。
# ComfyUI/custom_nodes/my_text_tools/__init__.py
import logging
log = logging.getLogger(__name__)
class PrefixText:
CATEGORY = "my_tools/text"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "run"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"multiline": True, "default": ""}),
"prefix": ("STRING", {"default": "[prefix] "}),
}
}
def run(self, text: str, prefix: str):
if not isinstance(text, str):
raise TypeError(f"text must be str, got {type(text).__name__}")
cleaned = text.strip()
if not cleaned:
log.warning("PrefixText: empty text received")
return (prefix + cleaned,)
NODE_CLASS_MAPPINGS = {
"PrefixText": PrefixText,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PrefixText": "Prefix Text (my_tools)",
}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
公式で決まっている約束は4つです。
INPUT_TYPESは@classmethodで、required・optional・hiddenのいずれかのキーを持つdictを返す- 各入力は
(型文字列, オプションdict)のtupleで書く RETURN_TYPESはtupleで、出力が1つでも末尾のカンマを省かないFUNCTIONに書いた名前のメソッドが、RETURN_TYPESと同じ長さのtupleを返す
return (prefix + cleaned,)のカンマを落として文字列をそのまま返すと、ComfyUIは文字列を「1文字ずつの出力」と解釈しようとして失敗します。最小例で最も多い間違いなので、先に覚えておいてください。
ComfyUIの外で先に動かす
登録する前に、同じフォルダで普通のPythonとして呼びます。
from __init__ import PrefixText
node = PrefixText()
print(node.run(" 夕方の海辺 ", "[prefix] ")) # ('[prefix] 夕方の海辺',)
print(node.run("", "[prefix] ")) # ('[prefix] ',) 警告ログが出る
ここで期待どおりなら、以降の問題はComfyUI側の登録・型・UIに限定できます。
ComfyUIへどう登録するか
登録に必要な作業は、配置・再起動・読み込み確認の3つだけです。
ComfyUI/custom_nodes/my_text_tools/を作り、__init__.pyを置く- ComfyUIを再起動する(custom nodeは起動時に走査される)
- 起動ログで
my_text_toolsが読み込まれたか、失敗として報告されていないかを見る - 現行UI(2026年8月時点)でnode検索に「Prefix Text」と入れ、
my_tools/textカテゴリに出るか確認する
公式ドキュメントによると、ComfyUIはcustom_nodes内の__init__.pyを持つフォルダを走査し、NODE_CLASS_MAPPINGSをexportしているものをnodeとして登録します。コードにエラーがあってもComfyUI自体は起動を続け、そのモジュールを読み込み失敗として報告します。ログの正確な文言はバージョンで変わるため、手元の起動ログで「自分のフォルダ名」を検索して確認してください。
読み込み失敗の典型はimport時の例外(構文エラー、未インストールのライブラリ、INPUT_TYPESのtypo)です。ログにはPythonのtracebackがそのまま出るので、行番号を見れば原因にたどり着けます。nodeが一覧に出ない場合も、まずログを見てからNODE_CLASS_MAPPINGSのキーと__all__を確認してください。登録したnodeは、保存したworkflow JSONの中ではclass_typeにこのキーで記録されます。JSONの見方はComfyUIのGUI用JSONとAPI用JSONの違い|変換できない原因を解説を参照してください。
入力型・出力型をどう一致させるか
ComfyUIでは、nodeの出力socketと次のnodeの入力socketの型文字列が一致しないと接続できません。自分で型名を発明せず、公式のData Typeを使います。
| 型文字列 | Pythonで受け取る値 | 主なwidget option |
|---|---|---|
STRING |
str |
default、multiline、placeholder |
INT / FLOAT |
int / float |
default、min、max、step |
BOOLEAN |
bool |
default、label_on、label_off |
COMBO |
選択肢のうち1つ(str) |
選択肢のlistを型の位置に渡す |
IMAGE |
torch.Tensor、形は[B, H, W, C] |
widgetなし(接続のみ) |
LATENT / MASK / MODEL / CLIP / VAE / CONDITIONING |
ComfyUI内部のオブジェクト | widgetなし(接続のみ) |
文字列以外を扱う段階になったら、次の2点を守ります。
INTやFLOATにはminとmaxを必ず付ける。上限のない数値入力は、UIからの誤入力でそのまま実行時エラーになるIMAGEは[B, H, W, C]のtensorで、batch次元を落とさない。1枚だけ返すときもunsqueeze(0)などでBを残す
入力値の事前チェックにはVALIDATE_INPUTSというclassmethodも用意されています。Trueを返せば通り、文字列を返せばそれがエラーメッセージになる仕組みです。最小例では処理関数内の例外で足りますが、定数入力の範囲チェックを実行前に済ませたいときに使います。
壊れにくく整えるには何を最低限そろえるか
動いたあと、次の5つをそろえておくと、ComfyUIの更新や他のPCへの移動で困りにくくなります。
| 項目 | 最低限の形 | ないと起きること |
|---|---|---|
| Logging | logging.getLogger(__name__)で警告・情報を出す。printは使わない |
起動ログに混ざって原因が追えない |
| Error | 想定外の入力は例外を投げ、メッセージに入力名と受け取った型を含める | 黙って空文字やNoneが下流に流れる |
| Version | pyproject.tomlの[project] versionをsemantic versioningで上げる |
どの版で壊れたか分からない |
| README | 何をするnodeか、入力・出力、動作確認したComfyUIのversion | 数か月後の自分が使い方を忘れる |
| Requirements | requirements.txtに追加ライブラリだけを書く(torch等ComfyUI同梱のものは書かない) |
別環境でimportに失敗する |
# ComfyUI/custom_nodes/my_text_tools/pyproject.toml
[project]
name = "my_text_tools"
version = "0.1.0"
description = "Prefix and trim text for prompt assembly"
license = { text = "MIT License" }
requires-python = ">=3.10"
[tool.comfy]
PublisherId = "your-publisher-id"
DisplayName = "My Text Tools"
pyproject.tomlの項目名は公式のRegistry仕様に合わせています。Registryへ公開するかどうかに関わらず、この形にしておけば後で公開に進むときの書き直しが不要です。公開手順そのものは別記事で扱います。
正常・異常入力をどう確認するか
最小nodeでも、正常系と異常系を分けて一度通しておくと、あとで型や処理を広げたときの基準になります。結果は環境で変わるため、ここでは記録表だけを示します。
- ComfyUIで「Prefix Text」nodeを置き、出力を
Preview Any系のnode、なければCLIPTextEncodeにつなぐ - 下の表の入力を1件ずつ
textに入れ、Queueで実行する - 出力文字列、ログ、UIに出たエラーをそれぞれ記録する
| No | 入力text |
期待する扱い | 実際の出力 | ログ・エラー |
|---|---|---|---|---|
| 1 | 夕方の海辺 |
接頭辞付きで返る | ||
| 2 | 前後に空白 |
空白が除かれる | ||
| 3 | (空文字) | 接頭辞だけ返り、警告ログが出る | ||
| 4 | 改行を含む複数行 | 改行は保持され、前後だけ除かれる | ||
| 5 | 絵文字・全角記号 | そのまま通る | ||
| 6 | 他nodeからINT出力を接続 |
型不一致で接続できない |
6番は「接続すらできない」のが正しい挙動です。接続できてしまう場合は、入力の型文字列がSTRINGか、型を"*"のように緩めていないかを確認します。1〜5で期待と違う行は、処理関数をComfyUIの外で同じ入力で呼び、ComfyUI側かPython側かを切り分けます。
よくある質問
nodeを直したら毎回ComfyUIを再起動する必要がありますか?
custom nodeは起動時に読み込まれるため、Pythonを変更したら再起動が基本です。処理関数だけを先にComfyUIの外で試す運用にすると、再起動の回数を減らせます。
ファイル名は__init__.pyだけで足りますか?
最小例なら足ります。公式のwalkthroughではsrc/nodes.pyに分けて__init__.pyからimportする構成も示されています。nodeが増えたら分割してください。
他の人のcustom nodeと名前が衝突したらどうなりますか?
NODE_CLASS_MAPPINGSのキーはComfyUI全体で一意である必要があります。PrefixTextのような短い名前は衝突しやすいので、実際にはMyTools_PrefixTextのように自分の接頭辞を付けることを勧めます。
まとめ
ComfyUIのカスタムノードは、INPUT_TYPES・RETURN_TYPES・FUNCTIONを持つクラスとNODE_CLASS_MAPPINGSがあれば動きます。作る前にSubgraphや既存nodeで足りないかを確かめ、作るなら文字列→文字列の副作用なしの最小nodeから始めてください。
登録は配置・再起動・ログ確認の3手順、型は公式のData Typeに合わせ、logging・例外・version・README・requirementsを最低限そろえれば、更新や移動で壊れにくいnodeになります。UI側の拡張と公開は別記事で続けます。