n8n Workflow Builder
Design and author production-quality n8n workflows by emitting valid workflow
JSON the user can import directly (Editor → Workflow menu → Import from File /
Import from URL / paste).
References: WORKFLOW_JSON.md | NODE_CATALOG.md | EXPRESSIONS.md | DATA_STRUCTURE.md | AI_WORKFLOWS.md | PATTERNS.md
Lessons: LESSONS_LEARNED.md — read before non-trivial work.
Authoritative docs: https://docs.n8n.io/workflows/
Before you start
Identify the trigger. Every n8n workflow needs at least one trigger
node. Ask what kicks off the workflow if it isn't obvious. Common choices:
| Trigger |
When to use |
| Manual Trigger |
Dev/testing only — user clicks Execute Workflow |
| Schedule Trigger |
Cron-style time-based runs |
| Webhook |
External HTTP callers (synchronous response option available) |
| n8n Form Trigger |
User-facing form submission |
| Chat Trigger |
AI chat workflows (LangChain bundle) |
| App triggers (Slack, GitHub, Gmail, etc.) |
Event push from a SaaS |
| Email Trigger (IMAP) |
Polling a mailbox |
| Error Trigger |
A dedicated workflow that fires when ANOTHER workflow errors |
| Execute Sub-workflow Trigger |
Entry point for a workflow called by another |
Identify the destination(s). Where does the data end up — Slack, DB,
another workflow, a webhook response, nowhere (just side effects)?
Identify the data shape. n8n moves an array of items between nodes.
Each item is { json: {...}, binary?: {...}, pairedItem?: {...} }. Read
DATA_STRUCTURE.md before authoring any
non-trivial workflow.
Identify edition constraints. Some features are Enterprise/Cloud only:
sub-workflows in some plans, RBAC, External Secrets, Source Control, Log
Streaming. Don't propose Enterprise features without confirming the user's
edition.
Workflow JSON minimum shape
{
"name": "My Workflow",
"nodes": [
{
"parameters": {},
"id": "11111111-1111-4111-8111-111111111111",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [240, 300]
},
{
"parameters": {
"values": {
"string": [{ "name": "greeting", "value": "hello" }]
}
},
"id": "22222222-2222-4222-8222-222222222222",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [460, 300]
}
],
"connections": {
"Manual Trigger": {
"main": [
[{ "node": "Set", "type": "main", "index": 0 }]
]
}
},
"active": false,
"settings": { "executionOrder": "v1" },
"pinData": {}
}
Critical rules — get any of these wrong and import breaks:
- Each node needs a UUID
id. Use proper UUID v4. Never reuse IDs.
- Each node needs a unique
name. Connections key on names. Renaming a
node requires rewriting every connection entry that references the old name.
type is <package>.<nodeName> — almost always n8n-nodes-base.<name>
for built-in nodes, or @n8n/n8n-nodes-langchain.<name> for AI cluster
nodes. Look up the exact id at
https://docs.n8n.io/integrations/builtin/node-types/.
typeVersion matters. Older typeVersions accept different parameter
shapes. When in doubt, use the highest documented version for that node.
connections.<sourceName>.main[outputIndex] is an array of arrays — the
inner arrays group connections going to the same output port (allowing
fan-out to multiple downstream nodes).
position is [x, y] pixel coords on the editor canvas. Spacing of
~220 px horizontally and ~160 px vertically reads well.
settings.executionOrder: "v1" is the modern execution order. Always
set this; the legacy mode is deprecated.
See WORKFLOW_JSON.md for the complete schema
including pinData, staticData, versionId, credentials references, and
sub-workflow meta.templateCredsSetupCompleted.
Connection wiring patterns
Linear (most common)
"connections": {
"Trigger": { "main": [[{ "node": "Step 1", "type": "main", "index": 0 }]] },
"Step 1": { "main": [[{ "node": "Step 2", "type": "main", "index": 0 }]] },
"Step 2": { "main": [[{ "node": "Step 3", "type": "main", "index": 0 }]] }
}
Fan-out (one source, two parallel branches)
"connections": {
"Trigger": {
"main": [[
{ "node": "Branch A", "type": "main", "index": 0 },
{ "node": "Branch B", "type": "main", "index": 0 }
]]
}
}
Two outputs (IF node — true on [0], false on [1])
"connections": {
"IF": {
"main": [
[{ "node": "True Branch", "type": "main", "index": 0 }],
[{ "node": "False Branch", "type": "main", "index": 0 }]
]
}
}
AI cluster wiring (Agent node uses non-main ports)
"connections": {
"OpenAI Chat Model": {
"ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]
},
"Window Buffer Memory": {
"ai_memory": [[{ "node": "AI Agent", "type": "ai_memory", "index": 0 }]]
},
"HTTP Request Tool": {
"ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]]
}
}
AI cluster connection types: ai_languageModel, ai_memory, ai_tool,
ai_outputParser, ai_embedding, ai_vectorStore, ai_document,
ai_textSplitter. See AI_WORKFLOWS.md.
Expressions — the ={{ }} template syntax
n8n parameters accept two modes per field:
- Fixed value — a literal string/number/boolean.
- Expression — starts with
= and contains {{ ... }} JS-evaluated
templates referencing built-in variables.
Common built-ins inside {{ }}:
| Variable |
What it is |
$json |
The current item's .json payload |
$binary |
The current item's .binary payload |
$input.item |
Wraps the current item (use to access .pairedItem) |
$input.all() |
Array of all incoming items |
$input.first() / $input.last() |
First/last incoming item |
$('Node Name').item |
Single item from a previously-executed node |
$('Node Name').all() |
All items output by that node |
$('Node Name').first() / .last() |
Self-explanatory |
$node["Node Name"].json |
Legacy alternative to $('Node Name').item.json |
$workflow |
Workflow metadata (id, name, active) |
$execution |
Execution metadata (id, mode, resumeUrl) |
$env |
Hosting env vars (only if N8N_BLOCK_ENV_ACCESS_IN_NODE=false) |
$now / $today |
Luxon DateTime helpers |
$jmespath() |
JMESPath query helper |
$secrets.<vault>.<key> |
External Secrets (Enterprise) |
Example: pull a field from a prior node into a Slack message:
=Hi {{ $('Webhook').item.json.body.username }}, new order #{{ $json.orderId }}
See EXPRESSIONS.md for the full reference
including string/array/object/date helpers, the $() accessor's rules around
nodes that haven't executed yet, and gotchas with multi-item references.
The flow-logic node toolbox
| Node |
Use it for |
IF (n8n-nodes-base.if) |
Two-way branch on a single condition |
Switch (n8n-nodes-base.switch) |
N-way branch on rules or on a value matching cases |
Filter (n8n-nodes-base.filter) |
Drop items that don't match a rule (no second branch) |
Merge (n8n-nodes-base.merge) |
Combine inputs (Append, Combine by Key, Combine by Position, SQL Query) |
Loop Over Items / Split In Batches (n8n-nodes-base.splitInBatches) |
Iterate the array of items in chunks; has a "done" output |
Wait (n8n-nodes-base.wait) |
Pause N seconds, resume at time, or resume on Webhook |
Stop and Error (n8n-nodes-base.stopAndError) |
Throw to halt the workflow with a custom message |
No Operation (n8n-nodes-base.noop) |
Join branches visually without changing data |
Execute Sub-workflow (n8n-nodes-base.executeWorkflow) |
Call another workflow (returns items) |
| Execute Sub-workflow Trigger |
Entry point in the called workflow |
See PATTERNS.md for proven recipes:
- Loop with rate limit — Loop Over Items + Wait
- Aggregate then summarize — Aggregate + Summarize
- Error workflow handoff — Error Trigger → Slack/PagerDuty
- Webhook → process → respond synchronously — Webhook (Respond: "Using
Respond to Webhook Node") → ... → Respond to Webhook
- Long-running webhook — Webhook (Respond: Immediately) → background work
- Idempotency — Remove Duplicates + Data Tables
- Pagination — HTTP Request with built-in Pagination options OR Loop with
cursor stored in
getWorkflowStaticData('global')
AI workflows (cluster nodes)
The LangChain bundle (@n8n/n8n-nodes-langchain.*) adds root nodes
(Agent, Chains, Vector Store roots, Information Extractor, Text Classifier,
Sentiment Analysis) plus sub-nodes (Chat Models, Memory, Tools, Output
Parsers, Embeddings, Text Splitters, Document Loaders, Retrievers, Rerankers).
Sub-nodes attach to root nodes via non-main connection types listed
above. They don't process items in the normal data-flow sense — they
configure the root node.
Common stacks:
- Chat Agent: Chat Trigger → AI Agent + OpenAI Chat Model + Window Buffer
Memory + (one or more Tools)
- RAG QA: Question and Answer Chain + OpenAI Chat Model + Vector Store
Retriever + Vector Store + Embeddings + Document Loader + Text Splitter
- Information Extractor: Source → Information Extractor + Chat Model +
Structured Output Parser → downstream
See AI_WORKFLOWS.md for full topologies, the
fromAI() function for tool parameters, evaluations, and human-in-the-loop
patterns.
Procedure (every workflow-build request)
- Clarify trigger + destination + data shape (one short paragraph back to
the user) if not obvious from the request.
- Read existing JSON if any. Don't blindly overwrite. Preserve unrelated
nodes, keep node IDs, keep credential references.
- Plan node graph on paper first. List nodes, their types, and the
connection topology. Catch missing merges or unwired branches NOW, not
after generating JSON.
- Generate the JSON. Use the patterns above. Use real UUIDs (
uuidgen,
crypto.randomUUID(), or any UUID v4 generator).
- Hand the user copy/paste-ready JSON. Tell them: "Import this via
Workflow menu → Import from File (or paste into a new tab via Ctrl/Cmd-A,
Ctrl/Cmd-V on the canvas)."
- Call out credentials they need to attach. Workflow JSON includes a
credentials reference per node by name+id, but the actual credential
values live in the n8n instance. The user must select credentials for any
node that needs them after import.
- Mention activation gotchas: for Webhook/Schedule/Form/App triggers,
workflows only fire on the Production URL / production schedule after
the user toggles "Inactive → Active" in the top bar.
- Update
LESSONS_LEARNED.md if you discovered anything per the agent's
continuous-learning rules.
Best practices
Do:
- Set
settings.executionOrder to "v1" always.
- Name nodes descriptively. The name is what shows up in expressions
(
$('My Descriptive Name').item.json...).
- Add Sticky Notes (
n8n-nodes-base.stickyNote) to document non-obvious
sections. They cost nothing at runtime and survive export/import.
- Use Edit Fields (Set) liberally to shape data into the schema the next
node expects. Better than complex expressions everywhere.
- Use Sub-workflows for any logic shared by 2+ workflows or for blocks
that exceed ~15 nodes.
- Add an Error Workflow at the instance level for production work — set
it as
errorWorkflow in the workflow settings, and build a separate
workflow whose trigger is the Error Trigger node.
- Use
Loop Over Items (Split In Batches) when calling rate-limited APIs;
pair it with the Wait node.
- Use
Remove Duplicates keyed on a stable ID for idempotent processing of
upstream feeds.
- For long-running webhooks, set Webhook → Respond: "Immediately" and do work
in the background.
Don't:
- Don't fan out into 5+ parallel branches that all hit the same API — you'll
rate-limit yourself. Sequence with Loop Over Items + Wait instead.
- Don't use Manual Trigger in production workflows. It won't fire on
schedule/webhook/event.
- Don't put secrets in node parameters as plain text — use Credentials, or
for Enterprise, External Secrets.
- Don't depend on item order across branches that merged — order is
preserved within a branch but not across merges (use Merge → Combine by
Key when order matters).
- Don't forget to handle the "no items" case in Loop Over Items — if the
input is empty, the loop never runs and downstream nodes may not execute.
Common mistakes (read PATTERNS.md for full list)
| Symptom |
Likely cause |
Fix |
| "Cannot read properties of undefined (reading 'json')" |
Referencing a node that hasn't run on this branch |
Use $('Node').first()?.json.field ?? 'fallback' |
| Workflow runs once then nothing |
Manual Trigger only fires on Execute Workflow |
Replace with Schedule/Webhook/App trigger |
| Webhook returns empty body to caller |
Default Webhook responds with "When last node finishes" but workflow doesn't return data |
Use Respond to Webhook node and set Webhook → Respond: "Using Respond to Webhook Node" |
| IF/Switch sends nothing downstream |
Confused which output port is true vs false |
IF: index 0 = true, 1 = false. Switch: order matches rule order |
$json in Code node returns wrong shape |
Code node runs ONCE for all items by default |
Set "Mode: Run Once for Each Item" or iterate $input.all() |
| Pairing breaks after a Code node |
Code node didn't set pairedItem on returned items |
See n8n-code-node for the canonical pattern |
| Imported workflow can't run |
Credentials weren't selected post-import |
After import, open each node with a yellow warning triangle and pick credentials |
Continuous learning
If during this session you discovered:
- A node-specific quirk that wasn't documented
- A wiring pattern that solved a non-obvious problem
- A version-specific behavior change
- A workaround for an n8n bug
… append a dated entry to LESSONS_LEARNED.md using the
Lesson entry template from
the agent file. Brief is better than nothing.
1---2name: n8n-build-workflow3description: Design and author n8n workflow JSON files. Use when user says "build an n8n workflow", "design a workflow", "wire these nodes", "add a trigger", "convert this requirement into n8n", "set up a Schedule/Webhook trigger", "build an AI workflow", "use the AI Agent node", "add a Switch/IF/Merge/Loop Over Items", "split into sub-workflows", "add error handling", "create an error workflow", "build a webhook that responds to the caller", or asks for a runnable workflow JSON. Covers workflow JSON schema, node/connection wiring, the n8n data structure (array-of-items), triggers (Manual, Webhook, Schedule, Form, App triggers, Error Trigger), flow logic (IF/Switch/Merge/Loop Over Items/Wait), expressions and the `={{ }}` template, AI cluster nodes (Agent + Chat Model + Memory + Tools + Vector Store), sub-workflows, and import/export. Do NOT use for executing workflows at runtime, building community node packages (use n8n-create-nodes), Code-node scripting (use n8n-code-node), or hosting setup (use n8n-self-host).4license: MIT-05---67# n8n Workflow Builder89Design and author production-quality n8n workflows by emitting valid workflow10JSON the user can import directly (Editor → Workflow menu → Import from File /11Import from URL / paste).1213**References:** [WORKFLOW_JSON.md](references/WORKFLOW_JSON.md) | [NODE_CATALOG.md](references/NODE_CATALOG.md) | [EXPRESSIONS.md](references/EXPRESSIONS.md) | [DATA_STRUCTURE.md](references/DATA_STRUCTURE.md) | [AI_WORKFLOWS.md](references/AI_WORKFLOWS.md) | [PATTERNS.md](references/PATTERNS.md)1415**Lessons:** [LESSONS_LEARNED.md](LESSONS_LEARNED.md) — read before non-trivial work.1617**Authoritative docs:** <https://docs.n8n.io/workflows/>1819---2021## Before you start22231. **Identify the trigger.** Every n8n workflow needs at least one trigger24 node. Ask what kicks off the workflow if it isn't obvious. Common choices:2526 | Trigger | When to use |27 |---|---|28 | Manual Trigger | Dev/testing only — user clicks Execute Workflow |29 | Schedule Trigger | Cron-style time-based runs |30 | Webhook | External HTTP callers (synchronous response option available) |31 | n8n Form Trigger | User-facing form submission |32 | Chat Trigger | AI chat workflows (LangChain bundle) |33 | App triggers (Slack, GitHub, Gmail, etc.) | Event push from a SaaS |34 | Email Trigger (IMAP) | Polling a mailbox |35 | Error Trigger | A dedicated workflow that fires when ANOTHER workflow errors |36 | Execute Sub-workflow Trigger | Entry point for a workflow called by another |37382. **Identify the destination(s).** Where does the data end up — Slack, DB,39 another workflow, a webhook response, nowhere (just side effects)?40413. **Identify the data shape.** n8n moves an **array of items** between nodes.42 Each item is `{ json: {...}, binary?: {...}, pairedItem?: {...} }`. Read43 [DATA_STRUCTURE.md](references/DATA_STRUCTURE.md) before authoring any44 non-trivial workflow.45464. **Identify edition constraints.** Some features are Enterprise/Cloud only:47 sub-workflows in some plans, RBAC, External Secrets, Source Control, Log48 Streaming. Don't propose Enterprise features without confirming the user's49 edition.5051---5253## Workflow JSON minimum shape5455```json56{57 "name": "My Workflow",58 "nodes": [59 {60 "parameters": {},61 "id": "11111111-1111-4111-8111-111111111111",62 "name": "Manual Trigger",63 "type": "n8n-nodes-base.manualTrigger",64 "typeVersion": 1,65 "position": [240, 300]66 },67 {68 "parameters": {69 "values": {70 "string": [{ "name": "greeting", "value": "hello" }]71 }72 },73 "id": "22222222-2222-4222-8222-222222222222",74 "name": "Set",75 "type": "n8n-nodes-base.set",76 "typeVersion": 3.4,77 "position": [460, 300]78 }79 ],80 "connections": {81 "Manual Trigger": {82 "main": [83 [{ "node": "Set", "type": "main", "index": 0 }]84 ]85 }86 },87 "active": false,88 "settings": { "executionOrder": "v1" },89 "pinData": {}90}91```9293**Critical rules — get any of these wrong and import breaks:**9495- **Each node needs a UUID `id`.** Use proper UUID v4. Never reuse IDs.96- **Each node needs a unique `name`.** Connections key on names. Renaming a97 node requires rewriting every connection entry that references the old name.98- **`type` is `<package>.<nodeName>`** — almost always `n8n-nodes-base.<name>`99 for built-in nodes, or `@n8n/n8n-nodes-langchain.<name>` for AI cluster100 nodes. Look up the exact id at101 <https://docs.n8n.io/integrations/builtin/node-types/>.102- **`typeVersion` matters.** Older typeVersions accept different parameter103 shapes. When in doubt, use the highest documented version for that node.104- **`connections.<sourceName>.main[outputIndex]`** is an array of arrays — the105 inner arrays group connections going to the same output port (allowing106 fan-out to multiple downstream nodes).107- **`position` is `[x, y]` pixel coords** on the editor canvas. Spacing of108 ~220 px horizontally and ~160 px vertically reads well.109- **`settings.executionOrder: "v1"`** is the modern execution order. Always110 set this; the legacy mode is deprecated.111112See [WORKFLOW_JSON.md](references/WORKFLOW_JSON.md) for the complete schema113including `pinData`, `staticData`, `versionId`, credentials references, and114sub-workflow `meta.templateCredsSetupCompleted`.115116---117118## Connection wiring patterns119120### Linear (most common)121122```json123"connections": {124 "Trigger": { "main": [[{ "node": "Step 1", "type": "main", "index": 0 }]] },125 "Step 1": { "main": [[{ "node": "Step 2", "type": "main", "index": 0 }]] },126 "Step 2": { "main": [[{ "node": "Step 3", "type": "main", "index": 0 }]] }127}128```129130### Fan-out (one source, two parallel branches)131132```json133"connections": {134 "Trigger": {135 "main": [[136 { "node": "Branch A", "type": "main", "index": 0 },137 { "node": "Branch B", "type": "main", "index": 0 }138 ]]139 }140}141```142143### Two outputs (IF node — true on `[0]`, false on `[1]`)144145```json146"connections": {147 "IF": {148 "main": [149 [{ "node": "True Branch", "type": "main", "index": 0 }],150 [{ "node": "False Branch", "type": "main", "index": 0 }]151 ]152 }153}154```155156### AI cluster wiring (Agent node uses non-`main` ports)157158```json159"connections": {160 "OpenAI Chat Model": {161 "ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]162 },163 "Window Buffer Memory": {164 "ai_memory": [[{ "node": "AI Agent", "type": "ai_memory", "index": 0 }]]165 },166 "HTTP Request Tool": {167 "ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]]168 }169}170```171172AI cluster connection types: `ai_languageModel`, `ai_memory`, `ai_tool`,173`ai_outputParser`, `ai_embedding`, `ai_vectorStore`, `ai_document`,174`ai_textSplitter`. See [AI_WORKFLOWS.md](references/AI_WORKFLOWS.md).175176---177178## Expressions — the `={{ }}` template syntax179180n8n parameters accept two modes per field:181182- **Fixed value** — a literal string/number/boolean.183- **Expression** — starts with `=` and contains `{{ ... }}` JS-evaluated184 templates referencing built-in variables.185186Common built-ins inside `{{ }}`:187188| Variable | What it is |189|---|---|190| `$json` | The current item's `.json` payload |191| `$binary` | The current item's `.binary` payload |192| `$input.item` | Wraps the current item (use to access `.pairedItem`) |193| `$input.all()` | Array of all incoming items |194| `$input.first()` / `$input.last()` | First/last incoming item |195| `$('Node Name').item` | Single item from a previously-executed node |196| `$('Node Name').all()` | All items output by that node |197| `$('Node Name').first()` / `.last()` | Self-explanatory |198| `$node["Node Name"].json` | Legacy alternative to `$('Node Name').item.json` |199| `$workflow` | Workflow metadata (id, name, active) |200| `$execution` | Execution metadata (id, mode, resumeUrl) |201| `$env` | Hosting env vars (only if `N8N_BLOCK_ENV_ACCESS_IN_NODE=false`) |202| `$now` / `$today` | Luxon DateTime helpers |203| `$jmespath()` | JMESPath query helper |204| `$secrets.<vault>.<key>` | External Secrets (Enterprise) |205206**Example: pull a field from a prior node into a Slack message:**207208```209=Hi {{ $('Webhook').item.json.body.username }}, new order #{{ $json.orderId }}210```211212See [EXPRESSIONS.md](references/EXPRESSIONS.md) for the full reference213including string/array/object/date helpers, the `$()` accessor's rules around214nodes that haven't executed yet, and gotchas with multi-item references.215216---217218## The flow-logic node toolbox219220| Node | Use it for |221|---|---|222| **IF** (`n8n-nodes-base.if`) | Two-way branch on a single condition |223| **Switch** (`n8n-nodes-base.switch`) | N-way branch on rules or on a value matching cases |224| **Filter** (`n8n-nodes-base.filter`) | Drop items that don't match a rule (no second branch) |225| **Merge** (`n8n-nodes-base.merge`) | Combine inputs (Append, Combine by Key, Combine by Position, SQL Query) |226| **Loop Over Items** / Split In Batches (`n8n-nodes-base.splitInBatches`) | Iterate the array of items in chunks; has a "done" output |227| **Wait** (`n8n-nodes-base.wait`) | Pause N seconds, resume at time, or resume on Webhook |228| **Stop and Error** (`n8n-nodes-base.stopAndError`) | Throw to halt the workflow with a custom message |229| **No Operation** (`n8n-nodes-base.noop`) | Join branches visually without changing data |230| **Execute Sub-workflow** (`n8n-nodes-base.executeWorkflow`) | Call another workflow (returns items) |231| **Execute Sub-workflow Trigger** | Entry point in the called workflow |232233See [PATTERNS.md](references/PATTERNS.md) for proven recipes:234235- **Loop with rate limit** — Loop Over Items + Wait236- **Aggregate then summarize** — Aggregate + Summarize237- **Error workflow handoff** — Error Trigger → Slack/PagerDuty238- **Webhook → process → respond synchronously** — Webhook (Respond: "Using239 Respond to Webhook Node") → ... → Respond to Webhook240- **Long-running webhook** — Webhook (Respond: Immediately) → background work241- **Idempotency** — Remove Duplicates + Data Tables242- **Pagination** — HTTP Request with built-in Pagination options OR Loop with243 cursor stored in `getWorkflowStaticData('global')`244245---246247## AI workflows (cluster nodes)248249The LangChain bundle (`@n8n/n8n-nodes-langchain.*`) adds **root nodes**250(Agent, Chains, Vector Store roots, Information Extractor, Text Classifier,251Sentiment Analysis) plus **sub-nodes** (Chat Models, Memory, Tools, Output252Parsers, Embeddings, Text Splitters, Document Loaders, Retrievers, Rerankers).253254Sub-nodes attach to root nodes via **non-`main` connection types** listed255above. They don't process items in the normal data-flow sense — they256configure the root node.257258Common stacks:259260- **Chat Agent:** Chat Trigger → AI Agent + OpenAI Chat Model + Window Buffer261 Memory + (one or more Tools)262- **RAG QA:** Question and Answer Chain + OpenAI Chat Model + Vector Store263 Retriever + Vector Store + Embeddings + Document Loader + Text Splitter264- **Information Extractor:** Source → Information Extractor + Chat Model +265 Structured Output Parser → downstream266267See [AI_WORKFLOWS.md](references/AI_WORKFLOWS.md) for full topologies, the268`fromAI()` function for tool parameters, evaluations, and human-in-the-loop269patterns.270271---272273## Procedure (every workflow-build request)2742751. **Clarify trigger + destination + data shape** (one short paragraph back to276 the user) if not obvious from the request.2772. **Read existing JSON if any.** Don't blindly overwrite. Preserve unrelated278 nodes, keep node IDs, keep credential references.2793. **Plan node graph on paper first.** List nodes, their types, and the280 connection topology. Catch missing merges or unwired branches NOW, not281 after generating JSON.2824. **Generate the JSON.** Use the patterns above. Use real UUIDs (`uuidgen`,283 `crypto.randomUUID()`, or any UUID v4 generator).2845. **Hand the user copy/paste-ready JSON.** Tell them: "Import this via285 Workflow menu → Import from File (or paste into a new tab via Ctrl/Cmd-A,286 Ctrl/Cmd-V on the canvas)."2876. **Call out credentials they need to attach.** Workflow JSON includes a288 `credentials` reference per node by name+id, but the actual credential289 values live in the n8n instance. The user must select credentials for any290 node that needs them after import.2917. **Mention activation gotchas:** for Webhook/Schedule/Form/App triggers,292 workflows only fire on the **Production URL / production schedule** after293 the user toggles "Inactive → Active" in the top bar.2948. **Update `LESSONS_LEARNED.md`** if you discovered anything per the agent's295 continuous-learning rules.296297---298299## Best practices300301**Do:**302- Set `settings.executionOrder` to `"v1"` always.303- Name nodes descriptively. The name is what shows up in expressions304 (`$('My Descriptive Name').item.json...`).305- Add **Sticky Notes** (`n8n-nodes-base.stickyNote`) to document non-obvious306 sections. They cost nothing at runtime and survive export/import.307- Use **Edit Fields (Set)** liberally to shape data into the schema the next308 node expects. Better than complex expressions everywhere.309- Use **Sub-workflows** for any logic shared by 2+ workflows or for blocks310 that exceed ~15 nodes.311- Add an **Error Workflow** at the instance level for production work — set312 it as `errorWorkflow` in the workflow settings, and build a separate313 workflow whose trigger is the Error Trigger node.314- Use `Loop Over Items` (Split In Batches) when calling rate-limited APIs;315 pair it with the Wait node.316- Use `Remove Duplicates` keyed on a stable ID for idempotent processing of317 upstream feeds.318- For long-running webhooks, set Webhook → Respond: "Immediately" and do work319 in the background.320321**Don't:**322- Don't fan out into 5+ parallel branches that all hit the same API — you'll323 rate-limit yourself. Sequence with Loop Over Items + Wait instead.324- Don't use Manual Trigger in production workflows. It won't fire on325 schedule/webhook/event.326- Don't put secrets in node parameters as plain text — use Credentials, or327 for Enterprise, External Secrets.328- Don't depend on item order across branches that merged — order is329 preserved within a branch but not across merges (use Merge → Combine by330 Key when order matters).331- Don't forget to handle the "no items" case in Loop Over Items — if the332 input is empty, the loop never runs and downstream nodes may not execute.333334---335336## Common mistakes (read [PATTERNS.md](references/PATTERNS.md) for full list)337338| Symptom | Likely cause | Fix |339|---|---|---|340| "Cannot read properties of undefined (reading 'json')" | Referencing a node that hasn't run on this branch | Use `$('Node').first()?.json.field ?? 'fallback'` |341| Workflow runs once then nothing | Manual Trigger only fires on Execute Workflow | Replace with Schedule/Webhook/App trigger |342| Webhook returns empty body to caller | Default Webhook responds with "When last node finishes" but workflow doesn't return data | Use Respond to Webhook node and set Webhook → Respond: "Using Respond to Webhook Node" |343| IF/Switch sends nothing downstream | Confused which output port is true vs false | IF: index 0 = true, 1 = false. Switch: order matches rule order |344| `$json` in Code node returns wrong shape | Code node runs ONCE for all items by default | Set "Mode: Run Once for Each Item" or iterate `$input.all()` |345| Pairing breaks after a Code node | Code node didn't set `pairedItem` on returned items | See [n8n-code-node](../n8n-code-node/SKILL.md) for the canonical pattern |346| Imported workflow can't run | Credentials weren't selected post-import | After import, open each node with a yellow warning triangle and pick credentials |347348---349350## Continuous learning351352If during this session you discovered:353- A node-specific quirk that wasn't documented354- A wiring pattern that solved a non-obvious problem355- A version-specific behavior change356- A workaround for an n8n bug357358… append a dated entry to [LESSONS_LEARNED.md](LESSONS_LEARNED.md) using the359[Lesson entry template](../../agents/n8n.agent.md#lesson-entry-template) from360the agent file. Brief is better than nothing.