【Dify】複雑なフローをスッキリ整理!条件分岐を減らすJinja2テンプレート活用術
1. はじめに
Difyでワークフローを作り込んでいくと、気づけば画面が「条件分岐(If/Else)ノード」だらけの巨大迷路になっていませんか?
「会員ランクで挨拶を変えたい」「エラー時は文言を変えたい」といった些細な調整のためにノードを増やし、線をつなぐ作業をしていると、フロー図の視認性を下げるだけでなく、修正時のミスも誘発してしまいますよね…

分岐だらけのフローのイメージ
しかし、Dify標準機能の「Jinja2」を使えば、この複雑な分岐をたった1つのノードに集約することができるんです!

1つのノードに集約した同じフローのイメージ
そこで!この記事では、Difyの標準機能である「Jinja2テンプレート」を活用し、複雑になりがちな分岐処理をたった1つのノードに集約する方法を解説していきます!
📦 検証用DSLファイルの配布
この記事で解説する完成版のチャットフロー(DSLファイル)を配布します。
Difyにインポートするだけで、すぐに動作確認が可能です。記事を読みながら実際に動かしてみることをおすすめします!
2. Jinja2とは何か?
一言で言えば、「データを流し込んでテキストを作るための型(テンプレート)」です。
Python製の技術ですが、難しいことはありません。実は、やっていることを簡単に表すと以下のような「単純な足し算」になるんです!
仕組みのイメージ
例えば、こんにちは、{{ name }}さん というテンプレートがあるとします。
ここに name = "佐藤" というデータを渡すと、Jinja2が穴埋めを行い、こんにちは、佐藤さん というテキストを出力してくれるといった形です。
また、単なる穴埋めだけでなく、条件分岐や繰り返し処理などをテンプレートの中に直接書き込めるのもポイントです。
Difyでの使い所
Difyでの役割:フロー図をシンプルにする
最大のメリットは、「If/Elseノード(分岐)」を使わずに、1つのノード内で出力を動的に制御できる点になります。
具体的には、主に以下の2箇所で活躍します。
- Templateノード: 入力によって動的に変化する、ユーザーに見せるための文章を作る。
- LLMノード: 条件(If文)を使ってAIへの指示(プロンプト)そのものを動的に切り替える。
3. なぜJinja2を使うべきなのか(Before/After)
具体的なシナリオを想定して、その効果を比較してみましょう!
シナリオ:ユーザーごとに異なる「週次レポート」の作成
タスク管理アプリの運営者として、ユーザーに今週のアドバイス(週次レポート)を送ろうとしている状況を考えてみましょう!
より親身なアドバイスにするため、以下の3つの条件で条件を組み合わせて文面を作成することにしました。
-
会員ランク (
Premium/Free): 文体の丁寧さと、オファー内容を変える -
先週の目標達成 (
Achieved/Failed): 「お祝い」か「励まし」かを変える -
未読のお知らせ (
True/False): 文末の案内を変える
組み合わせは全部で 2 × 2 × 2 = 8 パターン です。
例文:プレミアムプラン × 先週目標達成 × 未読あり
◯◯さん
先週は目標達成おめでとうございます!素晴らしい進捗ですね。🎉
プレミアム会員様向けの「詳細分析レポート」を添付しました。
※重要なお知らせが届いています。受信箱をご確認ください。
Before:Jinja2なし(条件分岐ノード)
Dify標準の「If/Else」ノードだけで実装しようとすると、フロー図はこうなります。

図1. 全て「条件分岐ノード」を使用した例
- 視覚的デメリット: 分岐が重なり、全体像が把握できません。どこでどのメッセージを作っているのか、作った本人でも迷子になります。
- 運用の課題: もし「全てのパターンの文頭に『今週も頑張りましょう!』と入れたい」と思ったら? 8つのノード全てを一つずつ開いてコピペして回る必要があります。
After:Jinja2あり(テンプレートノード)
Jinja2を使えば、フロー図は劇的に変わります。

図2. 「テンプレートノード」を使用した例
- 視覚的メリット: フローは美しい「一本道」のままです。
- 運用のメリット: 「条件による出し分け」はノード内部のテキスト処理で完結しています。以下のように記述するだけで、8パターンの出し分けが完了します。
実際のコード(テンプレートノード)
{# --- 0. ユーザー名による挨拶(defaultフィルタを使用) --- #}
こんにちは、{{ user_name | default('ゲスト', true) }}さん。
{# --- 1. 目標達成状況による挨拶 --- #}
{% if goal_status == 'Achieved' %}
先週は目標達成おめでとうございます!素晴らしい進捗ですね。🎉
{% elif goal_status == 'Failed' %}
先週は惜しくも目標まであと少しでしたね。今週こそ達成しましょう!💪
{% else %}
【エラー】「先週の目標達成(goal_status)」が入力されていません。
{% endif %}
{# --- 2. 会員ランクによるアドバイス --- #}
{% if user_plan == 'Premium' %}
プレミアム会員様向けの「詳細分析レポート」を添付しました。
{% elif user_plan == 'Free' %}
プレミアム会員になると、あなたの傾向をAIが分析できます。ぜひご検討ください。
{% else %}
【エラー】「会員ランク(user_plan)」が入力されていません。
{% endif %}
{# --- 3. 未読通知による締めくくり --- #}
{# 注意: True / False を明示的に分ける必要があります #}
{% if has_unread_messages == yes %}
※重要なお知らせが届いています。受信箱をご確認ください。
{% elif has_unread_messages == no %}
今週も良い1週間になりますように!
{% else %}
【エラー】「未読のお知らせ(has_unread_messages)」が入力されていません。
{% endif %}
これなら、共通の修正が入っても一箇所書き換えるだけで全パターンに即座に反映されます。
4. Difyで即戦力になるJinja2の必須構文3選
DifyでJinja2を使うために必要なJinja2の構文のうち、代表的なものは3つです。
プログラミングの知識がなくても、「条件で分ける」「繰り返す」「加工する」という3つのパターンさえ覚えればすぐに活用できます!
① 条件分岐 {% if %}
「もし〜ならA、そうでなければBを表示する」というように、状況に合わせてAIへの指示やメッセージを切り替えたい時に使います。
基本的な構成要素は以下の4つです。
-
{% if 条件 %}: 「もし〜なら」(開始・必須) -
{% elif 条件 %}: 「そうではなく、もし〜なら」(追加条件・任意) -
{% else %}: 「どれにも当てはまらないなら」(その他・任意) -
{% endif %}: 「ここまで」(終了・必須)
これらを組み合わせてロジックを作り、最後に必ず {% endif %} で閉じるのがルールです。
Pythonユーザー向けの補足
Pythonの if 文とほぼ同じです。
大きな違いは、最後に必ず {% endif %} で閉じる必要がある点です。
Python:
if x > 10:
print("OK")
else:
print("NG")
Jinja2:
{% if x > 10 %}
OK
{% else %}
NG
{% endif %}
Difyでの使い所:
フラグによるメッセージの出し分けなどに使います。
{# ユーザー区分によるメッセージ分岐 #}
{% if user_type == 'premium' %}
プレミアム機能が有効です。
{% elif user_type == 'free' %}
アップグレードをご検討ください。
{% else %}
アカウントが無効です。
{% endif %}
→ユーザータイプがpremiumなら「プレミアム機能が有効です。」と出力
→ユーザータイプがfree なら「アップグレードをご検討ください。」と出力
→どちらでもなければ「アカウントが無効です。」と出力
② 反復処理 {% for %}
検索結果や商品リストなど、複数のデータが入っているリストを順番に取り出して表示したい時に使います。
「リストにあるデータの数だけ繰り返す」という処理です。
基本構文は {% for 一時変数 in リストデータ %} で始まり、{% endfor %} で終わります。
構文の構成要素
ここを理解すると、自分で自由にループが書けるようになります。
-
{% for: 「ループ開始」の合図(必須) -
一時変数: 自分で好きな名前をつけられます(例: item, doc, user)。リストから取り出した「1つ分のデータ」がここに入ります。 -
in: 「〜の中から取り出す」という意味のつなぎ言葉(必須) -
リストデータ %}: Difyから渡される実際のデータ(変数名)を指定します。 -
{% endfor %}: 「ループ終了」の合図(必須)
つまり、「リストデータ から順番に中身を取り出して、一時変数 という名前で扱いますよ」という宣言です。
Pythonユーザー向けの補足
こちらもPythonと似ていますが、最後に {% endfor %} が必要です。
Python:
for item in items:
print(item)
Jinja2:
{% for item in items %}
{{ item }}
{% endfor %}
Difyでの使い所:
検索結果やJSONリストを、Markdownの箇条書きやテーブルに整形する際に必須です。
具体的な処理イメージ:
もし search_results に以下のデータが入っていた場合:
入力データ:
search_results: [
{"title": "Difyの使い方", "content": "DifyはLLMアプリ開発プラットフォームです..."},
{"title": "Jinja2入門", "content": "テンプレートエンジンJinja2の基礎..."}
]
Jinja2:for構文使用例
{% for doc in search_results %}
- タイトル: {{ doc.title }}
内容: {{ doc.content }}
{% endfor %}
出力結果:
- タイトル: Difyの使い方
内容: DifyはLLMアプリ開発プラットフォームです...
- タイトル: Jinja2入門
内容: テンプレートエンジンJinja2の基礎...
③ フィルタ |
変数の後ろに | (パイプ)とコマンドを書くことで、データをちょっと手直し(加工)することができます!
よく使う便利な加工コマンドを3つ紹介します👇
1. trim(空白削除)
テキストの先頭と末尾にある余分なスペースや改行を自動でカットしてくれます。
Pythonユーザー向けの補足
trim: Pythonの strip()
-
Python:
text.strip() -
Jinja2:
text | trim
書き方:
{{ user_name | trim }}
→ " 鈴木 太郎 " という入力が "鈴木 太郎" になります。(文字の間のスペースは保持されます)
2. default(初期値設定)
変数が空っぽ(None)だった場合に使う値を決めます。
Pythonユーザー向けの補足
default: Pythonの三項演算子に近いイメージ
-
Python:
value if value else 'default' -
Jinja2:
value | default('default')
書き方:
{{ user_name | default('ゲスト') }}
→ 変数に入力があればそれを表示し、空であれば「ゲスト」と表示します。
3. join(結合)
リストの中身を指定した文字で繋げて文字列にします。
Pythonユーザー向けの補足
join: Pythonの join()
-
Python:
", ".join(items) -
Jinja2:
items | join(', ')
書き方:
{{ user_names | join(', ') }}
→ ['鈴木太郎', '鈴木次郎'] というリストが "鈴木太郎, 鈴木次郎" という文字列になります。
5. 実践テクニック①:Templateノード(ユーザー出力用)
ここまででjinja2の基礎を押さえられたので、ここからは具体的な応用方法のレシピを紹介していきます!
3章で紹介した「会員アプリのプッシュ通知」のシナリオをそのまま使い、さらに一歩進んだ活用法を見ていきましょう!
ここでは、会員ランクや利用状況に基づいて抽出された「おすすめアクションリスト(recommended_actions)」というデータがあるとします。
おすすめアクションリスト例(`recommended_actions`)
"recommended_actions": [
{
"title": "プロフィール入力を完了する",
"points": 100
},
{
"title": "友人を招待する",
"points": 500
},
{
"title": "デイリーミッション",
"points": 10
}
]
この同じデータを、Jinja2を使って「HTMLメール」と「チャット用の表」に整形し分けてみます。
【レシピA:HTML編】リッチなHTMLメールを作る
シナリオ:
アプリの利用頻度が低いユーザーに対して、再訪を促すメールを送りたい時。
文字だけのメールよりも、ボタンや色を使ったHTMLメールの方がクリック率は高まりますよね。
アプリ:

図3. HTMLを使ったフロー図
テクニック:user_plan(会員ランク)によってヘッダーの色を変えつつ、リストをループ処理でカード風に表示します!
コード例
<div style="background-color: #f6f6f6; padding: 40px 0; font-family: 'Helvetica Neue', Arial, sans-serif; color: #333333;">
<div style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
<div style="background-color: {% if user_plan == 'Premium' %}#B8860B{% else %}#5a6c7d{% endif %}; padding: 30px 20px; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 24px; font-weight: bold; letter-spacing: 1px;">
{% if user_plan == 'Premium' %}🌟 PREMIUM UPDATE{% else %}📢 WEEKLY UPDATE{% endif %}
</h1>
<p style="color: rgba(255,255,255,0.9); margin: 10px 0 0 0; font-size: 14px;">
あなただけのお知らせをお届けします
</p>
</div>
<div style="padding: 30px;">
<p style="font-size: 16px; line-height: 1.6; margin-bottom: 25px; color: #555;">
最近アプリのご利用がありませんが、いかがお過ごしですか?<br>
今のあなたにぴったりな、<b style="color: #333;">おすすめのアクション</b>をピックアップしました。
</p>
<div style="margin-bottom: 30px;">
{% for action in recommended_actions %}
<div style="border: 1px solid #eeeeee; border-radius: 8px; padding: 15px; margin-bottom: 12px; background-color: #fcfcfc;">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="font-weight: bold; font-size: 16px; color: #333;">
{{ action.title }}
</td>
<td width="90" style="text-align: right;">
<span style="display: inline-block; background-color: #e3f2fd; color: #1976d2; font-size: 12px; font-weight: bold; padding: 6px 12px; border-radius: 20px; white-space: nowrap;">
+{{ action.points }} pt
</span>
</td>
</tr>
</table>
</div>
{% endfor %}
</div>
<div style="text-align: center; margin-top: 40px; margin-bottom: 10px;">
<a href="https://example.com/login" style="display: inline-block; background-color: {% if user_plan == 'Premium' %}#B8860B{% else %}#007bff{% endif %}; color: #ffffff; text-decoration: none; padding: 14px 30px; border-radius: 6px; font-weight: bold; font-size: 16px;">
アプリを開いてポイント獲得
</a>
</div>
</div>
<div style="background-color: #f1f1f1; padding: 15px; text-align: center; font-size: 12px; color: #999;">
© 2025 Your App Service. All rights reserved.
</div>
このフローを実際に実行すると以下の出力イメージのようになります。
出力イメージ:

図4. HTMLを使ったフロー図の出力結果
効果:
- 「プレミアム会員ならゴールド、無料会員ならグレー」といったデザインの出し分けと、動的なリスト生成を同時に実現できます。
【レシピB:Markdown編】チャット画面で見やすい「表」を作る
シナリオ:
同じデータを、今度はDifyのチャットボット上で「今週のタスク一覧」として回答させたいとしましょう。
チャット画面では、HTMLメールのような装飾よりも、Markdownによる「表組み」の方が見やすいですよね。
アプリ:

図5. Markdownを使ったフロー図
テクニック:
Markdownの表構文の中に変数を流し込みます。獲得ポイントが高ければ優先順位を高く表示するようにしてみます!
コード例
### 📋 今週のおすすめアクション
| アクション名 | 獲得ポイント | 優先度 |
| :--- | :---: | :---: |{% for action in recommended_actions %}
| **{{ action.title }}** | {{ action.points }} pt | {% if action.points > 50 %}🔥 高{% else %}中{% endif %} |{%- endfor %}
※完了するとポイントが即時反映されます。
このフローを実際に実行すると以下の出力イメージのようになります。
出力イメージ:

図6. Markdownを使ったフロー図の出力結果
効果:
- 表形式にすることによって情報を構造化して見せることが容易になります。
Tips: 「空白制御」が必要になる時
Jinja2を使っていると、出力結果に意図しない「空行」や「スペース」が入ってしまうことがあります。
今回のデモのようにHTMLやチャット文を作る場合は問題になりませんが、CSVデータや厳密なMarkdownの表を作る場合は、この「空白」がエラーの原因になってしまいます。
そんな時に使うのが {%- や -%} (ハイフン付きのタグ)です。
例:CSVデータ(カンマ区切り)を作りたい時
× ハイフンなし(NG)
{% for item in items %}
{{ item }},
{% endfor %}
これだと、ループのたびに改行されてしまい、1行のCSVになりません。
〇 ハイフンあり(OK)
{%- for item in items -%}
{{ item }},
{%- endfor -%}
タグの内側にハイフンをつけることで、タグ自体の前後の改行や空白が削除され、データが横にきれいに繋がります。
「データ生成」にJinja2を使う際は、このテクニックを思い出してください。
以上のように、同じデータソースを使っていても、Jinja2の書き方を変えるだけで、メール用(HTML)とチャット用(Markdown)の両方に対応できます!
6. 実践テクニック②:LLMノード(AI指示用)
Jinja2は、Templateノードだけでなく、LLMノードのプロンプトエンジニアリングにも活用できます。
最大のメリットは、共通の「役割定義(System Prompt)」を維持しつつ、状況に応じて「指示(Instruction)」だけを切り替えられる点です。これにより、プロンプトの二重管理を防ぐことができます。
【レシピC:LLM編】データの有無で指示を切り替える
シナリオ:
3章・5章と同じ「タスク管理アプリ」のシナリオです。
3章で作成した週次レポートを元にAIパートナーから、ユーザーへ「今週のアドバイス」を生成してもらいたいと思います!しかし、新規ユーザーの場合は週次レポートがないので、一番初めのアクションについてアドバイスをしたいとします。
- データあり: すでに生成された週次レポートがチャット内にある場合、その内容に基づいて具体的なタスク実行のアドバイスを行う。
- データなし: まだレポートがない(新規ユーザー)場合、分析ではなく「最初の使い方」を優しく教える。
これを「If/Elseノード」で分岐させると、LLMノードが2つになり管理が煩雑になります。Jinja2を使えば、1つのプロンプト内で完結します。
使用方法:
- LLMノードを作成
- プロンプト入力欄右上にある「jinja」をオンにする
アプリ:

図7. LLMを使ったフロー図
フロー解説:変数代入ノード
週次レポートの内容を会話変数に代入して、次以降のチャットでもLLMが参照できるようにします!
テクニック:{% if %} でデータの有無を判定し、指示ブロックそのものを出し分けます。
プロンプト例
# 役割定義(共通部分)
あなたは {{ user_name | default('ゲスト', true) }} さんのタスク管理アプリの専属AIパートナーです。
常に論理的かつ親身な口調で、{{ user_name | default('ゲスト', true) }} さんの生産性向上をサポートしてください。
# 具体的な指示(ここだけ切り替える)
{% if weekly_report %}
### 指示A:アドバイスモード
以下の「週次レポート」の内容を参考に、{{ user_name | default('ゲスト', true) }} さんへのフィードバックを行ってください。
レポート内で指摘されている課題を踏まえ、来週より効率的にタスクを実行するための具体的な改善策を1つ提案してください。
[週次レポート]
{{ weekly_report }}
{% else %}
### 指示B:チュートリアルモード
{{ user_name | default('ゲスト', true) }} さんはまだタスク記録がない「新規ユーザー」のようです。
まずは「頭の中のタスクを全て書き出すこと」の心理的効果(GTDなど)を短く解説し、
今日やるべきことを1つ登録するよう促してください。
※「ログが見つかりません」などのシステム的な発言は不要です。
{% endif %}
出力イメージ:
パターン①:週次レポートが前にあった場合👇

図8. LLMを使ったフロー図の出力結果①
パターン②:週次レポートがなかった場合👇

図9. LLMを使ったフロー図の出力結果②
効果:
- 管理の効率化: 共通部分(役割定義)の修正が一度で済み、メンテナンス性が向上します。
- ハルシネーション防止: データがない場合に空の変数を読み込ませず、適切な指示へ誘導することで回答の質が安定します。
このように、LLMへの入力前にJinja2で情報を整理することで、フロー図を複雑にせずAIの挙動を制御できるようになります!
7. 判断基準:フローチャート
ここまでJinja2の魅力をお伝えしてきましたが、すべての分岐や処理をJinja2ですることはできません。
「If/Elseノード」「Codeノード」「Templateノード(Jinja2)」。これらを適材適所で使い分けることが、美しいフローを作る鍵です。
各ツールの使い分け基準
| ツール名 | 役割 | 主な使い所 |
|---|---|---|
| If/Elseノード | 道を分ける | 「Aの場合はメールを送る」「Bの場合はDBに保存する」のように、実行するアクション(ツール)自体が異なる時。 |
| Codeノード | データを加工する | JSON操作、計算、正規表現など、高度なデータ処理や計算が必要な時。 |
| Templateノード(Jinja2) | テキストを整える | HTML生成やリストの整形、言葉尻の調整など、最終的な出力テキストの見た目を整える時。 |

図10. 各ツールの使い分けの基準
8. まとめ
Jinja2は単なる「テキスト置換ツール」ではなく、Difyアプリの構造をシンプルに保つための重要な機能です。
条件分岐ノードを減らし、一本道のフロー図を維持することは、開発中の迷いを減らすだけでなく、将来のメンテナンスを容易にします。
まずは「条件による挨拶の出し分け」のような小さな部分から、Jinja2テンプレートノードを試してみてください。
その一つのノードが、あなたのワークフローを「巨大迷路」から救う第一歩になるはずです。
DifyにそのままインポートできるDSLファイルのダウンロードリンクを再掲👇

Discussion