AI活用

ComfyUIカスタムノードの作り方|Pythonで最小ノードを実装する

ComfyUIのカスタムノードは、INPUT_TYPES・RETURN_TYPES・FUNCTIONを持つPythonクラスをcustom_nodes配下に置き、NODE_CLASS_MAPPINGSで登録すれば動きます。文字列を加工する最小ノードを例に、作るべきかの判断、実装、登録、型、壊れにくくする整備まで解説します。

この記事の目次
  1. 結論:文字列→文字列の最小ノードで「登録が通る」ことを先に確かめる
  2. Custom Nodeは本当に必要か
  3. 最小Nodeの入出力をどう決めるか
  4. Python処理をどう実装するか
  5. ComfyUIの外で先に動かす
  6. ComfyUIへどう登録するか
  7. 入力型・出力型をどう一致させるか
  8. 壊れにくく整えるには何を最低限そろえるか
  9. 正常・異常入力をどう確認するか
  10. よくある質問
  11. nodeを直したら毎回ComfyUIを再起動する必要がありますか?
  12. ファイル名は__init__.pyだけで足りますか?
  13. 他の人のcustom nodeと名前が衝突したらどうなりますか?
  14. まとめ

ComfyUIのカスタムノードは、INPUT_TYPESRETURN_TYPESFUNCTIONを持つ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: Truedefault: ""
入力(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で、requiredoptionalhiddenのいずれかのキーを持つ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つだけです。

  1. ComfyUI/custom_nodes/my_text_tools/を作り、__init__.pyを置く
  2. ComfyUIを再起動する(custom nodeは起動時に走査される)
  3. 起動ログでmy_text_toolsが読み込まれたか、失敗として報告されていないかを見る
  4. 現行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 defaultmultilineplaceholder
INT / FLOAT int / float defaultminmaxstep
BOOLEAN bool defaultlabel_onlabel_off
COMBO 選択肢のうち1つ(str 選択肢のlistを型の位置に渡す
IMAGE torch.Tensor、形は[B, H, W, C] widgetなし(接続のみ)
LATENT / MASK / MODEL / CLIP / VAE / CONDITIONING ComfyUI内部のオブジェクト widgetなし(接続のみ)

文字列以外を扱う段階になったら、次の2点を守ります。

  • INTFLOATにはminmaxを必ず付ける。上限のない数値入力は、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でも、正常系と異常系を分けて一度通しておくと、あとで型や処理を広げたときの基準になります。結果は環境で変わるため、ここでは記録表だけを示します。

  1. ComfyUIで「Prefix Text」nodeを置き、出力をPreview Any系のnode、なければCLIPTextEncodeにつなぐ
  2. 下の表の入力を1件ずつtextに入れ、Queueで実行する
  3. 出力文字列、ログ、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_TYPESRETURN_TYPESFUNCTIONを持つクラスとNODE_CLASS_MAPPINGSがあれば動きます。作る前にSubgraphや既存nodeで足りないかを確かめ、作るなら文字列→文字列の副作用なしの最小nodeから始めてください。

登録は配置・再起動・ログ確認の3手順、型は公式のData Typeに合わせ、logging・例外・version・README・requirementsを最低限そろえれば、更新や移動で壊れにくいnodeになります。UI側の拡張と公開は別記事で続けます。

スポンサーリンク