[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-n8n-n8n-subworkflows-official":3,"mdc-3uxn51-key":29,"related-repo-n8n-n8n-subworkflows-official":2606,"related-org-n8n-n8n-subworkflows-official":2706},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":19,"repoUrl":20,"updatedAt":21,"license":22,"forks":23,"topics":24,"repo":25,"sourceUrl":27,"mdContent":28},"n8n-subworkflows-official","build reusable n8n subworkflows","Use when building anything multi-step, anything that looks repeatable, anything the user mentions reusing, or any workflow with more than ~10 nodes. Triggers on \"reuse\", \"I do this in another workflow\", \"extract\", \"modular\", \"shared logic\", \"subworkflow\", multi-step builds, or any task that mentions logic the user has built before.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},"n8n","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fn8n.png","n8n-io",[12,16],{"name":13,"slug":14,"type":15},"Automation","automation","tag",{"name":17,"slug":18,"type":15},"Workflow Automation","workflow-automation",319,"https:\u002F\u002Fgithub.com\u002Fn8n-io\u002Fskills","2026-07-24T05:37:44.030001",null,30,[],{"repoUrl":20,"stars":19,"forks":23,"topics":26,"description":22},[],"https:\u002F\u002Fgithub.com\u002Fn8n-io\u002Fskills\u002Ftree\u002FHEAD\u002Fskills\u002Fn8n-subworkflows-official","---\nname: n8n-subworkflows-official\ndescription: Use when building anything multi-step, anything that looks repeatable, anything the user mentions reusing, or any workflow with more than ~10 nodes. Triggers on \"reuse\", \"I do this in another workflow\", \"extract\", \"modular\", \"shared logic\", \"subworkflow\", multi-step builds, or any task that mentions logic the user has built before.\n---\n\n# n8n Sub-workflows\n\nSub-workflows are reusable functions. The `Execute Workflow Trigger` declares input parameters, the body does work, the last node returns output. Callers invoke it like any other node.\n\nThat framing opens up the function-shaped wins: encapsulation, reuse, testability, replaceability. It's the primary reuse mechanism in n8n, and unfortunately underused.\n\nWithout sub-workflows, the same logic gets duplicated across workflows. Bug fixes happen in multiple places, one gets missed, and \"identical\" copies drift.\n\n## Non-negotiables\n\n1. **Search before you build.** Before writing logic that handles a generic problem, check if a sub-workflow already exists. Filter by tag (`search_workflows({ tags: ['subworkflow'] })`, a domain tag) and\u002For keyword (`query: '\u003Ckeyword>'`). Tags are the discovery mechanism (n8n 2.27.0+).\n2. **`Execute Workflow Trigger` uses \"Define Below\" with typed fields, not passthrough.** Define Below is the only mode that lets agent tools (`fromAi`) and structured callers pass values in. Two exceptions: (a) the sub-workflow specifically needs to receive binary (then it can't be wired as an agent tool directly), or (b) the sub-workflow takes no inputs at all (Define Below requires at least one field). See \"Sub-workflow inputs and outputs\" below.\n\n## Strong defaults\n\n- **Anything reusable becomes a sub-workflow.** If a logical chunk could plausibly be needed elsewhere, extract it. Exception: trivial wrappers (one HTTP call, no logic) and tightly-coupled-to-this-caller chunks.\n- **Default to stateless for pure logic** (input → output, no external state). For state-touching logic, build *deliberately* stateful sub-workflows that abstract the operation behind a clean contract (the ORM \u002F repository pattern). What to avoid is *accidental* state: a \"validate\" sub-workflow that quietly writes to a log table.\n- **Tag for discovery**: every sub-workflow gets `subworkflow`, a domain tag (`customer`), and\u002For `tool`. Tags are what `search_workflows({ tags })` filters on; names stay plain and descriptive (`Parse RFC2822 date`). See `references\u002FNAMING_AND_DISCOVERY.md`.\n- **Description carries keywords.** Input\u002Foutput shape + representative terms, so varied queries surface it.\n- **Split when input contracts genuinely differ** (binary vs JSON, sync vs async, divergent auth schemes). Don't fit divergent contracts under one trigger via passthrough + internal branching. See `references\u002FSUBWORKFLOW_PATTERNS.md` \"Splitting by input shape\".\n\n## Decision tree: should this be a sub-workflow?\n\n```\nAbout to write a chunk of logic?\n├── Could this plausibly be needed in another workflow?\n│   ├── Yes → extract to sub-workflow\n│   └── No → keep inline\n│\n├── Is this chunk >5 nodes and conceptually one thing?\n│   └── Extract if you want testability, isolation, or reuse; if it's only for a cleaner canvas, group it inline instead (see below).\n│\n├── Is this chunk dealing with a generic concern (auth, retry, parsing, formatting)?\n│   └── Almost certainly extract. These are the canonical reusable sub-workflows.\n│\n└── Is this chunk doing one HTTP call with no logic around it?\n    └── Don't extract. Extra workflow boundary for nothing.\n```\n\nWhen the only motivation is a cleaner canvas (not reuse, isolation, testability, or an agent tool), a **canvas node group** is the tool for readability-only sectioning: faster (no sub-execution per call) and simpler (no input\u002Foutput contract), with the logic staying inline. Group it where the section forms a valid group (connected, single entry\u002Fexit); split a too-branchy section into smaller groups or leave it ungrouped (a sticky note annotates, it doesn't group). Extract to a sub-workflow only when you genuinely need reuse, isolation, independent testing, or an agent tool. See `n8n-workflow-lifecycle-official` Readability.\n\n## Stateless vs. stateful sub-workflows\n\nBoth are first-class. The choice is about intent and encapsulation.\n\n### Stateless\n\nTakes input, returns output. No I\u002FO outside the inputs\u002Foutputs. Default for pure logic.\n\nExamples:\n\n- `Parse RFC2822 date` (tag `subworkflow`). Input: date string. Output: ISO date or error.\n- `Compute MRR from subscription` (tag `subworkflow`). Input: subscription object. Output: MRR number.\n- `Format invoice as HTML` (tag `subworkflow`). Input: invoice data. Output: HTML string.\n\nWhen you need the logic again, call it without worrying about side effects firing.\n\n### Stateful (deliberate)\n\nReads or writes external state behind a clean input\u002Foutput contract. Comparable to a repository pattern: the sub-workflow abstracts the state operation so callers think in domain terms, not implementation.\n\nExamples:\n\n- `Get customer by id` (tag `customer`). Input: id. Output: customer object or `{ ok: false, error: 'not_found' }`. Reads the DB.\n- `Write customer billing record` (tags `customer`, `billing`). Input: record. Output: `{ ok: true, id }`. Writes the DB.\n- `Append audit event` (tag `audit`). Input: event. Output: `{ ok: true, eventId }`. Writes to a logging store.\n- `Send to on-call` (tag `notification`). Input: channel, message. Output: `{ ok: true, messageId }`. Calls Slack\u002FSMTP.\n\nThe point of building these as sub-workflows:\n\n- Callers think in domain terms (`get customer by id`), not in storage (`SELECT * FROM customers ...`).\n- Swap the underlying store\u002FAPI behind it (Postgres → Supabase, native node → HTTP) without touching callers.\n- Idempotency, retry, and validation become the sub-workflow's responsibility, centralized in one place.\n\nWhat to avoid is *accidental* state: a sub-workflow named\u002Fdescribed as pure that quietly writes to a log table. That ambushes callers who reasonably assumed it was safe to retry or compose. Either make the side effect part of the contract (rename, document, return its result) or move it out.\n\n## When to extract\n\nThe two main signals:\n\n### 1. Conceptual coherence\n\nWhen a chunk of nodes does one logical thing, even unreused, extraction can be worth it for:\n\n- **Testability.** Run the sub-workflow on its own with pinned data.\n- **Replaceability.** Swapping implementations doesn't ripple to callers.\n\nReadability alone is not a reason to extract: a node group collapses five nodes into one labeled box more cheaply (no sub-execution, no input\u002Foutput contract). Extract when you also want testability, replaceability, or reuse.\n\n### 1.5 The fire-and-forget audit-log pattern\n\n> Audit logging is used here as a concrete illustration of the fire-and-forget stateful pattern. **Don't add audit logging to a workflow unless the user asked for it.** The pattern itself (fire a sub-workflow async, don't block on it) generalizes to any side observation: metrics, notifications, etc.\n\nA deliberately stateful audit-log sub-workflow invoked with `Execute Workflow`'s `waitForSubWorkflow: false` so the caller doesn't block on the write.\n\n```\nCaller ──→ [Execute Workflow: DB audit log]\n              { title: 'Email Confirmation Received',\n                description: \u003Cserialized data> }\n              waitForSubWorkflow: false\n              ↓ (caller continues immediately)\n        ──→ [Continue with next step]\n```\n\nThe sub-workflow takes a title and description, writes to a logging table (or Slack, or both), returns. The caller doesn't wait. Audit log is a side observation, not the critical path.\n\nWhen the user has asked for it, fire one at every meaningful state transition (\"email confirmation received\", \"user verified\", \"processing started\", \"eligibility decision made\") so the timeline reconstructs from logs.\n\nWhy it's valuable:\n\n- **Observability for free.** Per-execution timeline when something goes wrong.\n- **No coupling.** Implementation (DB, Slack, both) can change without touching callers.\n- **Async by default.** `waitForSubWorkflow: false` means the audit doesn't slow the main workflow.\n\nThe audit-log workflow is the right kind of stateful sub-workflow. The side effect is the point.\n\n### 1.7 The middleware pattern\n\nWhen a webhook workflow is API-shaped, treat it like one. Sub-workflows become middleware: small stateless functions that run before the main handler and either pass through or short-circuit with a 4xx.\n\n```\nWebhook\n  → [Verify JWT]    # decode + validate; 401 on failure\n  → [Rate limit]    # check + bump counter; 429 on failure\n  → IF (all middleware ok)\n    → Main handler logic\n    → Respond 200\n  → ELSE → Respond with the 4xx the middleware returned\n```\n\nCanonical example: custom JWT auth rolled inside n8n. `Verify JWT` (tag `subworkflow`) takes the raw `Authorization` header, decodes, validates signature and expiry, returns `{ ok: true, user_id }` or `{ ok: false, status: 401, message }`. The caller IFs on `ok`, responds early on failure, continues on success.\n\nWhy a sub-workflow and not inline: every webhook that needs auth calls the same one. Swap the library, rotate the signing key, or add refresh-token logic in a single place. The reuse target is exact, the contract is small, and the failure response shape is consistent across every API endpoint.\n\nPairs with `n8n-error-handling-official` for 4xx\u002F5xx response shapes and `n8n-credentials-and-security-official` for the underlying secret handling.\n\n### 2. Repetition pattern\n\nYou're about to build something you've built before. Stop. Search.\n\n```\nsearch_workflows({ query: 'date' })\nsearch_workflows({ tags: ['customer'] })\nsearch_workflows({ tags: ['subworkflow'] })\n```\n\nIf something matches, use it. If not, build it as a sub-workflow and tag it so the *next* search finds it. The tag convention (`subworkflow`, domain tags, `tool`) is what makes that work.\n\n## Linear, long workflows are fine when most of the work is in sub-workflows\n\nA workflow can have 20+ nodes and still be readable if it's mostly a linear orchestration of sub-workflow calls and decisions. The shape (audit-log nodes shown only because they're a vivid example of \"side observation between real steps\", include them only if the user asked for audit logging):\n\n```\nWebhook\n  → Audit log (sub-workflow)\n  → Validate\n  → Audit log (sub-workflow)\n  → IF auth ok\n    → Look up user (or sub-workflow)\n    → Audit log (sub-workflow)\n    → Process step 1 (sub-workflow)\n    → Audit log (sub-workflow)\n    → Process step 2 (sub-workflow)\n    → Audit log (sub-workflow)\n    → Decide eligibility (sub-workflow)\n    → Audit log (sub-workflow)\n    → Send notification (sub-workflow)\n    → Respond\n```\n\nEach \"logical step\" is a sub-workflow call. The caller is a long but linear narrative, easy to follow top-to-bottom. Logic lives in the sub-workflows.\n\nThis is *not* the same as a 20-node workflow with 20 inline transformations. That's hard to read. The pattern above is fine because:\n\n- Each node has one purpose (call a specific sub-workflow).\n- Node groups mark sections, sticky notes annotate them (per `n8n-workflow-lifecycle-official` \"Readability\").\n- Inspecting a section means opening the sub-workflow it calls. That's encapsulation.\n- Orchestration logic at the top level is visible without reading implementations.\n\nIf your workflow has 15+ nodes and isn't mostly Execute Workflow calls and branches, extract more where reuse or testing warrants it, and group the rest inline (node groups).\n\n## When NOT to extract\n\n- **One HTTP call with no logic.** A sub-workflow that's just `Execute Workflow → HTTP Request → return` adds a boundary for nothing. Inline it.\n- **Tightly coupled to the caller's specific shape.** If the chunk takes a deeply nested input that only this caller produces, extracting it just relocates the coupling. Fix the data shape first.\n- **Performance-critical hot paths.** Each sub-workflow call adds latency (small, but real). For high-throughput workflows, profile before adding boundaries.\n\n## Search-before-build protocol\n\nWhen the user describes something multi-step or generic-sounding:\n\n```\n1. search_workflows with relevant tags and\u002For query (e.g. `tags: ['subworkflow']`, a domain tag, the operation keyword)\n2. If candidates appear, fetch get_workflow_details on the top 1-3\n3. Confirm fit by reading the inputs\u002Foutputs and (briefly) the body\n4. If a fit exists → use it. Tell the user \"I found `\u003Cname>`. Using that.\"\n5. If no fit exists → build new and tag it (`subworkflow`, domain, `tool`) so the next search finds it\n```\n\nThe \"tell the user\" step matters. They benefit from knowing what's already in their library.\n\nIf a workflow you expect to find isn't appearing, the most common cause is per-workflow MCP access not being enabled. See `n8n-workflow-lifecycle-official` `references\u002FMCP_ACCESS_PER_WORKFLOW.md`.\n\n## Sub-workflow inputs and outputs\n\nSub-workflows are triggered by `Execute Workflow Trigger` nodes. The trigger declares the input schema. The caller passes data via `Execute Workflow`, and the sub-workflow returns whatever its last node outputs.\n\n### Always use \"Define Below\" with explicit fields\n\nThe `Execute Workflow Trigger` has two input modes. **Default to \"Define Below\" (typed fields).** This is the only mode that lets agent tools (via `fromAi()`) and any structured caller pass values in. Without declared fields, the agent has no schema to fill and the sub-workflow can't be wired as a `toolWorkflow` cleanly.\n\nShape:\n\n```ts\nconst subTrigger = trigger({\n    type: 'n8n-nodes-base.executeWorkflowTrigger',\n    config: {\n        parameters: {\n            workflowInputs: {\n                values: [\n                    { name: 'list_of_ids', type: 'array' },\n                    { name: 'include_transcript', type: 'boolean' },\n                    { name: 'session_id', type: 'string' },\n                ],\n            },\n        },\n    },\n})\n```\n\nEach declared input becomes a typed parameter the caller can fill. Inside the workflow, access via `$json.list_of_ids`, etc., or `$('When Executed by Another Workflow').first().json.\u003Cfield>` from anywhere downstream.\n\nPick types deliberately (`string`, `number`, `boolean`, `array`, `object`). The model uses these as the required types when filling agent tool parameters, and humans rely on them when wiring callers.\n\n### Exception 1: passthrough mode for binary\n\nIf the sub-workflow needs to receive binary (image, file, PDF), `Define Below` doesn't work because typed fields are JSON only. Switch to passthrough:\n\n```ts\nconst subTrigger = trigger({\n    type: 'n8n-nodes-base.executeWorkflowTrigger',\n    config: {\n        parameters: {\n            inputSource: 'passthrough',\n        },\n    },\n})\n```\n\nIn passthrough mode, the sub-workflow receives the caller's items as-is, including the `binary` slot. Cost: no typed input schema, so agent tools can't pass parameters through `fromAi()`. Use this mode for sub-workflows called by other workflows (not agents) where binary needs to flow through.\n\nFor sub-workflows that need binary AND are called by an agent, see `n8n-binary-and-data-official` `references\u002FAGENT_TOOL_BINARY.md` (agent tools can't pass binary directly).\n\n### Exception 2: passthrough for sub-workflows with no inputs\n\nDefine Below requires at least one declared field. A sub-workflow that genuinely takes no inputs (a \"list active credentials\" tool, a \"current count\" lookup, any zero-arg operation) has nowhere to put the empty schema, so passthrough is the only option.\n\nWhen using passthrough specifically for the no-input case:\n\n- **Start the body with a `Set` (Edit Fields) node in \"Keep Only Set\" mode with no fields.** This clears the caller's JSON so downstream nodes don't accidentally read fields from whatever shape the caller happened to pass. Without it, the body silently picks up whatever the caller forwarded.\n- **Add a sticky note on the trigger documenting that no inputs are expected.** Future readers (and the agent re-wiring this as a tool) need to know passthrough isn't here for binary, it's here because the schema is empty by design.\n\nAgent-tool wiring still works in the no-input case: `toolWorkflow` accepts a sub-workflow whose input mapping has no fields. The agent's only decision is whether to invoke. The pattern from `n8n-agents-official` `references\u002FTOOLS.md` (\"zero `fromAi` parameters\") applies directly.\n\n### Other conventions\n\n- **Document inputs and outputs in the workflow `description`.** Field names, types, purpose. The description is what callers (humans and agents) read for the contract.\n- **Return a consistent shape.** For expected failures (e.g., parse error), return `{ success: false, error: '...' }` rather than throwing. Callers can branch without wrapping error outputs.\n- **Treat the input schema as a contract once it has callers.** Adding optional fields is safe. Renaming or removing fields can be done, but only carefully: enumerate every caller (`search_workflows` for the sub-workflow's name + manual scan), migrate them in the same change, and verify with `validate_workflow` + `get_workflow_details` before publishing. A silent break here is hard to detect because n8n won't error on an unrecognized input field. The sub-workflow just sees `undefined` and the caller has no idea.\n- **Use a final Set \u002F Edit Fields node to shape the return.** Optional, sometimes required (when the last computation node carries noise fields), and good practice for sub-workflows even when not strictly required. It makes the return contract explicit at the boundary, so readers see the API by reading one node. This is the legitimate exception to the Set-node antipattern from `n8n-expressions-official`: the implicit consumer of a sub-workflow's last node is *every caller*, so the Set earns its place as the explicit API boundary. Name it `Return` or `Return \u003Cthing>`.\n- **Return natural shapes, not storage shapes.** A sub-workflow that owns a Data Table, a file in S3, or any storage layer should hide that representation from callers. Arrays return as arrays, objects as objects, dates as ISO strings, regardless of whether the underlying storage was JSON-stringified text or another internal format. The return contract is the *interface*. The storage layout is *implementation detail*.\n\n  Common slip: a sub-workflow has a \"fresh\" path (data just produced, natural shape) and a \"cached\" path (data just read from a `_object` column, still stringified). Wrong instinct: stringify the fresh path \"to match\" the cached path. Right instinct: parse the cached path so both return the natural shape. Callers shouldn't have to know which they got.\n\nFor sub-workflows wired as agent tools specifically, see `n8n-agents-official` `references\u002FSUBWORKFLOW_AS_TOOL.md`.\n\n## Calling sub-workflows: `Execute Workflow` modes\n\nTwo settings on the caller-side `Execute Workflow` node beyond inputs\u002FworkflowId:\n\n- **`mode`** defaults to `'all'`: the sub-workflow runs **once** with all N items as input. Items still flow through nodes per-item like any other workflow. Set `mode: 'each'` to run the sub-workflow N separate times, one item per execution. For sub-workflows whose body just processes items normally, the two are equivalent. The split matters when the sub-workflow's body assumes it sees exactly one item (per-run aggregation, \"this is THE customer to operate on\" logic, a final write that should fire once per input). `mode: 'each'` matches that assumption, `mode: 'all'` breaks it. When you DO need per-item iteration, prefer `mode: 'each'` over a Loop Over Items node inside the sub-workflow.\n- **`waitForSubWorkflow`** defaults to `true`. Setting `options.waitForSubWorkflow: false` fires the call and immediately moves on, and the sub-workflow continues in the background. The caller's downstream sees no return data.\n\n`mode: 'each'` + `waitForSubWorkflow: false` is **the only true parallelization n8n offers**: N sub-workflow executions dispatched without waiting, running concurrently (still bounded by per-instance concurrency limits and per-call overhead). Useful for \"kick off N independent jobs, poll\u002Faggregate later\". For example: dispatch a long-running job per item, track each in a Data Table, then loop until all rows mark themselves complete or time out.\n\nFor the polling-after-fire-and-forget pattern, see `references\u002FSUBWORKFLOW_PATTERNS.md` \"Fire-and-forget parallelization\".\n\n## Reference files\n\n| File | Read when |\n|---|---|\n| `references\u002FSUBWORKFLOW_PATTERNS.md` | `mode: 'all'` vs `'each'` default, splitting by input shape (binary\u002Fpassthrough vs Define Below), fire-and-forget parallelization with Data Table polling |\n| `references\u002FNAMING_AND_DISCOVERY.md` | Naming and tagging a new sub-workflow, searching for existing ones, the tag convention |\n\n## Anti-patterns\n\n| Anti-pattern | What goes wrong | Fix |\n|---|---|---|\n| Duplicating the same date-parsing nodes in three workflows | Bug fixes happen in two places, miss the third | Extract to a single `Parse \u003Cformat> date` sub-workflow (tag `subworkflow`) once |\n| Building a new sub-workflow without searching | Library grows duplicates, and future searches find both | Always `search_workflows` first |\n| Sub-workflow named\u002Fdescribed as pure that quietly writes to a log table | Callers can't reason about retry or idempotency, side effect ambushes them | Either make the side effect part of the contract (rename, document, return its result) or move it out |\n| Sub-workflow with no `description` | Won't be found in future searches, nobody knows what it does | Set `description` with input\u002Foutput shape and purpose |\n| Sub-workflow named `Helper 3` | Name doesn't tell anyone what it does | Verb-first descriptive name (`Parse RFC2822 date`), see `n8n-workflow-lifecycle-official` `NAMING_CONVENTIONS.md` |\n| Untagged sub-workflow | Won't show up under any `tags` filter, future you can't find it | Tag it (`subworkflow`, domain, `tool`) right after create via `update_workflow` `addTags` |\n| `Execute Workflow Trigger` set to `passthrough` when not handling binary and not deliberately zero-input | No typed schema means agent tools can't fill parameters via `fromAi`, structured callers can't pass values cleanly | Use \"Define Below\" with declared `workflowInputs.values` (name + type per field). The exceptions are binary-receiving sub-workflows and sub-workflows that genuinely take no inputs (see \"Exception 2\") |\n| Passthrough trigger for a zero-input sub-workflow without a Set-to-clear node and explanatory sticky | Body silently reads stray fields from whatever the caller forwarded; future readers think passthrough is for binary | Add a `Set` (\"Keep Only Set\", no fields) at the top of the body and a sticky on the trigger noting no inputs are expected |\n| Sub-workflow called as an agent tool that expects binary input | Agent tools can't pass binary directly | See `n8n-binary-and-data-official` `AGENT_TOOL_BINARY.md` for the right pattern |\n| 30-node workflow with no extraction | Hard to read, hard to test, hard to replace | Extract logical sections into sub-workflows |\n\n",{"data":30,"body":31},{"name":4,"description":6},{"type":32,"children":33},"root",[34,43,58,63,68,75,130,136,261,267,279,299,305,310,317,322,327,381,386,392,397,401,515,520,554,565,571,576,582,587,610,615,621,637,658,667,672,677,682,722,727,733,738,747,798,803,824,830,835,844,870,876,881,890,895,907,937,942,948,989,995,1000,1009,1014,1032,1038,1057,1063,1098,1103,1491,1512,1550,1556,1569,1714,1734,1754,1760,1765,1770,1801,1835,1841,1997,2015,2028,2040,2131,2154,2166,2172,2245,2251,2600],{"type":35,"tag":36,"props":37,"children":39},"element","h1",{"id":38},"n8n-sub-workflows",[40],{"type":41,"value":42},"text","n8n Sub-workflows",{"type":35,"tag":44,"props":45,"children":46},"p",{},[47,49,56],{"type":41,"value":48},"Sub-workflows are reusable functions. The ",{"type":35,"tag":50,"props":51,"children":53},"code",{"className":52},[],[54],{"type":41,"value":55},"Execute Workflow Trigger",{"type":41,"value":57}," declares input parameters, the body does work, the last node returns output. Callers invoke it like any other node.",{"type":35,"tag":44,"props":59,"children":60},{},[61],{"type":41,"value":62},"That framing opens up the function-shaped wins: encapsulation, reuse, testability, replaceability. It's the primary reuse mechanism in n8n, and unfortunately underused.",{"type":35,"tag":44,"props":64,"children":65},{},[66],{"type":41,"value":67},"Without sub-workflows, the same logic gets duplicated across workflows. Bug fixes happen in multiple places, one gets missed, and \"identical\" copies drift.",{"type":35,"tag":69,"props":70,"children":72},"h2",{"id":71},"non-negotiables",[73],{"type":41,"value":74},"Non-negotiables",{"type":35,"tag":76,"props":77,"children":78},"ol",{},[79,107],{"type":35,"tag":80,"props":81,"children":82},"li",{},[83,89,91,97,99,105],{"type":35,"tag":84,"props":85,"children":86},"strong",{},[87],{"type":41,"value":88},"Search before you build.",{"type":41,"value":90}," Before writing logic that handles a generic problem, check if a sub-workflow already exists. Filter by tag (",{"type":35,"tag":50,"props":92,"children":94},{"className":93},[],[95],{"type":41,"value":96},"search_workflows({ tags: ['subworkflow'] })",{"type":41,"value":98},", a domain tag) and\u002For keyword (",{"type":35,"tag":50,"props":100,"children":102},{"className":101},[],[103],{"type":41,"value":104},"query: '\u003Ckeyword>'",{"type":41,"value":106},"). Tags are the discovery mechanism (n8n 2.27.0+).",{"type":35,"tag":80,"props":108,"children":109},{},[110,120,122,128],{"type":35,"tag":84,"props":111,"children":112},{},[113,118],{"type":35,"tag":50,"props":114,"children":116},{"className":115},[],[117],{"type":41,"value":55},{"type":41,"value":119}," uses \"Define Below\" with typed fields, not passthrough.",{"type":41,"value":121}," Define Below is the only mode that lets agent tools (",{"type":35,"tag":50,"props":123,"children":125},{"className":124},[],[126],{"type":41,"value":127},"fromAi",{"type":41,"value":129},") and structured callers pass values in. Two exceptions: (a) the sub-workflow specifically needs to receive binary (then it can't be wired as an agent tool directly), or (b) the sub-workflow takes no inputs at all (Define Below requires at least one field). See \"Sub-workflow inputs and outputs\" below.",{"type":35,"tag":69,"props":131,"children":133},{"id":132},"strong-defaults",[134],{"type":41,"value":135},"Strong defaults",{"type":35,"tag":137,"props":138,"children":139},"ul",{},[140,150,175,233,243],{"type":35,"tag":80,"props":141,"children":142},{},[143,148],{"type":35,"tag":84,"props":144,"children":145},{},[146],{"type":41,"value":147},"Anything reusable becomes a sub-workflow.",{"type":41,"value":149}," If a logical chunk could plausibly be needed elsewhere, extract it. Exception: trivial wrappers (one HTTP call, no logic) and tightly-coupled-to-this-caller chunks.",{"type":35,"tag":80,"props":151,"children":152},{},[153,158,160,166,168,173],{"type":35,"tag":84,"props":154,"children":155},{},[156],{"type":41,"value":157},"Default to stateless for pure logic",{"type":41,"value":159}," (input → output, no external state). For state-touching logic, build ",{"type":35,"tag":161,"props":162,"children":163},"em",{},[164],{"type":41,"value":165},"deliberately",{"type":41,"value":167}," stateful sub-workflows that abstract the operation behind a clean contract (the ORM \u002F repository pattern). What to avoid is ",{"type":35,"tag":161,"props":169,"children":170},{},[171],{"type":41,"value":172},"accidental",{"type":41,"value":174}," state: a \"validate\" sub-workflow that quietly writes to a log table.",{"type":35,"tag":80,"props":176,"children":177},{},[178,183,185,191,193,199,201,207,209,215,217,223,225,231],{"type":35,"tag":84,"props":179,"children":180},{},[181],{"type":41,"value":182},"Tag for discovery",{"type":41,"value":184},": every sub-workflow gets ",{"type":35,"tag":50,"props":186,"children":188},{"className":187},[],[189],{"type":41,"value":190},"subworkflow",{"type":41,"value":192},", a domain tag (",{"type":35,"tag":50,"props":194,"children":196},{"className":195},[],[197],{"type":41,"value":198},"customer",{"type":41,"value":200},"), and\u002For ",{"type":35,"tag":50,"props":202,"children":204},{"className":203},[],[205],{"type":41,"value":206},"tool",{"type":41,"value":208},". Tags are what ",{"type":35,"tag":50,"props":210,"children":212},{"className":211},[],[213],{"type":41,"value":214},"search_workflows({ tags })",{"type":41,"value":216}," filters on; names stay plain and descriptive (",{"type":35,"tag":50,"props":218,"children":220},{"className":219},[],[221],{"type":41,"value":222},"Parse RFC2822 date",{"type":41,"value":224},"). See ",{"type":35,"tag":50,"props":226,"children":228},{"className":227},[],[229],{"type":41,"value":230},"references\u002FNAMING_AND_DISCOVERY.md",{"type":41,"value":232},".",{"type":35,"tag":80,"props":234,"children":235},{},[236,241],{"type":35,"tag":84,"props":237,"children":238},{},[239],{"type":41,"value":240},"Description carries keywords.",{"type":41,"value":242}," Input\u002Foutput shape + representative terms, so varied queries surface it.",{"type":35,"tag":80,"props":244,"children":245},{},[246,251,253,259],{"type":35,"tag":84,"props":247,"children":248},{},[249],{"type":41,"value":250},"Split when input contracts genuinely differ",{"type":41,"value":252}," (binary vs JSON, sync vs async, divergent auth schemes). Don't fit divergent contracts under one trigger via passthrough + internal branching. See ",{"type":35,"tag":50,"props":254,"children":256},{"className":255},[],[257],{"type":41,"value":258},"references\u002FSUBWORKFLOW_PATTERNS.md",{"type":41,"value":260}," \"Splitting by input shape\".",{"type":35,"tag":69,"props":262,"children":264},{"id":263},"decision-tree-should-this-be-a-sub-workflow",[265],{"type":41,"value":266},"Decision tree: should this be a sub-workflow?",{"type":35,"tag":268,"props":269,"children":273},"pre",{"className":270,"code":272,"language":41},[271],"language-text","About to write a chunk of logic?\n├── Could this plausibly be needed in another workflow?\n│   ├── Yes → extract to sub-workflow\n│   └── No → keep inline\n│\n├── Is this chunk >5 nodes and conceptually one thing?\n│   └── Extract if you want testability, isolation, or reuse; if it's only for a cleaner canvas, group it inline instead (see below).\n│\n├── Is this chunk dealing with a generic concern (auth, retry, parsing, formatting)?\n│   └── Almost certainly extract. These are the canonical reusable sub-workflows.\n│\n└── Is this chunk doing one HTTP call with no logic around it?\n    └── Don't extract. Extra workflow boundary for nothing.\n",[274],{"type":35,"tag":50,"props":275,"children":277},{"__ignoreMap":276},"",[278],{"type":41,"value":272},{"type":35,"tag":44,"props":280,"children":281},{},[282,284,289,291,297],{"type":41,"value":283},"When the only motivation is a cleaner canvas (not reuse, isolation, testability, or an agent tool), a ",{"type":35,"tag":84,"props":285,"children":286},{},[287],{"type":41,"value":288},"canvas node group",{"type":41,"value":290}," is the tool for readability-only sectioning: faster (no sub-execution per call) and simpler (no input\u002Foutput contract), with the logic staying inline. Group it where the section forms a valid group (connected, single entry\u002Fexit); split a too-branchy section into smaller groups or leave it ungrouped (a sticky note annotates, it doesn't group). Extract to a sub-workflow only when you genuinely need reuse, isolation, independent testing, or an agent tool. See ",{"type":35,"tag":50,"props":292,"children":294},{"className":293},[],[295],{"type":41,"value":296},"n8n-workflow-lifecycle-official",{"type":41,"value":298}," Readability.",{"type":35,"tag":69,"props":300,"children":302},{"id":301},"stateless-vs-stateful-sub-workflows",[303],{"type":41,"value":304},"Stateless vs. stateful sub-workflows",{"type":35,"tag":44,"props":306,"children":307},{},[308],{"type":41,"value":309},"Both are first-class. The choice is about intent and encapsulation.",{"type":35,"tag":311,"props":312,"children":314},"h3",{"id":313},"stateless",[315],{"type":41,"value":316},"Stateless",{"type":35,"tag":44,"props":318,"children":319},{},[320],{"type":41,"value":321},"Takes input, returns output. No I\u002FO outside the inputs\u002Foutputs. Default for pure logic.",{"type":35,"tag":44,"props":323,"children":324},{},[325],{"type":41,"value":326},"Examples:",{"type":35,"tag":137,"props":328,"children":329},{},[330,347,364],{"type":35,"tag":80,"props":331,"children":332},{},[333,338,340,345],{"type":35,"tag":50,"props":334,"children":336},{"className":335},[],[337],{"type":41,"value":222},{"type":41,"value":339}," (tag ",{"type":35,"tag":50,"props":341,"children":343},{"className":342},[],[344],{"type":41,"value":190},{"type":41,"value":346},"). Input: date string. Output: ISO date or error.",{"type":35,"tag":80,"props":348,"children":349},{},[350,356,357,362],{"type":35,"tag":50,"props":351,"children":353},{"className":352},[],[354],{"type":41,"value":355},"Compute MRR from subscription",{"type":41,"value":339},{"type":35,"tag":50,"props":358,"children":360},{"className":359},[],[361],{"type":41,"value":190},{"type":41,"value":363},"). Input: subscription object. Output: MRR number.",{"type":35,"tag":80,"props":365,"children":366},{},[367,373,374,379],{"type":35,"tag":50,"props":368,"children":370},{"className":369},[],[371],{"type":41,"value":372},"Format invoice as HTML",{"type":41,"value":339},{"type":35,"tag":50,"props":375,"children":377},{"className":376},[],[378],{"type":41,"value":190},{"type":41,"value":380},"). Input: invoice data. Output: HTML string.",{"type":35,"tag":44,"props":382,"children":383},{},[384],{"type":41,"value":385},"When you need the logic again, call it without worrying about side effects firing.",{"type":35,"tag":311,"props":387,"children":389},{"id":388},"stateful-deliberate",[390],{"type":41,"value":391},"Stateful (deliberate)",{"type":35,"tag":44,"props":393,"children":394},{},[395],{"type":41,"value":396},"Reads or writes external state behind a clean input\u002Foutput contract. Comparable to a repository pattern: the sub-workflow abstracts the state operation so callers think in domain terms, not implementation.",{"type":35,"tag":44,"props":398,"children":399},{},[400],{"type":41,"value":326},{"type":35,"tag":137,"props":402,"children":403},{},[404,429,463,489],{"type":35,"tag":80,"props":405,"children":406},{},[407,413,414,419,421,427],{"type":35,"tag":50,"props":408,"children":410},{"className":409},[],[411],{"type":41,"value":412},"Get customer by id",{"type":41,"value":339},{"type":35,"tag":50,"props":415,"children":417},{"className":416},[],[418],{"type":41,"value":198},{"type":41,"value":420},"). Input: id. Output: customer object or ",{"type":35,"tag":50,"props":422,"children":424},{"className":423},[],[425],{"type":41,"value":426},"{ ok: false, error: 'not_found' }",{"type":41,"value":428},". Reads the DB.",{"type":35,"tag":80,"props":430,"children":431},{},[432,438,440,445,447,453,455,461],{"type":35,"tag":50,"props":433,"children":435},{"className":434},[],[436],{"type":41,"value":437},"Write customer billing record",{"type":41,"value":439}," (tags ",{"type":35,"tag":50,"props":441,"children":443},{"className":442},[],[444],{"type":41,"value":198},{"type":41,"value":446},", ",{"type":35,"tag":50,"props":448,"children":450},{"className":449},[],[451],{"type":41,"value":452},"billing",{"type":41,"value":454},"). Input: record. Output: ",{"type":35,"tag":50,"props":456,"children":458},{"className":457},[],[459],{"type":41,"value":460},"{ ok: true, id }",{"type":41,"value":462},". Writes the DB.",{"type":35,"tag":80,"props":464,"children":465},{},[466,472,473,479,481,487],{"type":35,"tag":50,"props":467,"children":469},{"className":468},[],[470],{"type":41,"value":471},"Append audit event",{"type":41,"value":339},{"type":35,"tag":50,"props":474,"children":476},{"className":475},[],[477],{"type":41,"value":478},"audit",{"type":41,"value":480},"). Input: event. Output: ",{"type":35,"tag":50,"props":482,"children":484},{"className":483},[],[485],{"type":41,"value":486},"{ ok: true, eventId }",{"type":41,"value":488},". Writes to a logging store.",{"type":35,"tag":80,"props":490,"children":491},{},[492,498,499,505,507,513],{"type":35,"tag":50,"props":493,"children":495},{"className":494},[],[496],{"type":41,"value":497},"Send to on-call",{"type":41,"value":339},{"type":35,"tag":50,"props":500,"children":502},{"className":501},[],[503],{"type":41,"value":504},"notification",{"type":41,"value":506},"). Input: channel, message. Output: ",{"type":35,"tag":50,"props":508,"children":510},{"className":509},[],[511],{"type":41,"value":512},"{ ok: true, messageId }",{"type":41,"value":514},". Calls Slack\u002FSMTP.",{"type":35,"tag":44,"props":516,"children":517},{},[518],{"type":41,"value":519},"The point of building these as sub-workflows:",{"type":35,"tag":137,"props":521,"children":522},{},[523,544,549],{"type":35,"tag":80,"props":524,"children":525},{},[526,528,534,536,542],{"type":41,"value":527},"Callers think in domain terms (",{"type":35,"tag":50,"props":529,"children":531},{"className":530},[],[532],{"type":41,"value":533},"get customer by id",{"type":41,"value":535},"), not in storage (",{"type":35,"tag":50,"props":537,"children":539},{"className":538},[],[540],{"type":41,"value":541},"SELECT * FROM customers ...",{"type":41,"value":543},").",{"type":35,"tag":80,"props":545,"children":546},{},[547],{"type":41,"value":548},"Swap the underlying store\u002FAPI behind it (Postgres → Supabase, native node → HTTP) without touching callers.",{"type":35,"tag":80,"props":550,"children":551},{},[552],{"type":41,"value":553},"Idempotency, retry, and validation become the sub-workflow's responsibility, centralized in one place.",{"type":35,"tag":44,"props":555,"children":556},{},[557,559,563],{"type":41,"value":558},"What to avoid is ",{"type":35,"tag":161,"props":560,"children":561},{},[562],{"type":41,"value":172},{"type":41,"value":564}," state: a sub-workflow named\u002Fdescribed as pure that quietly writes to a log table. That ambushes callers who reasonably assumed it was safe to retry or compose. Either make the side effect part of the contract (rename, document, return its result) or move it out.",{"type":35,"tag":69,"props":566,"children":568},{"id":567},"when-to-extract",[569],{"type":41,"value":570},"When to extract",{"type":35,"tag":44,"props":572,"children":573},{},[574],{"type":41,"value":575},"The two main signals:",{"type":35,"tag":311,"props":577,"children":579},{"id":578},"_1-conceptual-coherence",[580],{"type":41,"value":581},"1. Conceptual coherence",{"type":35,"tag":44,"props":583,"children":584},{},[585],{"type":41,"value":586},"When a chunk of nodes does one logical thing, even unreused, extraction can be worth it for:",{"type":35,"tag":137,"props":588,"children":589},{},[590,600],{"type":35,"tag":80,"props":591,"children":592},{},[593,598],{"type":35,"tag":84,"props":594,"children":595},{},[596],{"type":41,"value":597},"Testability.",{"type":41,"value":599}," Run the sub-workflow on its own with pinned data.",{"type":35,"tag":80,"props":601,"children":602},{},[603,608],{"type":35,"tag":84,"props":604,"children":605},{},[606],{"type":41,"value":607},"Replaceability.",{"type":41,"value":609}," Swapping implementations doesn't ripple to callers.",{"type":35,"tag":44,"props":611,"children":612},{},[613],{"type":41,"value":614},"Readability alone is not a reason to extract: a node group collapses five nodes into one labeled box more cheaply (no sub-execution, no input\u002Foutput contract). Extract when you also want testability, replaceability, or reuse.",{"type":35,"tag":311,"props":616,"children":618},{"id":617},"_15-the-fire-and-forget-audit-log-pattern",[619],{"type":41,"value":620},"1.5 The fire-and-forget audit-log pattern",{"type":35,"tag":622,"props":623,"children":624},"blockquote",{},[625],{"type":35,"tag":44,"props":626,"children":627},{},[628,630,635],{"type":41,"value":629},"Audit logging is used here as a concrete illustration of the fire-and-forget stateful pattern. ",{"type":35,"tag":84,"props":631,"children":632},{},[633],{"type":41,"value":634},"Don't add audit logging to a workflow unless the user asked for it.",{"type":41,"value":636}," The pattern itself (fire a sub-workflow async, don't block on it) generalizes to any side observation: metrics, notifications, etc.",{"type":35,"tag":44,"props":638,"children":639},{},[640,642,648,650,656],{"type":41,"value":641},"A deliberately stateful audit-log sub-workflow invoked with ",{"type":35,"tag":50,"props":643,"children":645},{"className":644},[],[646],{"type":41,"value":647},"Execute Workflow",{"type":41,"value":649},"'s ",{"type":35,"tag":50,"props":651,"children":653},{"className":652},[],[654],{"type":41,"value":655},"waitForSubWorkflow: false",{"type":41,"value":657}," so the caller doesn't block on the write.",{"type":35,"tag":268,"props":659,"children":662},{"className":660,"code":661,"language":41},[271],"Caller ──→ [Execute Workflow: DB audit log]\n              { title: 'Email Confirmation Received',\n                description: \u003Cserialized data> }\n              waitForSubWorkflow: false\n              ↓ (caller continues immediately)\n        ──→ [Continue with next step]\n",[663],{"type":35,"tag":50,"props":664,"children":665},{"__ignoreMap":276},[666],{"type":41,"value":661},{"type":35,"tag":44,"props":668,"children":669},{},[670],{"type":41,"value":671},"The sub-workflow takes a title and description, writes to a logging table (or Slack, or both), returns. The caller doesn't wait. Audit log is a side observation, not the critical path.",{"type":35,"tag":44,"props":673,"children":674},{},[675],{"type":41,"value":676},"When the user has asked for it, fire one at every meaningful state transition (\"email confirmation received\", \"user verified\", \"processing started\", \"eligibility decision made\") so the timeline reconstructs from logs.",{"type":35,"tag":44,"props":678,"children":679},{},[680],{"type":41,"value":681},"Why it's valuable:",{"type":35,"tag":137,"props":683,"children":684},{},[685,695,705],{"type":35,"tag":80,"props":686,"children":687},{},[688,693],{"type":35,"tag":84,"props":689,"children":690},{},[691],{"type":41,"value":692},"Observability for free.",{"type":41,"value":694}," Per-execution timeline when something goes wrong.",{"type":35,"tag":80,"props":696,"children":697},{},[698,703],{"type":35,"tag":84,"props":699,"children":700},{},[701],{"type":41,"value":702},"No coupling.",{"type":41,"value":704}," Implementation (DB, Slack, both) can change without touching callers.",{"type":35,"tag":80,"props":706,"children":707},{},[708,713,715,720],{"type":35,"tag":84,"props":709,"children":710},{},[711],{"type":41,"value":712},"Async by default.",{"type":41,"value":714}," ",{"type":35,"tag":50,"props":716,"children":718},{"className":717},[],[719],{"type":41,"value":655},{"type":41,"value":721}," means the audit doesn't slow the main workflow.",{"type":35,"tag":44,"props":723,"children":724},{},[725],{"type":41,"value":726},"The audit-log workflow is the right kind of stateful sub-workflow. The side effect is the point.",{"type":35,"tag":311,"props":728,"children":730},{"id":729},"_17-the-middleware-pattern",[731],{"type":41,"value":732},"1.7 The middleware pattern",{"type":35,"tag":44,"props":734,"children":735},{},[736],{"type":41,"value":737},"When a webhook workflow is API-shaped, treat it like one. Sub-workflows become middleware: small stateless functions that run before the main handler and either pass through or short-circuit with a 4xx.",{"type":35,"tag":268,"props":739,"children":742},{"className":740,"code":741,"language":41},[271],"Webhook\n  → [Verify JWT]    # decode + validate; 401 on failure\n  → [Rate limit]    # check + bump counter; 429 on failure\n  → IF (all middleware ok)\n    → Main handler logic\n    → Respond 200\n  → ELSE → Respond with the 4xx the middleware returned\n",[743],{"type":35,"tag":50,"props":744,"children":745},{"__ignoreMap":276},[746],{"type":41,"value":741},{"type":35,"tag":44,"props":748,"children":749},{},[750,752,758,759,764,766,772,774,780,782,788,790,796],{"type":41,"value":751},"Canonical example: custom JWT auth rolled inside n8n. ",{"type":35,"tag":50,"props":753,"children":755},{"className":754},[],[756],{"type":41,"value":757},"Verify JWT",{"type":41,"value":339},{"type":35,"tag":50,"props":760,"children":762},{"className":761},[],[763],{"type":41,"value":190},{"type":41,"value":765},") takes the raw ",{"type":35,"tag":50,"props":767,"children":769},{"className":768},[],[770],{"type":41,"value":771},"Authorization",{"type":41,"value":773}," header, decodes, validates signature and expiry, returns ",{"type":35,"tag":50,"props":775,"children":777},{"className":776},[],[778],{"type":41,"value":779},"{ ok: true, user_id }",{"type":41,"value":781}," or ",{"type":35,"tag":50,"props":783,"children":785},{"className":784},[],[786],{"type":41,"value":787},"{ ok: false, status: 401, message }",{"type":41,"value":789},". The caller IFs on ",{"type":35,"tag":50,"props":791,"children":793},{"className":792},[],[794],{"type":41,"value":795},"ok",{"type":41,"value":797},", responds early on failure, continues on success.",{"type":35,"tag":44,"props":799,"children":800},{},[801],{"type":41,"value":802},"Why a sub-workflow and not inline: every webhook that needs auth calls the same one. Swap the library, rotate the signing key, or add refresh-token logic in a single place. The reuse target is exact, the contract is small, and the failure response shape is consistent across every API endpoint.",{"type":35,"tag":44,"props":804,"children":805},{},[806,808,814,816,822],{"type":41,"value":807},"Pairs with ",{"type":35,"tag":50,"props":809,"children":811},{"className":810},[],[812],{"type":41,"value":813},"n8n-error-handling-official",{"type":41,"value":815}," for 4xx\u002F5xx response shapes and ",{"type":35,"tag":50,"props":817,"children":819},{"className":818},[],[820],{"type":41,"value":821},"n8n-credentials-and-security-official",{"type":41,"value":823}," for the underlying secret handling.",{"type":35,"tag":311,"props":825,"children":827},{"id":826},"_2-repetition-pattern",[828],{"type":41,"value":829},"2. Repetition pattern",{"type":35,"tag":44,"props":831,"children":832},{},[833],{"type":41,"value":834},"You're about to build something you've built before. Stop. Search.",{"type":35,"tag":268,"props":836,"children":839},{"className":837,"code":838,"language":41},[271],"search_workflows({ query: 'date' })\nsearch_workflows({ tags: ['customer'] })\nsearch_workflows({ tags: ['subworkflow'] })\n",[840],{"type":35,"tag":50,"props":841,"children":842},{"__ignoreMap":276},[843],{"type":41,"value":838},{"type":35,"tag":44,"props":845,"children":846},{},[847,849,854,856,861,863,868],{"type":41,"value":848},"If something matches, use it. If not, build it as a sub-workflow and tag it so the ",{"type":35,"tag":161,"props":850,"children":851},{},[852],{"type":41,"value":853},"next",{"type":41,"value":855}," search finds it. The tag convention (",{"type":35,"tag":50,"props":857,"children":859},{"className":858},[],[860],{"type":41,"value":190},{"type":41,"value":862},", domain tags, ",{"type":35,"tag":50,"props":864,"children":866},{"className":865},[],[867],{"type":41,"value":206},{"type":41,"value":869},") is what makes that work.",{"type":35,"tag":69,"props":871,"children":873},{"id":872},"linear-long-workflows-are-fine-when-most-of-the-work-is-in-sub-workflows",[874],{"type":41,"value":875},"Linear, long workflows are fine when most of the work is in sub-workflows",{"type":35,"tag":44,"props":877,"children":878},{},[879],{"type":41,"value":880},"A workflow can have 20+ nodes and still be readable if it's mostly a linear orchestration of sub-workflow calls and decisions. The shape (audit-log nodes shown only because they're a vivid example of \"side observation between real steps\", include them only if the user asked for audit logging):",{"type":35,"tag":268,"props":882,"children":885},{"className":883,"code":884,"language":41},[271],"Webhook\n  → Audit log (sub-workflow)\n  → Validate\n  → Audit log (sub-workflow)\n  → IF auth ok\n    → Look up user (or sub-workflow)\n    → Audit log (sub-workflow)\n    → Process step 1 (sub-workflow)\n    → Audit log (sub-workflow)\n    → Process step 2 (sub-workflow)\n    → Audit log (sub-workflow)\n    → Decide eligibility (sub-workflow)\n    → Audit log (sub-workflow)\n    → Send notification (sub-workflow)\n    → Respond\n",[886],{"type":35,"tag":50,"props":887,"children":888},{"__ignoreMap":276},[889],{"type":41,"value":884},{"type":35,"tag":44,"props":891,"children":892},{},[893],{"type":41,"value":894},"Each \"logical step\" is a sub-workflow call. The caller is a long but linear narrative, easy to follow top-to-bottom. Logic lives in the sub-workflows.",{"type":35,"tag":44,"props":896,"children":897},{},[898,900,905],{"type":41,"value":899},"This is ",{"type":35,"tag":161,"props":901,"children":902},{},[903],{"type":41,"value":904},"not",{"type":41,"value":906}," the same as a 20-node workflow with 20 inline transformations. That's hard to read. The pattern above is fine because:",{"type":35,"tag":137,"props":908,"children":909},{},[910,915,927,932],{"type":35,"tag":80,"props":911,"children":912},{},[913],{"type":41,"value":914},"Each node has one purpose (call a specific sub-workflow).",{"type":35,"tag":80,"props":916,"children":917},{},[918,920,925],{"type":41,"value":919},"Node groups mark sections, sticky notes annotate them (per ",{"type":35,"tag":50,"props":921,"children":923},{"className":922},[],[924],{"type":41,"value":296},{"type":41,"value":926}," \"Readability\").",{"type":35,"tag":80,"props":928,"children":929},{},[930],{"type":41,"value":931},"Inspecting a section means opening the sub-workflow it calls. That's encapsulation.",{"type":35,"tag":80,"props":933,"children":934},{},[935],{"type":41,"value":936},"Orchestration logic at the top level is visible without reading implementations.",{"type":35,"tag":44,"props":938,"children":939},{},[940],{"type":41,"value":941},"If your workflow has 15+ nodes and isn't mostly Execute Workflow calls and branches, extract more where reuse or testing warrants it, and group the rest inline (node groups).",{"type":35,"tag":69,"props":943,"children":945},{"id":944},"when-not-to-extract",[946],{"type":41,"value":947},"When NOT to extract",{"type":35,"tag":137,"props":949,"children":950},{},[951,969,979],{"type":35,"tag":80,"props":952,"children":953},{},[954,959,961,967],{"type":35,"tag":84,"props":955,"children":956},{},[957],{"type":41,"value":958},"One HTTP call with no logic.",{"type":41,"value":960}," A sub-workflow that's just ",{"type":35,"tag":50,"props":962,"children":964},{"className":963},[],[965],{"type":41,"value":966},"Execute Workflow → HTTP Request → return",{"type":41,"value":968}," adds a boundary for nothing. Inline it.",{"type":35,"tag":80,"props":970,"children":971},{},[972,977],{"type":35,"tag":84,"props":973,"children":974},{},[975],{"type":41,"value":976},"Tightly coupled to the caller's specific shape.",{"type":41,"value":978}," If the chunk takes a deeply nested input that only this caller produces, extracting it just relocates the coupling. Fix the data shape first.",{"type":35,"tag":80,"props":980,"children":981},{},[982,987],{"type":35,"tag":84,"props":983,"children":984},{},[985],{"type":41,"value":986},"Performance-critical hot paths.",{"type":41,"value":988}," Each sub-workflow call adds latency (small, but real). For high-throughput workflows, profile before adding boundaries.",{"type":35,"tag":69,"props":990,"children":992},{"id":991},"search-before-build-protocol",[993],{"type":41,"value":994},"Search-before-build protocol",{"type":35,"tag":44,"props":996,"children":997},{},[998],{"type":41,"value":999},"When the user describes something multi-step or generic-sounding:",{"type":35,"tag":268,"props":1001,"children":1004},{"className":1002,"code":1003,"language":41},[271],"1. search_workflows with relevant tags and\u002For query (e.g. `tags: ['subworkflow']`, a domain tag, the operation keyword)\n2. If candidates appear, fetch get_workflow_details on the top 1-3\n3. Confirm fit by reading the inputs\u002Foutputs and (briefly) the body\n4. If a fit exists → use it. Tell the user \"I found `\u003Cname>`. Using that.\"\n5. If no fit exists → build new and tag it (`subworkflow`, domain, `tool`) so the next search finds it\n",[1005],{"type":35,"tag":50,"props":1006,"children":1007},{"__ignoreMap":276},[1008],{"type":41,"value":1003},{"type":35,"tag":44,"props":1010,"children":1011},{},[1012],{"type":41,"value":1013},"The \"tell the user\" step matters. They benefit from knowing what's already in their library.",{"type":35,"tag":44,"props":1015,"children":1016},{},[1017,1019,1024,1025,1031],{"type":41,"value":1018},"If a workflow you expect to find isn't appearing, the most common cause is per-workflow MCP access not being enabled. See ",{"type":35,"tag":50,"props":1020,"children":1022},{"className":1021},[],[1023],{"type":41,"value":296},{"type":41,"value":714},{"type":35,"tag":50,"props":1026,"children":1028},{"className":1027},[],[1029],{"type":41,"value":1030},"references\u002FMCP_ACCESS_PER_WORKFLOW.md",{"type":41,"value":232},{"type":35,"tag":69,"props":1033,"children":1035},{"id":1034},"sub-workflow-inputs-and-outputs",[1036],{"type":41,"value":1037},"Sub-workflow inputs and outputs",{"type":35,"tag":44,"props":1039,"children":1040},{},[1041,1043,1048,1050,1055],{"type":41,"value":1042},"Sub-workflows are triggered by ",{"type":35,"tag":50,"props":1044,"children":1046},{"className":1045},[],[1047],{"type":41,"value":55},{"type":41,"value":1049}," nodes. The trigger declares the input schema. The caller passes data via ",{"type":35,"tag":50,"props":1051,"children":1053},{"className":1052},[],[1054],{"type":41,"value":647},{"type":41,"value":1056},", and the sub-workflow returns whatever its last node outputs.",{"type":35,"tag":311,"props":1058,"children":1060},{"id":1059},"always-use-define-below-with-explicit-fields",[1061],{"type":41,"value":1062},"Always use \"Define Below\" with explicit fields",{"type":35,"tag":44,"props":1064,"children":1065},{},[1066,1068,1073,1075,1080,1082,1088,1090,1096],{"type":41,"value":1067},"The ",{"type":35,"tag":50,"props":1069,"children":1071},{"className":1070},[],[1072],{"type":41,"value":55},{"type":41,"value":1074}," has two input modes. ",{"type":35,"tag":84,"props":1076,"children":1077},{},[1078],{"type":41,"value":1079},"Default to \"Define Below\" (typed fields).",{"type":41,"value":1081}," This is the only mode that lets agent tools (via ",{"type":35,"tag":50,"props":1083,"children":1085},{"className":1084},[],[1086],{"type":41,"value":1087},"fromAi()",{"type":41,"value":1089},") and any structured caller pass values in. Without declared fields, the agent has no schema to fill and the sub-workflow can't be wired as a ",{"type":35,"tag":50,"props":1091,"children":1093},{"className":1092},[],[1094],{"type":41,"value":1095},"toolWorkflow",{"type":41,"value":1097}," cleanly.",{"type":35,"tag":44,"props":1099,"children":1100},{},[1101],{"type":41,"value":1102},"Shape:",{"type":35,"tag":268,"props":1104,"children":1108},{"className":1105,"code":1106,"language":1107,"meta":276,"style":276},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const subTrigger = trigger({\n    type: 'n8n-nodes-base.executeWorkflowTrigger',\n    config: {\n        parameters: {\n            workflowInputs: {\n                values: [\n                    { name: 'list_of_ids', type: 'array' },\n                    { name: 'include_transcript', type: 'boolean' },\n                    { name: 'session_id', type: 'string' },\n                ],\n            },\n        },\n    },\n})\n","ts",[1109],{"type":35,"tag":50,"props":1110,"children":1111},{"__ignoreMap":276},[1112,1152,1188,1206,1223,1240,1258,1321,1379,1437,1450,1459,1468,1477],{"type":35,"tag":1113,"props":1114,"children":1117},"span",{"class":1115,"line":1116},"line",1,[1118,1124,1130,1136,1142,1147],{"type":35,"tag":1113,"props":1119,"children":1121},{"style":1120},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[1122],{"type":41,"value":1123},"const",{"type":35,"tag":1113,"props":1125,"children":1127},{"style":1126},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[1128],{"type":41,"value":1129}," subTrigger ",{"type":35,"tag":1113,"props":1131,"children":1133},{"style":1132},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[1134],{"type":41,"value":1135},"=",{"type":35,"tag":1113,"props":1137,"children":1139},{"style":1138},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[1140],{"type":41,"value":1141}," trigger",{"type":35,"tag":1113,"props":1143,"children":1144},{"style":1126},[1145],{"type":41,"value":1146},"(",{"type":35,"tag":1113,"props":1148,"children":1149},{"style":1132},[1150],{"type":41,"value":1151},"{\n",{"type":35,"tag":1113,"props":1153,"children":1155},{"class":1115,"line":1154},2,[1156,1162,1167,1172,1178,1183],{"type":35,"tag":1113,"props":1157,"children":1159},{"style":1158},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[1160],{"type":41,"value":1161},"    type",{"type":35,"tag":1113,"props":1163,"children":1164},{"style":1132},[1165],{"type":41,"value":1166},":",{"type":35,"tag":1113,"props":1168,"children":1169},{"style":1132},[1170],{"type":41,"value":1171}," '",{"type":35,"tag":1113,"props":1173,"children":1175},{"style":1174},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1176],{"type":41,"value":1177},"n8n-nodes-base.executeWorkflowTrigger",{"type":35,"tag":1113,"props":1179,"children":1180},{"style":1132},[1181],{"type":41,"value":1182},"'",{"type":35,"tag":1113,"props":1184,"children":1185},{"style":1132},[1186],{"type":41,"value":1187},",\n",{"type":35,"tag":1113,"props":1189,"children":1191},{"class":1115,"line":1190},3,[1192,1197,1201],{"type":35,"tag":1113,"props":1193,"children":1194},{"style":1158},[1195],{"type":41,"value":1196},"    config",{"type":35,"tag":1113,"props":1198,"children":1199},{"style":1132},[1200],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1202,"children":1203},{"style":1132},[1204],{"type":41,"value":1205}," {\n",{"type":35,"tag":1113,"props":1207,"children":1209},{"class":1115,"line":1208},4,[1210,1215,1219],{"type":35,"tag":1113,"props":1211,"children":1212},{"style":1158},[1213],{"type":41,"value":1214},"        parameters",{"type":35,"tag":1113,"props":1216,"children":1217},{"style":1132},[1218],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1220,"children":1221},{"style":1132},[1222],{"type":41,"value":1205},{"type":35,"tag":1113,"props":1224,"children":1226},{"class":1115,"line":1225},5,[1227,1232,1236],{"type":35,"tag":1113,"props":1228,"children":1229},{"style":1158},[1230],{"type":41,"value":1231},"            workflowInputs",{"type":35,"tag":1113,"props":1233,"children":1234},{"style":1132},[1235],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1237,"children":1238},{"style":1132},[1239],{"type":41,"value":1205},{"type":35,"tag":1113,"props":1241,"children":1243},{"class":1115,"line":1242},6,[1244,1249,1253],{"type":35,"tag":1113,"props":1245,"children":1246},{"style":1158},[1247],{"type":41,"value":1248},"                values",{"type":35,"tag":1113,"props":1250,"children":1251},{"style":1132},[1252],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1254,"children":1255},{"style":1126},[1256],{"type":41,"value":1257}," [\n",{"type":35,"tag":1113,"props":1259,"children":1261},{"class":1115,"line":1260},7,[1262,1267,1272,1276,1280,1285,1289,1294,1299,1303,1307,1312,1316],{"type":35,"tag":1113,"props":1263,"children":1264},{"style":1132},[1265],{"type":41,"value":1266},"                    {",{"type":35,"tag":1113,"props":1268,"children":1269},{"style":1158},[1270],{"type":41,"value":1271}," name",{"type":35,"tag":1113,"props":1273,"children":1274},{"style":1132},[1275],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1277,"children":1278},{"style":1132},[1279],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1281,"children":1282},{"style":1174},[1283],{"type":41,"value":1284},"list_of_ids",{"type":35,"tag":1113,"props":1286,"children":1287},{"style":1132},[1288],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1290,"children":1291},{"style":1132},[1292],{"type":41,"value":1293},",",{"type":35,"tag":1113,"props":1295,"children":1296},{"style":1158},[1297],{"type":41,"value":1298}," type",{"type":35,"tag":1113,"props":1300,"children":1301},{"style":1132},[1302],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1304,"children":1305},{"style":1132},[1306],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1308,"children":1309},{"style":1174},[1310],{"type":41,"value":1311},"array",{"type":35,"tag":1113,"props":1313,"children":1314},{"style":1132},[1315],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1317,"children":1318},{"style":1132},[1319],{"type":41,"value":1320}," },\n",{"type":35,"tag":1113,"props":1322,"children":1324},{"class":1115,"line":1323},8,[1325,1329,1333,1337,1341,1346,1350,1354,1358,1362,1366,1371,1375],{"type":35,"tag":1113,"props":1326,"children":1327},{"style":1132},[1328],{"type":41,"value":1266},{"type":35,"tag":1113,"props":1330,"children":1331},{"style":1158},[1332],{"type":41,"value":1271},{"type":35,"tag":1113,"props":1334,"children":1335},{"style":1132},[1336],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1338,"children":1339},{"style":1132},[1340],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1342,"children":1343},{"style":1174},[1344],{"type":41,"value":1345},"include_transcript",{"type":35,"tag":1113,"props":1347,"children":1348},{"style":1132},[1349],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1351,"children":1352},{"style":1132},[1353],{"type":41,"value":1293},{"type":35,"tag":1113,"props":1355,"children":1356},{"style":1158},[1357],{"type":41,"value":1298},{"type":35,"tag":1113,"props":1359,"children":1360},{"style":1132},[1361],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1363,"children":1364},{"style":1132},[1365],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1367,"children":1368},{"style":1174},[1369],{"type":41,"value":1370},"boolean",{"type":35,"tag":1113,"props":1372,"children":1373},{"style":1132},[1374],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1376,"children":1377},{"style":1132},[1378],{"type":41,"value":1320},{"type":35,"tag":1113,"props":1380,"children":1382},{"class":1115,"line":1381},9,[1383,1387,1391,1395,1399,1404,1408,1412,1416,1420,1424,1429,1433],{"type":35,"tag":1113,"props":1384,"children":1385},{"style":1132},[1386],{"type":41,"value":1266},{"type":35,"tag":1113,"props":1388,"children":1389},{"style":1158},[1390],{"type":41,"value":1271},{"type":35,"tag":1113,"props":1392,"children":1393},{"style":1132},[1394],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1396,"children":1397},{"style":1132},[1398],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1400,"children":1401},{"style":1174},[1402],{"type":41,"value":1403},"session_id",{"type":35,"tag":1113,"props":1405,"children":1406},{"style":1132},[1407],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1409,"children":1410},{"style":1132},[1411],{"type":41,"value":1293},{"type":35,"tag":1113,"props":1413,"children":1414},{"style":1158},[1415],{"type":41,"value":1298},{"type":35,"tag":1113,"props":1417,"children":1418},{"style":1132},[1419],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1421,"children":1422},{"style":1132},[1423],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1425,"children":1426},{"style":1174},[1427],{"type":41,"value":1428},"string",{"type":35,"tag":1113,"props":1430,"children":1431},{"style":1132},[1432],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1434,"children":1435},{"style":1132},[1436],{"type":41,"value":1320},{"type":35,"tag":1113,"props":1438,"children":1440},{"class":1115,"line":1439},10,[1441,1446],{"type":35,"tag":1113,"props":1442,"children":1443},{"style":1126},[1444],{"type":41,"value":1445},"                ]",{"type":35,"tag":1113,"props":1447,"children":1448},{"style":1132},[1449],{"type":41,"value":1187},{"type":35,"tag":1113,"props":1451,"children":1453},{"class":1115,"line":1452},11,[1454],{"type":35,"tag":1113,"props":1455,"children":1456},{"style":1132},[1457],{"type":41,"value":1458},"            },\n",{"type":35,"tag":1113,"props":1460,"children":1462},{"class":1115,"line":1461},12,[1463],{"type":35,"tag":1113,"props":1464,"children":1465},{"style":1132},[1466],{"type":41,"value":1467},"        },\n",{"type":35,"tag":1113,"props":1469,"children":1471},{"class":1115,"line":1470},13,[1472],{"type":35,"tag":1113,"props":1473,"children":1474},{"style":1132},[1475],{"type":41,"value":1476},"    },\n",{"type":35,"tag":1113,"props":1478,"children":1480},{"class":1115,"line":1479},14,[1481,1486],{"type":35,"tag":1113,"props":1482,"children":1483},{"style":1132},[1484],{"type":41,"value":1485},"}",{"type":35,"tag":1113,"props":1487,"children":1488},{"style":1126},[1489],{"type":41,"value":1490},")\n",{"type":35,"tag":44,"props":1492,"children":1493},{},[1494,1496,1502,1504,1510],{"type":41,"value":1495},"Each declared input becomes a typed parameter the caller can fill. Inside the workflow, access via ",{"type":35,"tag":50,"props":1497,"children":1499},{"className":1498},[],[1500],{"type":41,"value":1501},"$json.list_of_ids",{"type":41,"value":1503},", etc., or ",{"type":35,"tag":50,"props":1505,"children":1507},{"className":1506},[],[1508],{"type":41,"value":1509},"$('When Executed by Another Workflow').first().json.\u003Cfield>",{"type":41,"value":1511}," from anywhere downstream.",{"type":35,"tag":44,"props":1513,"children":1514},{},[1515,1517,1522,1523,1529,1530,1535,1536,1541,1542,1548],{"type":41,"value":1516},"Pick types deliberately (",{"type":35,"tag":50,"props":1518,"children":1520},{"className":1519},[],[1521],{"type":41,"value":1428},{"type":41,"value":446},{"type":35,"tag":50,"props":1524,"children":1526},{"className":1525},[],[1527],{"type":41,"value":1528},"number",{"type":41,"value":446},{"type":35,"tag":50,"props":1531,"children":1533},{"className":1532},[],[1534],{"type":41,"value":1370},{"type":41,"value":446},{"type":35,"tag":50,"props":1537,"children":1539},{"className":1538},[],[1540],{"type":41,"value":1311},{"type":41,"value":446},{"type":35,"tag":50,"props":1543,"children":1545},{"className":1544},[],[1546],{"type":41,"value":1547},"object",{"type":41,"value":1549},"). The model uses these as the required types when filling agent tool parameters, and humans rely on them when wiring callers.",{"type":35,"tag":311,"props":1551,"children":1553},{"id":1552},"exception-1-passthrough-mode-for-binary",[1554],{"type":41,"value":1555},"Exception 1: passthrough mode for binary",{"type":35,"tag":44,"props":1557,"children":1558},{},[1559,1561,1567],{"type":41,"value":1560},"If the sub-workflow needs to receive binary (image, file, PDF), ",{"type":35,"tag":50,"props":1562,"children":1564},{"className":1563},[],[1565],{"type":41,"value":1566},"Define Below",{"type":41,"value":1568}," doesn't work because typed fields are JSON only. Switch to passthrough:",{"type":35,"tag":268,"props":1570,"children":1572},{"className":1105,"code":1571,"language":1107,"meta":276,"style":276},"const subTrigger = trigger({\n    type: 'n8n-nodes-base.executeWorkflowTrigger',\n    config: {\n        parameters: {\n            inputSource: 'passthrough',\n        },\n    },\n})\n",[1573],{"type":35,"tag":50,"props":1574,"children":1575},{"__ignoreMap":276},[1576,1603,1630,1645,1660,1689,1696,1703],{"type":35,"tag":1113,"props":1577,"children":1578},{"class":1115,"line":1116},[1579,1583,1587,1591,1595,1599],{"type":35,"tag":1113,"props":1580,"children":1581},{"style":1120},[1582],{"type":41,"value":1123},{"type":35,"tag":1113,"props":1584,"children":1585},{"style":1126},[1586],{"type":41,"value":1129},{"type":35,"tag":1113,"props":1588,"children":1589},{"style":1132},[1590],{"type":41,"value":1135},{"type":35,"tag":1113,"props":1592,"children":1593},{"style":1138},[1594],{"type":41,"value":1141},{"type":35,"tag":1113,"props":1596,"children":1597},{"style":1126},[1598],{"type":41,"value":1146},{"type":35,"tag":1113,"props":1600,"children":1601},{"style":1132},[1602],{"type":41,"value":1151},{"type":35,"tag":1113,"props":1604,"children":1605},{"class":1115,"line":1154},[1606,1610,1614,1618,1622,1626],{"type":35,"tag":1113,"props":1607,"children":1608},{"style":1158},[1609],{"type":41,"value":1161},{"type":35,"tag":1113,"props":1611,"children":1612},{"style":1132},[1613],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1615,"children":1616},{"style":1132},[1617],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1619,"children":1620},{"style":1174},[1621],{"type":41,"value":1177},{"type":35,"tag":1113,"props":1623,"children":1624},{"style":1132},[1625],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1627,"children":1628},{"style":1132},[1629],{"type":41,"value":1187},{"type":35,"tag":1113,"props":1631,"children":1632},{"class":1115,"line":1190},[1633,1637,1641],{"type":35,"tag":1113,"props":1634,"children":1635},{"style":1158},[1636],{"type":41,"value":1196},{"type":35,"tag":1113,"props":1638,"children":1639},{"style":1132},[1640],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1642,"children":1643},{"style":1132},[1644],{"type":41,"value":1205},{"type":35,"tag":1113,"props":1646,"children":1647},{"class":1115,"line":1208},[1648,1652,1656],{"type":35,"tag":1113,"props":1649,"children":1650},{"style":1158},[1651],{"type":41,"value":1214},{"type":35,"tag":1113,"props":1653,"children":1654},{"style":1132},[1655],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1657,"children":1658},{"style":1132},[1659],{"type":41,"value":1205},{"type":35,"tag":1113,"props":1661,"children":1662},{"class":1115,"line":1225},[1663,1668,1672,1676,1681,1685],{"type":35,"tag":1113,"props":1664,"children":1665},{"style":1158},[1666],{"type":41,"value":1667},"            inputSource",{"type":35,"tag":1113,"props":1669,"children":1670},{"style":1132},[1671],{"type":41,"value":1166},{"type":35,"tag":1113,"props":1673,"children":1674},{"style":1132},[1675],{"type":41,"value":1171},{"type":35,"tag":1113,"props":1677,"children":1678},{"style":1174},[1679],{"type":41,"value":1680},"passthrough",{"type":35,"tag":1113,"props":1682,"children":1683},{"style":1132},[1684],{"type":41,"value":1182},{"type":35,"tag":1113,"props":1686,"children":1687},{"style":1132},[1688],{"type":41,"value":1187},{"type":35,"tag":1113,"props":1690,"children":1691},{"class":1115,"line":1242},[1692],{"type":35,"tag":1113,"props":1693,"children":1694},{"style":1132},[1695],{"type":41,"value":1467},{"type":35,"tag":1113,"props":1697,"children":1698},{"class":1115,"line":1260},[1699],{"type":35,"tag":1113,"props":1700,"children":1701},{"style":1132},[1702],{"type":41,"value":1476},{"type":35,"tag":1113,"props":1704,"children":1705},{"class":1115,"line":1323},[1706,1710],{"type":35,"tag":1113,"props":1707,"children":1708},{"style":1132},[1709],{"type":41,"value":1485},{"type":35,"tag":1113,"props":1711,"children":1712},{"style":1126},[1713],{"type":41,"value":1490},{"type":35,"tag":44,"props":1715,"children":1716},{},[1717,1719,1725,1727,1732],{"type":41,"value":1718},"In passthrough mode, the sub-workflow receives the caller's items as-is, including the ",{"type":35,"tag":50,"props":1720,"children":1722},{"className":1721},[],[1723],{"type":41,"value":1724},"binary",{"type":41,"value":1726}," slot. Cost: no typed input schema, so agent tools can't pass parameters through ",{"type":35,"tag":50,"props":1728,"children":1730},{"className":1729},[],[1731],{"type":41,"value":1087},{"type":41,"value":1733},". Use this mode for sub-workflows called by other workflows (not agents) where binary needs to flow through.",{"type":35,"tag":44,"props":1735,"children":1736},{},[1737,1739,1745,1746,1752],{"type":41,"value":1738},"For sub-workflows that need binary AND are called by an agent, see ",{"type":35,"tag":50,"props":1740,"children":1742},{"className":1741},[],[1743],{"type":41,"value":1744},"n8n-binary-and-data-official",{"type":41,"value":714},{"type":35,"tag":50,"props":1747,"children":1749},{"className":1748},[],[1750],{"type":41,"value":1751},"references\u002FAGENT_TOOL_BINARY.md",{"type":41,"value":1753}," (agent tools can't pass binary directly).",{"type":35,"tag":311,"props":1755,"children":1757},{"id":1756},"exception-2-passthrough-for-sub-workflows-with-no-inputs",[1758],{"type":41,"value":1759},"Exception 2: passthrough for sub-workflows with no inputs",{"type":35,"tag":44,"props":1761,"children":1762},{},[1763],{"type":41,"value":1764},"Define Below requires at least one declared field. A sub-workflow that genuinely takes no inputs (a \"list active credentials\" tool, a \"current count\" lookup, any zero-arg operation) has nowhere to put the empty schema, so passthrough is the only option.",{"type":35,"tag":44,"props":1766,"children":1767},{},[1768],{"type":41,"value":1769},"When using passthrough specifically for the no-input case:",{"type":35,"tag":137,"props":1771,"children":1772},{},[1773,1791],{"type":35,"tag":80,"props":1774,"children":1775},{},[1776,1789],{"type":35,"tag":84,"props":1777,"children":1778},{},[1779,1781,1787],{"type":41,"value":1780},"Start the body with a ",{"type":35,"tag":50,"props":1782,"children":1784},{"className":1783},[],[1785],{"type":41,"value":1786},"Set",{"type":41,"value":1788}," (Edit Fields) node in \"Keep Only Set\" mode with no fields.",{"type":41,"value":1790}," This clears the caller's JSON so downstream nodes don't accidentally read fields from whatever shape the caller happened to pass. Without it, the body silently picks up whatever the caller forwarded.",{"type":35,"tag":80,"props":1792,"children":1793},{},[1794,1799],{"type":35,"tag":84,"props":1795,"children":1796},{},[1797],{"type":41,"value":1798},"Add a sticky note on the trigger documenting that no inputs are expected.",{"type":41,"value":1800}," Future readers (and the agent re-wiring this as a tool) need to know passthrough isn't here for binary, it's here because the schema is empty by design.",{"type":35,"tag":44,"props":1802,"children":1803},{},[1804,1806,1811,1813,1819,1820,1826,1828,1833],{"type":41,"value":1805},"Agent-tool wiring still works in the no-input case: ",{"type":35,"tag":50,"props":1807,"children":1809},{"className":1808},[],[1810],{"type":41,"value":1095},{"type":41,"value":1812}," accepts a sub-workflow whose input mapping has no fields. The agent's only decision is whether to invoke. The pattern from ",{"type":35,"tag":50,"props":1814,"children":1816},{"className":1815},[],[1817],{"type":41,"value":1818},"n8n-agents-official",{"type":41,"value":714},{"type":35,"tag":50,"props":1821,"children":1823},{"className":1822},[],[1824],{"type":41,"value":1825},"references\u002FTOOLS.md",{"type":41,"value":1827}," (\"zero ",{"type":35,"tag":50,"props":1829,"children":1831},{"className":1830},[],[1832],{"type":41,"value":127},{"type":41,"value":1834}," parameters\") applies directly.",{"type":35,"tag":311,"props":1836,"children":1838},{"id":1837},"other-conventions",[1839],{"type":41,"value":1840},"Other conventions",{"type":35,"tag":137,"props":1842,"children":1843},{},[1844,1861,1879,1921,1960],{"type":35,"tag":80,"props":1845,"children":1846},{},[1847,1859],{"type":35,"tag":84,"props":1848,"children":1849},{},[1850,1852,1858],{"type":41,"value":1851},"Document inputs and outputs in the workflow ",{"type":35,"tag":50,"props":1853,"children":1855},{"className":1854},[],[1856],{"type":41,"value":1857},"description",{"type":41,"value":232},{"type":41,"value":1860}," Field names, types, purpose. The description is what callers (humans and agents) read for the contract.",{"type":35,"tag":80,"props":1862,"children":1863},{},[1864,1869,1871,1877],{"type":35,"tag":84,"props":1865,"children":1866},{},[1867],{"type":41,"value":1868},"Return a consistent shape.",{"type":41,"value":1870}," For expected failures (e.g., parse error), return ",{"type":35,"tag":50,"props":1872,"children":1874},{"className":1873},[],[1875],{"type":41,"value":1876},"{ success: false, error: '...' }",{"type":41,"value":1878}," rather than throwing. Callers can branch without wrapping error outputs.",{"type":35,"tag":80,"props":1880,"children":1881},{},[1882,1887,1889,1895,1897,1903,1905,1911,1913,1919],{"type":35,"tag":84,"props":1883,"children":1884},{},[1885],{"type":41,"value":1886},"Treat the input schema as a contract once it has callers.",{"type":41,"value":1888}," Adding optional fields is safe. Renaming or removing fields can be done, but only carefully: enumerate every caller (",{"type":35,"tag":50,"props":1890,"children":1892},{"className":1891},[],[1893],{"type":41,"value":1894},"search_workflows",{"type":41,"value":1896}," for the sub-workflow's name + manual scan), migrate them in the same change, and verify with ",{"type":35,"tag":50,"props":1898,"children":1900},{"className":1899},[],[1901],{"type":41,"value":1902},"validate_workflow",{"type":41,"value":1904}," + ",{"type":35,"tag":50,"props":1906,"children":1908},{"className":1907},[],[1909],{"type":41,"value":1910},"get_workflow_details",{"type":41,"value":1912}," before publishing. A silent break here is hard to detect because n8n won't error on an unrecognized input field. The sub-workflow just sees ",{"type":35,"tag":50,"props":1914,"children":1916},{"className":1915},[],[1917],{"type":41,"value":1918},"undefined",{"type":41,"value":1920}," and the caller has no idea.",{"type":35,"tag":80,"props":1922,"children":1923},{},[1924,1929,1931,1937,1939,1944,1946,1952,1953,1959],{"type":35,"tag":84,"props":1925,"children":1926},{},[1927],{"type":41,"value":1928},"Use a final Set \u002F Edit Fields node to shape the return.",{"type":41,"value":1930}," Optional, sometimes required (when the last computation node carries noise fields), and good practice for sub-workflows even when not strictly required. It makes the return contract explicit at the boundary, so readers see the API by reading one node. This is the legitimate exception to the Set-node antipattern from ",{"type":35,"tag":50,"props":1932,"children":1934},{"className":1933},[],[1935],{"type":41,"value":1936},"n8n-expressions-official",{"type":41,"value":1938},": the implicit consumer of a sub-workflow's last node is ",{"type":35,"tag":161,"props":1940,"children":1941},{},[1942],{"type":41,"value":1943},"every caller",{"type":41,"value":1945},", so the Set earns its place as the explicit API boundary. Name it ",{"type":35,"tag":50,"props":1947,"children":1949},{"className":1948},[],[1950],{"type":41,"value":1951},"Return",{"type":41,"value":781},{"type":35,"tag":50,"props":1954,"children":1956},{"className":1955},[],[1957],{"type":41,"value":1958},"Return \u003Cthing>",{"type":41,"value":232},{"type":35,"tag":80,"props":1961,"children":1962},{},[1963,1968,1970,1975,1977,1982,1983,1987,1989,1995],{"type":35,"tag":84,"props":1964,"children":1965},{},[1966],{"type":41,"value":1967},"Return natural shapes, not storage shapes.",{"type":41,"value":1969}," A sub-workflow that owns a Data Table, a file in S3, or any storage layer should hide that representation from callers. Arrays return as arrays, objects as objects, dates as ISO strings, regardless of whether the underlying storage was JSON-stringified text or another internal format. The return contract is the ",{"type":35,"tag":161,"props":1971,"children":1972},{},[1973],{"type":41,"value":1974},"interface",{"type":41,"value":1976},". The storage layout is ",{"type":35,"tag":161,"props":1978,"children":1979},{},[1980],{"type":41,"value":1981},"implementation detail",{"type":41,"value":232},{"type":35,"tag":1984,"props":1985,"children":1986},"br",{},[],{"type":41,"value":1988},"Common slip: a sub-workflow has a \"fresh\" path (data just produced, natural shape) and a \"cached\" path (data just read from a ",{"type":35,"tag":50,"props":1990,"children":1992},{"className":1991},[],[1993],{"type":41,"value":1994},"_object",{"type":41,"value":1996}," column, still stringified). Wrong instinct: stringify the fresh path \"to match\" the cached path. Right instinct: parse the cached path so both return the natural shape. Callers shouldn't have to know which they got.",{"type":35,"tag":44,"props":1998,"children":1999},{},[2000,2002,2007,2008,2014],{"type":41,"value":2001},"For sub-workflows wired as agent tools specifically, see ",{"type":35,"tag":50,"props":2003,"children":2005},{"className":2004},[],[2006],{"type":41,"value":1818},{"type":41,"value":714},{"type":35,"tag":50,"props":2009,"children":2011},{"className":2010},[],[2012],{"type":41,"value":2013},"references\u002FSUBWORKFLOW_AS_TOOL.md",{"type":41,"value":232},{"type":35,"tag":69,"props":2016,"children":2018},{"id":2017},"calling-sub-workflows-execute-workflow-modes",[2019,2021,2026],{"type":41,"value":2020},"Calling sub-workflows: ",{"type":35,"tag":50,"props":2022,"children":2024},{"className":2023},[],[2025],{"type":41,"value":647},{"type":41,"value":2027}," modes",{"type":35,"tag":44,"props":2029,"children":2030},{},[2031,2033,2038],{"type":41,"value":2032},"Two settings on the caller-side ",{"type":35,"tag":50,"props":2034,"children":2036},{"className":2035},[],[2037],{"type":41,"value":647},{"type":41,"value":2039}," node beyond inputs\u002FworkflowId:",{"type":35,"tag":137,"props":2041,"children":2042},{},[2043,2102],{"type":35,"tag":80,"props":2044,"children":2045},{},[2046,2055,2057,2063,2065,2070,2072,2078,2080,2085,2087,2093,2095,2100],{"type":35,"tag":84,"props":2047,"children":2048},{},[2049],{"type":35,"tag":50,"props":2050,"children":2052},{"className":2051},[],[2053],{"type":41,"value":2054},"mode",{"type":41,"value":2056}," defaults to ",{"type":35,"tag":50,"props":2058,"children":2060},{"className":2059},[],[2061],{"type":41,"value":2062},"'all'",{"type":41,"value":2064},": the sub-workflow runs ",{"type":35,"tag":84,"props":2066,"children":2067},{},[2068],{"type":41,"value":2069},"once",{"type":41,"value":2071}," with all N items as input. Items still flow through nodes per-item like any other workflow. Set ",{"type":35,"tag":50,"props":2073,"children":2075},{"className":2074},[],[2076],{"type":41,"value":2077},"mode: 'each'",{"type":41,"value":2079}," to run the sub-workflow N separate times, one item per execution. For sub-workflows whose body just processes items normally, the two are equivalent. The split matters when the sub-workflow's body assumes it sees exactly one item (per-run aggregation, \"this is THE customer to operate on\" logic, a final write that should fire once per input). ",{"type":35,"tag":50,"props":2081,"children":2083},{"className":2082},[],[2084],{"type":41,"value":2077},{"type":41,"value":2086}," matches that assumption, ",{"type":35,"tag":50,"props":2088,"children":2090},{"className":2089},[],[2091],{"type":41,"value":2092},"mode: 'all'",{"type":41,"value":2094}," breaks it. When you DO need per-item iteration, prefer ",{"type":35,"tag":50,"props":2096,"children":2098},{"className":2097},[],[2099],{"type":41,"value":2077},{"type":41,"value":2101}," over a Loop Over Items node inside the sub-workflow.",{"type":35,"tag":80,"props":2103,"children":2104},{},[2105,2114,2115,2121,2123,2129],{"type":35,"tag":84,"props":2106,"children":2107},{},[2108],{"type":35,"tag":50,"props":2109,"children":2111},{"className":2110},[],[2112],{"type":41,"value":2113},"waitForSubWorkflow",{"type":41,"value":2056},{"type":35,"tag":50,"props":2116,"children":2118},{"className":2117},[],[2119],{"type":41,"value":2120},"true",{"type":41,"value":2122},". Setting ",{"type":35,"tag":50,"props":2124,"children":2126},{"className":2125},[],[2127],{"type":41,"value":2128},"options.waitForSubWorkflow: false",{"type":41,"value":2130}," fires the call and immediately moves on, and the sub-workflow continues in the background. The caller's downstream sees no return data.",{"type":35,"tag":44,"props":2132,"children":2133},{},[2134,2139,2140,2145,2147,2152],{"type":35,"tag":50,"props":2135,"children":2137},{"className":2136},[],[2138],{"type":41,"value":2077},{"type":41,"value":1904},{"type":35,"tag":50,"props":2141,"children":2143},{"className":2142},[],[2144],{"type":41,"value":655},{"type":41,"value":2146}," is ",{"type":35,"tag":84,"props":2148,"children":2149},{},[2150],{"type":41,"value":2151},"the only true parallelization n8n offers",{"type":41,"value":2153},": N sub-workflow executions dispatched without waiting, running concurrently (still bounded by per-instance concurrency limits and per-call overhead). Useful for \"kick off N independent jobs, poll\u002Faggregate later\". For example: dispatch a long-running job per item, track each in a Data Table, then loop until all rows mark themselves complete or time out.",{"type":35,"tag":44,"props":2155,"children":2156},{},[2157,2159,2164],{"type":41,"value":2158},"For the polling-after-fire-and-forget pattern, see ",{"type":35,"tag":50,"props":2160,"children":2162},{"className":2161},[],[2163],{"type":41,"value":258},{"type":41,"value":2165}," \"Fire-and-forget parallelization\".",{"type":35,"tag":69,"props":2167,"children":2169},{"id":2168},"reference-files",[2170],{"type":41,"value":2171},"Reference files",{"type":35,"tag":2173,"props":2174,"children":2175},"table",{},[2176,2195],{"type":35,"tag":2177,"props":2178,"children":2179},"thead",{},[2180],{"type":35,"tag":2181,"props":2182,"children":2183},"tr",{},[2184,2190],{"type":35,"tag":2185,"props":2186,"children":2187},"th",{},[2188],{"type":41,"value":2189},"File",{"type":35,"tag":2185,"props":2191,"children":2192},{},[2193],{"type":41,"value":2194},"Read when",{"type":35,"tag":2196,"props":2197,"children":2198},"tbody",{},[2199,2229],{"type":35,"tag":2181,"props":2200,"children":2201},{},[2202,2211],{"type":35,"tag":2203,"props":2204,"children":2205},"td",{},[2206],{"type":35,"tag":50,"props":2207,"children":2209},{"className":2208},[],[2210],{"type":41,"value":258},{"type":35,"tag":2203,"props":2212,"children":2213},{},[2214,2219,2221,2227],{"type":35,"tag":50,"props":2215,"children":2217},{"className":2216},[],[2218],{"type":41,"value":2092},{"type":41,"value":2220}," vs ",{"type":35,"tag":50,"props":2222,"children":2224},{"className":2223},[],[2225],{"type":41,"value":2226},"'each'",{"type":41,"value":2228}," default, splitting by input shape (binary\u002Fpassthrough vs Define Below), fire-and-forget parallelization with Data Table polling",{"type":35,"tag":2181,"props":2230,"children":2231},{},[2232,2240],{"type":35,"tag":2203,"props":2233,"children":2234},{},[2235],{"type":35,"tag":50,"props":2236,"children":2238},{"className":2237},[],[2239],{"type":41,"value":230},{"type":35,"tag":2203,"props":2241,"children":2242},{},[2243],{"type":41,"value":2244},"Naming and tagging a new sub-workflow, searching for existing ones, the tag convention",{"type":35,"tag":69,"props":2246,"children":2248},{"id":2247},"anti-patterns",[2249],{"type":41,"value":2250},"Anti-patterns",{"type":35,"tag":2173,"props":2252,"children":2253},{},[2254,2275],{"type":35,"tag":2177,"props":2255,"children":2256},{},[2257],{"type":35,"tag":2181,"props":2258,"children":2259},{},[2260,2265,2270],{"type":35,"tag":2185,"props":2261,"children":2262},{},[2263],{"type":41,"value":2264},"Anti-pattern",{"type":35,"tag":2185,"props":2266,"children":2267},{},[2268],{"type":41,"value":2269},"What goes wrong",{"type":35,"tag":2185,"props":2271,"children":2272},{},[2273],{"type":41,"value":2274},"Fix",{"type":35,"tag":2196,"props":2276,"children":2277},{},[2278,2311,2336,2354,2384,2427,2480,2525,2550,2582],{"type":35,"tag":2181,"props":2279,"children":2280},{},[2281,2286,2291],{"type":35,"tag":2203,"props":2282,"children":2283},{},[2284],{"type":41,"value":2285},"Duplicating the same date-parsing nodes in three workflows",{"type":35,"tag":2203,"props":2287,"children":2288},{},[2289],{"type":41,"value":2290},"Bug fixes happen in two places, miss the third",{"type":35,"tag":2203,"props":2292,"children":2293},{},[2294,2296,2302,2304,2309],{"type":41,"value":2295},"Extract to a single ",{"type":35,"tag":50,"props":2297,"children":2299},{"className":2298},[],[2300],{"type":41,"value":2301},"Parse \u003Cformat> date",{"type":41,"value":2303}," sub-workflow (tag ",{"type":35,"tag":50,"props":2305,"children":2307},{"className":2306},[],[2308],{"type":41,"value":190},{"type":41,"value":2310},") once",{"type":35,"tag":2181,"props":2312,"children":2313},{},[2314,2319,2324],{"type":35,"tag":2203,"props":2315,"children":2316},{},[2317],{"type":41,"value":2318},"Building a new sub-workflow without searching",{"type":35,"tag":2203,"props":2320,"children":2321},{},[2322],{"type":41,"value":2323},"Library grows duplicates, and future searches find both",{"type":35,"tag":2203,"props":2325,"children":2326},{},[2327,2329,2334],{"type":41,"value":2328},"Always ",{"type":35,"tag":50,"props":2330,"children":2332},{"className":2331},[],[2333],{"type":41,"value":1894},{"type":41,"value":2335}," first",{"type":35,"tag":2181,"props":2337,"children":2338},{},[2339,2344,2349],{"type":35,"tag":2203,"props":2340,"children":2341},{},[2342],{"type":41,"value":2343},"Sub-workflow named\u002Fdescribed as pure that quietly writes to a log table",{"type":35,"tag":2203,"props":2345,"children":2346},{},[2347],{"type":41,"value":2348},"Callers can't reason about retry or idempotency, side effect ambushes them",{"type":35,"tag":2203,"props":2350,"children":2351},{},[2352],{"type":41,"value":2353},"Either make the side effect part of the contract (rename, document, return its result) or move it out",{"type":35,"tag":2181,"props":2355,"children":2356},{},[2357,2367,2372],{"type":35,"tag":2203,"props":2358,"children":2359},{},[2360,2362],{"type":41,"value":2361},"Sub-workflow with no ",{"type":35,"tag":50,"props":2363,"children":2365},{"className":2364},[],[2366],{"type":41,"value":1857},{"type":35,"tag":2203,"props":2368,"children":2369},{},[2370],{"type":41,"value":2371},"Won't be found in future searches, nobody knows what it does",{"type":35,"tag":2203,"props":2373,"children":2374},{},[2375,2377,2382],{"type":41,"value":2376},"Set ",{"type":35,"tag":50,"props":2378,"children":2380},{"className":2379},[],[2381],{"type":41,"value":1857},{"type":41,"value":2383}," with input\u002Foutput shape and purpose",{"type":35,"tag":2181,"props":2385,"children":2386},{},[2387,2398,2403],{"type":35,"tag":2203,"props":2388,"children":2389},{},[2390,2392],{"type":41,"value":2391},"Sub-workflow named ",{"type":35,"tag":50,"props":2393,"children":2395},{"className":2394},[],[2396],{"type":41,"value":2397},"Helper 3",{"type":35,"tag":2203,"props":2399,"children":2400},{},[2401],{"type":41,"value":2402},"Name doesn't tell anyone what it does",{"type":35,"tag":2203,"props":2404,"children":2405},{},[2406,2408,2413,2415,2420,2421],{"type":41,"value":2407},"Verb-first descriptive name (",{"type":35,"tag":50,"props":2409,"children":2411},{"className":2410},[],[2412],{"type":41,"value":222},{"type":41,"value":2414},"), see ",{"type":35,"tag":50,"props":2416,"children":2418},{"className":2417},[],[2419],{"type":41,"value":296},{"type":41,"value":714},{"type":35,"tag":50,"props":2422,"children":2424},{"className":2423},[],[2425],{"type":41,"value":2426},"NAMING_CONVENTIONS.md",{"type":35,"tag":2181,"props":2428,"children":2429},{},[2430,2435,2448],{"type":35,"tag":2203,"props":2431,"children":2432},{},[2433],{"type":41,"value":2434},"Untagged sub-workflow",{"type":35,"tag":2203,"props":2436,"children":2437},{},[2438,2440,2446],{"type":41,"value":2439},"Won't show up under any ",{"type":35,"tag":50,"props":2441,"children":2443},{"className":2442},[],[2444],{"type":41,"value":2445},"tags",{"type":41,"value":2447}," filter, future you can't find it",{"type":35,"tag":2203,"props":2449,"children":2450},{},[2451,2453,2458,2460,2465,2467,2473,2474],{"type":41,"value":2452},"Tag it (",{"type":35,"tag":50,"props":2454,"children":2456},{"className":2455},[],[2457],{"type":41,"value":190},{"type":41,"value":2459},", domain, ",{"type":35,"tag":50,"props":2461,"children":2463},{"className":2462},[],[2464],{"type":41,"value":206},{"type":41,"value":2466},") right after create via ",{"type":35,"tag":50,"props":2468,"children":2470},{"className":2469},[],[2471],{"type":41,"value":2472},"update_workflow",{"type":41,"value":714},{"type":35,"tag":50,"props":2475,"children":2477},{"className":2476},[],[2478],{"type":41,"value":2479},"addTags",{"type":35,"tag":2181,"props":2481,"children":2482},{},[2483,2500,2512],{"type":35,"tag":2203,"props":2484,"children":2485},{},[2486,2491,2493,2498],{"type":35,"tag":50,"props":2487,"children":2489},{"className":2488},[],[2490],{"type":41,"value":55},{"type":41,"value":2492}," set to ",{"type":35,"tag":50,"props":2494,"children":2496},{"className":2495},[],[2497],{"type":41,"value":1680},{"type":41,"value":2499}," when not handling binary and not deliberately zero-input",{"type":35,"tag":2203,"props":2501,"children":2502},{},[2503,2505,2510],{"type":41,"value":2504},"No typed schema means agent tools can't fill parameters via ",{"type":35,"tag":50,"props":2506,"children":2508},{"className":2507},[],[2509],{"type":41,"value":127},{"type":41,"value":2511},", structured callers can't pass values cleanly",{"type":35,"tag":2203,"props":2513,"children":2514},{},[2515,2517,2523],{"type":41,"value":2516},"Use \"Define Below\" with declared ",{"type":35,"tag":50,"props":2518,"children":2520},{"className":2519},[],[2521],{"type":41,"value":2522},"workflowInputs.values",{"type":41,"value":2524}," (name + type per field). The exceptions are binary-receiving sub-workflows and sub-workflows that genuinely take no inputs (see \"Exception 2\")",{"type":35,"tag":2181,"props":2526,"children":2527},{},[2528,2533,2538],{"type":35,"tag":2203,"props":2529,"children":2530},{},[2531],{"type":41,"value":2532},"Passthrough trigger for a zero-input sub-workflow without a Set-to-clear node and explanatory sticky",{"type":35,"tag":2203,"props":2534,"children":2535},{},[2536],{"type":41,"value":2537},"Body silently reads stray fields from whatever the caller forwarded; future readers think passthrough is for binary",{"type":35,"tag":2203,"props":2539,"children":2540},{},[2541,2543,2548],{"type":41,"value":2542},"Add a ",{"type":35,"tag":50,"props":2544,"children":2546},{"className":2545},[],[2547],{"type":41,"value":1786},{"type":41,"value":2549}," (\"Keep Only Set\", no fields) at the top of the body and a sticky on the trigger noting no inputs are expected",{"type":35,"tag":2181,"props":2551,"children":2552},{},[2553,2558,2563],{"type":35,"tag":2203,"props":2554,"children":2555},{},[2556],{"type":41,"value":2557},"Sub-workflow called as an agent tool that expects binary input",{"type":35,"tag":2203,"props":2559,"children":2560},{},[2561],{"type":41,"value":2562},"Agent tools can't pass binary directly",{"type":35,"tag":2203,"props":2564,"children":2565},{},[2566,2568,2573,2574,2580],{"type":41,"value":2567},"See ",{"type":35,"tag":50,"props":2569,"children":2571},{"className":2570},[],[2572],{"type":41,"value":1744},{"type":41,"value":714},{"type":35,"tag":50,"props":2575,"children":2577},{"className":2576},[],[2578],{"type":41,"value":2579},"AGENT_TOOL_BINARY.md",{"type":41,"value":2581}," for the right pattern",{"type":35,"tag":2181,"props":2583,"children":2584},{},[2585,2590,2595],{"type":35,"tag":2203,"props":2586,"children":2587},{},[2588],{"type":41,"value":2589},"30-node workflow with no extraction",{"type":35,"tag":2203,"props":2591,"children":2592},{},[2593],{"type":41,"value":2594},"Hard to read, hard to test, hard to replace",{"type":35,"tag":2203,"props":2596,"children":2597},{},[2598],{"type":41,"value":2599},"Extract logical sections into sub-workflows",{"type":35,"tag":2601,"props":2602,"children":2603},"style",{},[2604],{"type":41,"value":2605},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":2607,"total":1479},[2608,2621,2633,2649,2666,2681,2694],{"slug":1818,"name":1818,"fn":2609,"description":2610,"org":2611,"tags":2612,"stars":19,"repoUrl":20,"updatedAt":2620},"build AI agents in n8n","Use when building or editing any AI feature in n8n: AI Agents, Text Classifier, Information Extractor, Sentiment Analysis, Summarization Chain, Basic LLM Chain, embeddings, vector stores, single one-shot LLM calls, or AI media generation (image \u002F audio \u002F video) via the native LangChain provider nodes. Triggers on any `@n8n\u002Fn8n-nodes-langchain.*` node, \"agent\", \"chat assistant\", \"LLM with tools\", \"tool calling\", \"fromAi\", \"system prompt\", \"memory window\", \"structured output\", \"outputParser\", \"function calling\", \"RAG\", \"vector store\", \"embeddings\", \"classify with AI\", \"extract fields with LLM\", \"sentiment analysis\", \"summarize with LLM\", \"single LLM call\", chat triggers with files, AI image \u002F video \u002F audio generation, or any multi-turn or one-shot LLM behavior.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2613,2616,2619],{"name":2614,"slug":2615,"type":15},"Agents","agents",{"name":2617,"slug":2618,"type":15},"LLM","llm",{"name":17,"slug":18,"type":15},"2026-07-08T05:44:47.938896",{"slug":1744,"name":1744,"fn":2622,"description":2623,"org":2624,"tags":2625,"stars":19,"repoUrl":20,"updatedAt":2632},"handle binary data and files in n8n","Use when handling files, images, attachments, or binary data in n8n, OR when an AI agent needs to take a user-uploaded file as tool input or return a generated file. For Data Tables (schemas, dedup, persistent state), see the separate n8n-data-tables-official skill. Triggers on \"file\", \"image\", \"PDF\", \"attachment\", \"binary\", \"upload\", \"download\", chat trigger with files, agent tool that needs a file, vision\u002Fmultimodal, or any handling of non-JSON file data.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2626,2627,2630,2631],{"name":13,"slug":14,"type":15},{"name":2628,"slug":2629,"type":15},"File Uploads","file-uploads",{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-07-08T05:45:01.884342",{"slug":2634,"name":2634,"fn":2635,"description":2636,"org":2637,"tags":2638,"stars":19,"repoUrl":20,"updatedAt":2648},"n8n-code-nodes-official","write custom logic in n8n","Use when the user reaches for a Code node, mentions writing JavaScript or Python in n8n, or any custom logic comes up in workflow design. Triggers on \"Code node\", \"Code\", \"JavaScript\", \"Python\", \"custom logic\", \"transform data\", \"$input\", \"$json transformation\", \"loop in code\", \"write a function\", or any time the obvious answer seems to be \"just put it in code.\"",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2639,2642,2645],{"name":2640,"slug":2641,"type":15},"Engineering","engineering",{"name":2643,"slug":2644,"type":15},"JavaScript","javascript",{"name":2646,"slug":2647,"type":15},"Python","python","2026-07-08T05:44:49.656127",{"slug":821,"name":821,"fn":2650,"description":2651,"org":2652,"tags":2653,"stars":19,"repoUrl":20,"updatedAt":2665},"manage credentials and security in n8n","Use when handling any auth, API keys, tokens, OAuth, bearer tokens, basic auth, or secret values in n8n workflows. Triggers on \"API key\", \"token\", \"bearer\", \"OAuth\", \"secret\", \"auth\", \"credentials\", \"Authorization header\", \"x-api-key\", or any node configuration that mentions a third-party service.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2654,2657,2658,2659,2662],{"name":2655,"slug":2656,"type":15},"Authentication","authentication",{"name":13,"slug":14,"type":15},{"name":8,"slug":8,"type":15},{"name":2660,"slug":2661,"type":15},"OAuth","oauth",{"name":2663,"slug":2664,"type":15},"Security","security","2026-07-08T05:45:07.766252",{"slug":2667,"name":2667,"fn":2668,"description":2669,"org":2670,"tags":2671,"stars":19,"repoUrl":20,"updatedAt":2680},"n8n-data-tables-official","manage n8n Data Tables","Use when working with n8n's built-in Data Tables, designing schemas, inserting\u002Fupdating\u002Fupserting rows, deduping, or querying. Triggers on \"Data Table\", \"data table\", `n8n-nodes-base.dataTable`, \"dedup\", \"idempotency\", \"lookup\", \"persistent state\", \"store across executions\", or any schema design discussion inside n8n.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2672,2675,2678,2679],{"name":2673,"slug":2674,"type":15},"Data Modeling","data-modeling",{"name":2676,"slug":2677,"type":15},"Database","database",{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-07-08T05:44:52.048039",{"slug":2682,"name":2682,"fn":2683,"description":2684,"org":2685,"tags":2686,"stars":19,"repoUrl":20,"updatedAt":2693},"n8n-debugging-official","debug and fix n8n workflow errors","Use when an n8n workflow isn't working, errors appear, results don't match what was expected, or the user says \"this isn't working.\" Triggers on errors, unexpected output, \"it's not working\", \"why is this happening\", \"the workflow stopped\", failure investigation, or any debugging context.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2687,2688,2691,2692],{"name":13,"slug":14,"type":15},{"name":2689,"slug":2690,"type":15},"Debugging","debugging",{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-07-08T05:45:05.897341",{"slug":813,"name":813,"fn":2695,"description":2696,"org":2697,"tags":2698,"stars":19,"repoUrl":20,"updatedAt":2705},"implement error handling in n8n","Use when building any webhook-triggered workflow, scheduled\u002Fproduction-bound workflow, wiring a per-node error output, or any workflow where silent failure would drop user-visible work. Triggers on \"webhook\", \"respond to webhook\", \"API\", \"production\", \"error\", \"failure\", \"5xx\", \"try\u002Fcatch\", \"error workflow\", \"onError\", \"continueErrorOutput\", \"error branch\", \"node error output\", \"output(1)\", \"main[1]\", \"scheduled\", or any workflow that runs unattended.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2699,2700,2701,2704],{"name":2689,"slug":2690,"type":15},{"name":8,"slug":8,"type":15},{"name":2702,"slug":2703,"type":15},"Operations","operations",{"name":17,"slug":18,"type":15},"2026-07-08T05:44:59.710099",{"items":2707,"total":2847},[2708,2725,2738,2749,2759,2770,2783,2800,2809,2821,2831,2841],{"slug":2709,"name":2709,"fn":2710,"description":2711,"org":2712,"tags":2713,"stars":2722,"repoUrl":2723,"updatedAt":2724},"config-evals","configure workflow evaluations","Builds and maintains configuration-based evaluations on a workflow with the eval-config tool. Use when the user asks to set up, add, view, change, or remove an evaluation, score, grade, or judge a workflow's output, or measure answer quality against a test dataset. This is the only eval form Instance AI handles — it does not touch on-canvas evaluation nodes.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2714,2717,2718,2721],{"name":2715,"slug":2716,"type":15},"Evals","evals",{"name":8,"slug":8,"type":15},{"name":2719,"slug":2720,"type":15},"Testing","testing",{"name":17,"slug":18,"type":15},198156,"https:\u002F\u002Fgithub.com\u002Fn8n-io\u002Fn8n","2026-07-24T05:37:07.398695",{"slug":2726,"name":2726,"fn":2727,"description":2728,"org":2729,"tags":2730,"stars":2722,"repoUrl":2723,"updatedAt":2737},"credential-setup-with-computer-use","configure n8n credentials via browser","Guides n8n credential setup through Computer Use browser tools. Use when a user needs OAuth apps, API keys, client IDs, client secrets, or other credential values from an external service console.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2731,2732,2735,2736],{"name":13,"slug":14,"type":15},{"name":2733,"slug":2734,"type":15},"Configuration","configuration",{"name":8,"slug":8,"type":15},{"name":2660,"slug":2661,"type":15},"2026-06-30T07:40:45.54",{"slug":2739,"name":2739,"fn":2668,"description":2740,"org":2741,"tags":2742,"stars":2722,"repoUrl":2723,"updatedAt":2748},"data-table-manager","Load before calling data-tables or parse-file. Use for natural standalone requests like \"what data tables do I have?\", \"show\u002Flist my tables\", or \"what columns are in this table?\", and whenever the user asks to list, show, create, inspect, import, seed, query, update, clean up, rename columns in, or delete data tables and rows, especially from CSV\u002FXLSX\u002FJSON attachments. Also load before building or planning workflows that create or write to Data Tables (then load workflow-builder before build-workflow).",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2743,2746,2747],{"name":2744,"slug":2745,"type":15},"Data Engineering","data-engineering",{"name":2676,"slug":2677,"type":15},{"name":8,"slug":8,"type":15},"2026-07-27T06:07:14.648144",{"slug":2750,"name":2750,"fn":2751,"description":2752,"org":2753,"tags":2754,"stars":2722,"repoUrl":2723,"updatedAt":2737},"debugging-executions","debug failed n8n workflow executions","Debug failed or wrong-output workflow executions using executions tools. Load when the user reports execution failures, unexpected node output, empty parameter values after a successful run, or a node showing a red or failed expression error.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2755,2756,2757,2758],{"name":13,"slug":14,"type":15},{"name":2689,"slug":2690,"type":15},{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},{"slug":2760,"name":2760,"fn":2761,"description":2762,"org":2763,"tags":2764,"stars":2722,"repoUrl":2723,"updatedAt":2769},"intent-recognition","classify automation requests by control flow","Classifies automation requests using two decisions: anchor (which primitive owns the top-level control flow — workflow-anchored, agent-anchored, needs-clarification, or out-of-scope) and embeds_other (whether the other primitive appears embedded inside — an agent step inside a workflow, or a workflow invoked as an agent tool). Must be used before deciding the intent of any automation request, including compound requests with multiple independent automations, mid-build extensions to an existing workflow or agent, one-off questions or reports that need external systems you cannot query directly, and requests that need clarification before an anchor can be chosen, before choosing workflow-builder, planning, or an agent-oriented design.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2765,2766,2767,2768],{"name":2614,"slug":2615,"type":15},{"name":13,"slug":14,"type":15},{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-07-30T05:30:06.772347",{"slug":2771,"name":2771,"fn":2772,"description":2773,"org":2774,"tags":2775,"stars":2722,"repoUrl":2723,"updatedAt":2782},"n8n-cli","manage n8n workflows from the CLI","Use the n8n CLI to manage workflows, credentials, executions, and more on an n8n instance. Use when the user asks to interact with n8n, automate workflows, manage credentials, or operate their instance from the command line.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2776,2777,2780,2781],{"name":13,"slug":14,"type":15},{"name":2778,"slug":2779,"type":15},"CLI","cli",{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-04-06T18:38:40.360123",{"slug":2784,"name":2784,"fn":2785,"description":2786,"org":2787,"tags":2788,"stars":2722,"repoUrl":2723,"updatedAt":2799},"n8n-docs-assistant","retrieve n8n documentation and guidance","Answers n8n product, setup, credential, node, hosting, API, and usage questions from current n8n docs. Load n8n-docs via load_tool before calling it (search \"n8n docs\" if not visible). Use when the user asks how to configure, set up, troubleshoot, or understand n8n behavior, especially credential setup questions opened from the credential modal.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2789,2792,2793,2796],{"name":2790,"slug":2791,"type":15},"Documentation","documentation",{"name":8,"slug":8,"type":15},{"name":2794,"slug":2795,"type":15},"Reference","reference",{"name":2797,"slug":2798,"type":15},"Search","search","2026-07-27T06:07:15.692906",{"slug":2801,"name":2801,"fn":2802,"description":2803,"org":2804,"tags":2805,"stars":2722,"repoUrl":2723,"updatedAt":2737},"planned-task-runtime","manage n8n task runtimes","Handles system follow-up turns: planned-task-follow-up (synthesize, replan, build-workflow, checkpoint), background-task-completed, running-tasks context, and create-tasks silence rules. Load whenever any of these tags appear or after calling create-tasks.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2806,2807,2808],{"name":13,"slug":14,"type":15},{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},{"slug":2810,"name":2810,"fn":2811,"description":2812,"org":2813,"tags":2814,"stars":2722,"repoUrl":2723,"updatedAt":2820},"planning","coordinate multi-artifact workflows","ONLY for coordinated multi-artifact work: multiple workflows with dependencies, shared data-table schema\u002Fmigration across tasks, or the user explicitly asked to review a plan first. Load create-tasks via load_tool before calling it (search \"create tasks\" if not visible). Do NOT use for new one-off workflows, single-workflow edits, verification-only requests, or standalone data-table ops — use workflow-builder or data-table-manager instead.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2815,2816,2817,2819],{"name":13,"slug":14,"type":15},{"name":8,"slug":8,"type":15},{"name":2818,"slug":2810,"type":15},"Planning",{"name":17,"slug":18,"type":15},"2026-07-27T06:07:16.673218",{"slug":2822,"name":2822,"fn":2823,"description":2824,"org":2825,"tags":2826,"stars":2722,"repoUrl":2723,"updatedAt":2830},"post-build-flow","verify and set up n8n workflows","Handles workflow verification and setup after build-workflow succeeds, or when the message contains workflow-verification-follow-up or workflow-setup-required. Load after direct builds, when verificationReadiness requires action, or on orchestrator verify\u002Fsetup follow-up turns.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2827,2828,2829],{"name":13,"slug":14,"type":15},{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-07-24T05:37:08.421329",{"slug":2832,"name":2832,"fn":2833,"description":2834,"org":2835,"tags":2836,"stars":2722,"repoUrl":2723,"updatedAt":2840},"workflow-builder","build and edit n8n workflows","Load before calling build-workflow. Default path for all single-workflow work: new one-off workflows, existing-workflow edits, verification repairs, and workflow-local data tables. Write or edit a workspace source file, then call build-workflow with filePath. When the workflow creates or writes Data Tables, load data-table-manager first, then this skill. Do not load planning or create-tasks first. Load planning only when multiple coordinated workflows or shared cross-task data tables require a dependency-aware task graph.",{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2837,2838,2839],{"name":13,"slug":14,"type":15},{"name":8,"slug":8,"type":15},{"name":17,"slug":18,"type":15},"2026-07-30T05:30:07.798011",{"slug":1818,"name":1818,"fn":2609,"description":2610,"org":2842,"tags":2843,"stars":19,"repoUrl":20,"updatedAt":2620},{"slug":8,"name":8,"logoUrl":9,"githubOrg":10},[2844,2845,2846],{"name":2614,"slug":2615,"type":15},{"name":2617,"slug":2618,"type":15},{"name":17,"slug":18,"type":15},25]