n8n-subworkflows-official

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

n8n Sub-workflows

n8n Sub-workflows

Sub-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.
That framing opens up the function-shaped wins: encapsulation, reuse, testability, replaceability. It's the primary reuse mechanism in n8n, and unfortunately underused.
Without sub-workflows, the same logic gets duplicated across workflows. Bug fixes happen in multiple places, one gets missed, and "identical" copies drift.
子工作流是可复用的函数。
Execute Workflow Trigger
声明输入参数,工作流主体执行具体操作,最后一个节点返回输出结果。调用方可以像调用其他任何节点一样调用它。
这种架构带来了类似函数的优势:封装性、复用性、可测试性和可替换性。它是n8n中主要的复用机制,但遗憾的是并未被充分利用。
如果不使用子工作流,相同的逻辑会在多个工作流中重复出现。修复漏洞时可能需要修改多个地方,一旦遗漏一处,这些“完全相同”的副本就会逐渐产生差异。

Non-negotiables

必须遵守的规则

  1. 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/or keyword (
    query: '<keyword>'
    ). Tags are the discovery mechanism (n8n 2.27.0+).
  2. 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.
  1. 构建前先搜索。在编写处理通用问题的逻辑之前,先检查是否已存在对应的子工作流。可以通过标签过滤(
    search_workflows({ tags: ['subworkflow'] })
    、领域标签)和/或关键词搜索(
    query: '<keyword>'
    )。标签是n8n 2.27.0及以上版本中的发现机制。
  2. Execute Workflow Trigger
    使用“Define Below”模式并配置类型化字段,而非直通模式
    。只有“Define Below”模式允许Agent工具(
    fromAi
    )和结构化调用方传入值。有两个例外情况:(a) 子工作流明确需要接收二进制数据(此时无法直接作为Agent工具使用),或(b) 子工作流完全不需要输入(“Define Below”模式要求至少定义一个字段)。详情请参阅下方的“子工作流的输入与输出”部分。

Strong defaults

推荐默认做法

  • 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.
  • 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 / repository pattern). What to avoid is accidental state: a "validate" sub-workflow that quietly writes to a log table.
  • Tag for discovery: every sub-workflow gets
    subworkflow
    , a domain tag (
    customer
    ), and/or
    tool
    . Tags are what
    search_workflows({ tags })
    filters on; names stay plain and descriptive (
    Parse RFC2822 date
    ). See
    references/NAMING_AND_DISCOVERY.md
    .
  • Description carries keywords. Input/output shape + representative terms, so varied queries surface it.
  • 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/SUBWORKFLOW_PATTERNS.md
    "Splitting by input shape".
  • 任何可复用的内容都应转为子工作流。如果某段逻辑块有可能在其他地方被用到,就将其提取为子工作流。例外情况:简单的包装器(仅一个HTTP调用,无额外逻辑)和与调用方紧密耦合的逻辑块。
  • 纯逻辑默认采用无状态设计(输入→输出,无外部状态)。对于涉及状态操作的逻辑,需刻意构建有状态的子工作流,通过清晰的契约抽象操作(类似ORM/仓储模式)。要避免的是“意外”状态:例如名为“验证”的子工作流却悄悄写入日志表。
  • 添加标签便于发现:每个子工作流都应添加
    subworkflow
    标签、领域标签(如
    customer
    )和/或
    tool
    标签。
    search_workflows({ tags })
    会根据标签进行过滤;子工作流的名称应简洁明了(如
    Parse RFC2822 date
    )。详情请参阅
    references/NAMING_AND_DISCOVERY.md
  • 描述中包含关键词。需包含输入/输出结构及代表性术语,以便不同的搜索查询都能找到它。
  • 当输入契约确实不同时拆分(二进制vs JSON、同步vs异步、不同的认证方案)。不要通过直通模式+内部分支将不同的契约强行整合到同一个触发器下。详情请参阅
    references/SUBWORKFLOW_PATTERNS.md
    中的“按输入结构拆分”部分。

Decision tree: should this be a sub-workflow?

决策树:是否应将其转为子工作流?

About to write a chunk of logic?
├── Could this plausibly be needed in another workflow?
│   ├── Yes → extract to sub-workflow
│   └── No → keep inline
├── Is this chunk >5 nodes and conceptually one thing?
│   └── Extract if you want testability, isolation, or reuse; if it's only for a cleaner canvas, group it inline instead (see below).
├── Is this chunk dealing with a generic concern (auth, retry, parsing, formatting)?
│   └── Almost certainly extract. These are the canonical reusable sub-workflows.
└── Is this chunk doing one HTTP call with no logic around it?
    └── Don't extract. Extra workflow boundary for nothing.
When 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/output contract), with the logic staying inline. Group it where the section forms a valid group (connected, single entry/exit); 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.
即将编写一段逻辑?
├── 这段逻辑有可能在其他工作流中被用到吗?
│   ├── 是 → 提取为子工作流
│   └── 否 → 保持内联
├── 这段逻辑包含超过5个节点且在概念上是一个独立功能吗?
│   └── 如果需要可测试性、隔离性或复用性,则提取;如果只是为了让画布更整洁,改为内联分组(见下文)。
├── 这段逻辑处理的是通用问题(认证、重试、解析、格式化)吗?
│   └── 几乎肯定要提取。这些是典型的可复用子工作流场景。
└── 这段逻辑只是执行一个无额外逻辑的HTTP调用吗?
    └── 无需提取。额外的工作流边界毫无意义。
如果唯一的动机是让画布更整洁(而非复用、隔离、可测试性或作为Agent工具),那么画布节点分组是仅用于提升可读性的工具:速度更快(每次调用无需子执行)且更简单(无需输入/输出契约),逻辑仍保持内联。仅当该部分形成有效分组(连接、单入口/出口)时才进行分组;如果分支过多,可拆分为更小的分组或保持不分组(用便签注释即可,无需分组)。仅当确实需要复用、隔离、独立测试或作为Agent工具时,才提取为子工作流。详情请参阅
n8n-workflow-lifecycle-official
中的“可读性”部分。

Stateless vs. stateful sub-workflows

无状态与有状态子工作流

Both are first-class. The choice is about intent and encapsulation.
两者都是一等公民。选择哪种取决于意图和封装需求。

Stateless

无状态

Takes input, returns output. No I/O outside the inputs/outputs. Default for pure logic.
Examples:
  • Parse RFC2822 date
    (tag
    subworkflow
    ). Input: date string. Output: ISO date or error.
  • Compute MRR from subscription
    (tag
    subworkflow
    ). Input: subscription object. Output: MRR number.
  • Format invoice as HTML
    (tag
    subworkflow
    ). Input: invoice data. Output: HTML string.
When you need the logic again, call it without worrying about side effects firing.
接收输入,返回输出。除输入/输出外无其他I/O操作。纯逻辑默认采用此设计。
示例:
  • Parse RFC2822 date
    (标签
    subworkflow
    )。输入:日期字符串。输出:ISO日期或错误信息。
  • Compute MRR from subscription
    (标签
    subworkflow
    )。输入:订阅对象。输出:MRR数值。
  • Format invoice as HTML
    (标签
    subworkflow
    )。输入:发票数据。输出:HTML字符串。
当你再次需要该逻辑时,调用它即可,无需担心触发副作用。

Stateful (deliberate)

有状态(刻意设计)

Reads or writes external state behind a clean input/output contract. Comparable to a repository pattern: the sub-workflow abstracts the state operation so callers think in domain terms, not implementation.
Examples:
  • Get customer by id
    (tag
    customer
    ). Input: id. Output: customer object or
    { ok: false, error: 'not_found' }
    . Reads the DB.
  • Write customer billing record
    (tags
    customer
    ,
    billing
    ). Input: record. Output:
    { ok: true, id }
    . Writes the DB.
  • Append audit event
    (tag
    audit
    ). Input: event. Output:
    { ok: true, eventId }
    . Writes to a logging store.
  • Send to on-call
    (tag
    notification
    ). Input: channel, message. Output:
    { ok: true, messageId }
    . Calls Slack/SMTP.
The point of building these as sub-workflows:
  • Callers think in domain terms (
    get customer by id
    ), not in storage (
    SELECT * FROM customers ...
    ).
  • Swap the underlying store/API behind it (Postgres → Supabase, native node → HTTP) without touching callers.
  • Idempotency, retry, and validation become the sub-workflow's responsibility, centralized in one place.
What to avoid is accidental state: a sub-workflow named/described 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.
通过清晰的输入/输出契约读取或写入外部状态。类似仓储模式:子工作流抽象状态操作,使调用方关注领域术语而非实现细节。
示例:
  • Get customer by id
    (标签
    customer
    )。输入:ID。输出:客户对象或
    { ok: false, error: 'not_found' }
    。读取数据库。
  • Write customer billing record
    (标签
    customer
    billing
    )。输入:记录。输出:
    { ok: true, id }
    。写入数据库。
  • Append audit event
    (标签
    audit
    )。输入:事件。输出:
    { ok: true, eventId }
    。写入日志存储。
  • Send to on-call
    (标签
    notification
    )。输入:渠道、消息。输出:
    { ok: true, messageId }
    。调用Slack/SMTP。
将这些构建为子工作流的意义:
  • 调用方关注领域术语(如“根据ID获取客户”),而非存储细节(如
    SELECT * FROM customers ...
    )。
  • 无需修改调用方即可替换底层存储/API(如从Postgres切换到Supabase,从原生节点切换到HTTP)。
  • 幂等性、重试和验证成为子工作流的职责,集中在一处处理。
要避免的是“意外”状态:例如命名/描述为纯逻辑的子工作流却悄悄写入日志表。这会让合理假设它可以安全重试或组合的调用方陷入困境。要么将副作用纳入契约(重命名、文档说明、返回结果),要么将其移出。

When to extract

何时提取

The two main signals:
主要有两种信号:

1. Conceptual coherence

1. 概念连贯性

When a chunk of nodes does one logical thing, even unreused, extraction can be worth it for:
  • Testability. Run the sub-workflow on its own with pinned data.
  • Replaceability. Swapping implementations doesn't ripple to callers.
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/output contract). Extract when you also want testability, replaceability, or reuse.
即使某段节点逻辑未被复用,但它在概念上是一个独立功能,提取为子工作流仍有价值:
  • 可测试性。可以使用固定数据单独运行子工作流。
  • 可替换性。替换实现不会影响调用方。
仅为可读性不是提取的理由:节点分组可以更低成本地将五个节点折叠为一个带标签的框(无需子执行,无需输入/输出契约)。仅当同时需要可测试性、可替换性或复用性时才提取。

1.5 The fire-and-forget audit-log pattern

1.5 即发即弃审计日志模式

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.
A deliberately stateful audit-log sub-workflow invoked with
Execute Workflow
's
waitForSubWorkflow: false
so the caller doesn't block on the write.
Caller ──→ [Execute Workflow: DB audit log]
              { title: 'Email Confirmation Received',
                description: <serialized data> }
              waitForSubWorkflow: false
              ↓ (caller continues immediately)
        ──→ [Continue with next step]
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.
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.
Why it's valuable:
  • Observability for free. Per-execution timeline when something goes wrong.
  • No coupling. Implementation (DB, Slack, both) can change without touching callers.
  • Async by default.
    waitForSubWorkflow: false
    means the audit doesn't slow the main workflow.
The audit-log workflow is the right kind of stateful sub-workflow. The side effect is the point.
此处以审计日志为例说明即发即弃的有状态模式。除非用户要求,否则不要在工作流中添加审计日志。该模式(异步触发子工作流,不等待其完成)适用于任何辅助观测场景:指标、通知等。
调用
Execute Workflow
时设置
waitForSubWorkflow: false
,触发刻意设计的有状态审计日志子工作流,调用方无需等待写入完成即可继续执行。
调用方 ──→ [Execute Workflow: DB audit log]
              { title: 'Email Confirmation Received',
                description: <序列化数据> }
              waitForSubWorkflow: false
              ↓ (调用方立即继续)
        ──→ [继续执行下一步]
子工作流接收标题和描述,写入日志表(或Slack,或两者都写),然后返回。调用方无需等待。审计日志是辅助观测,而非关键路径。
当用户要求时,在每个有意义的状态转换时触发一次(如“收到邮件确认”、“用户已验证”、“处理开始”、“资格判定完成”),以便从日志中重建时间线。
其价值在于:
  • 免费获得可观测性。当出现问题时,可查看每次执行的时间线。
  • 无耦合。实现方式(数据库、Slack或两者)可以随时更改,无需修改调用方。
  • 默认异步
    waitForSubWorkflow: false
    意味着审计不会拖慢主工作流。
审计日志工作流是正确的有状态子工作流示例,其副作用就是核心目的。

1.7 The middleware pattern

1.7 中间件模式

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.
Webhook
  → [Verify JWT]    # decode + validate; 401 on failure
  → [Rate limit]    # check + bump counter; 429 on failure
  → IF (all middleware ok)
    → Main handler logic
    → Respond 200
  → ELSE → Respond with the 4xx the middleware returned
Canonical 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.
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.
Pairs with
n8n-error-handling-official
for 4xx/5xx response shapes and
n8n-credentials-and-security-official
for the underlying secret handling.
当Webhook工作流具有API形态时,可将其视为API。子工作流成为中间件:小型无状态函数,在主处理程序之前运行,要么通过验证继续执行,要么返回4xx错误终止流程。
Webhook
  → [Verify JWT]    # 解码+验证;验证失败返回401
  → [Rate limit]    # 检查+更新计数器;触发限流返回429
  → IF (所有中间件验证通过)
    → 主处理程序逻辑
    → 返回200
  → ELSE → 返回中间件返回的4xx错误
典型示例:在n8n内部实现自定义JWT认证。
Verify JWT
(标签
subworkflow
)接收原始
Authorization
头,解码、验证签名和过期时间,返回
{ ok: true, user_id }
{ ok: false, status: 401, message }
。调用方根据
ok
值进行分支,验证失败时提前返回,验证通过时继续执行。
为何使用子工作流而非内联:每个需要认证的Webhook都调用同一个子工作流。只需在一处修改即可更换库、轮换签名密钥或添加刷新令牌逻辑。复用目标明确,契约简洁,所有API端点的错误响应格式一致。
配合
n8n-error-handling-official
处理4xx/5xx响应格式,配合
n8n-credentials-and-security-official
处理底层密钥管理。

2. Repetition pattern

2. 重复模式

You're about to build something you've built before. Stop. Search.
search_workflows({ query: 'date' })
search_workflows({ tags: ['customer'] })
search_workflows({ tags: ['subworkflow'] })
If 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.
你即将构建的内容之前已经构建过。停止操作,先搜索。
search_workflows({ query: 'date' })
search_workflows({ tags: ['customer'] })
search_workflows({ tags: ['subworkflow'] })
如果找到匹配项,就使用它。如果没有,就将其构建为子工作流并添加标签,以便后续搜索能找到它。标签约定(
subworkflow
、领域标签、
tool
)是实现这一点的关键。

Linear, long workflows are fine when most of the work is in sub-workflows

若大部分工作由子工作流完成,线性长工作流是可行的

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):
Webhook
  → Audit log (sub-workflow)
  → Validate
  → Audit log (sub-workflow)
  → IF auth ok
    → Look up user (or sub-workflow)
    → Audit log (sub-workflow)
    → Process step 1 (sub-workflow)
    → Audit log (sub-workflow)
    → Process step 2 (sub-workflow)
    → Audit log (sub-workflow)
    → Decide eligibility (sub-workflow)
    → Audit log (sub-workflow)
    → Send notification (sub-workflow)
    → Respond
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.
This is not the same as a 20-node workflow with 20 inline transformations. That's hard to read. The pattern above is fine because:
  • Each node has one purpose (call a specific sub-workflow).
  • Node groups mark sections, sticky notes annotate them (per
    n8n-workflow-lifecycle-official
    "Readability").
  • Inspecting a section means opening the sub-workflow it calls. That's encapsulation.
  • Orchestration logic at the top level is visible without reading implementations.
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).
如果工作流主要由子工作流调用和决策组成,即使包含20个以上节点也依然具有可读性。其结构如下(仅展示审计日志节点,因为它们是“实际步骤之间的辅助观测”的生动示例,仅当用户要求审计日志时才包含):
Webhook
  → 审计日志(子工作流)
  → 验证
  → 审计日志(子工作流)
  → IF 认证通过
    → 查询用户(或子工作流)
    → 审计日志(子工作流)
    → 处理步骤1(子工作流)
    → 审计日志(子工作流)
    → 处理步骤2(子工作流)
    → 审计日志(子工作流)
    → 判定资格(子工作流)
    → 审计日志(子工作流)
    → 发送通知(子工作流)
    → 返回响应
每个“逻辑步骤”都是一个子工作流调用。调用方是一个长但线性的流程,从上到下易于理解。具体逻辑都在子工作流中。
这与包含20个内联转换节点的工作流不同,后者难以阅读。上述模式是可行的,因为:
  • 每个节点只有一个用途(调用特定子工作流)。
  • 节点分组标记不同部分,便签进行注释(遵循
    n8n-workflow-lifecycle-official
    中的“可读性”要求)。
  • 查看某部分逻辑只需打开对应的子工作流。这就是封装性。
  • 顶层的编排逻辑无需查看具体实现即可一目了然。
如果你的工作流包含15个以上节点且大部分不是Execute Workflow调用和分支,那么应根据复用或测试需求提取更多子工作流,并将其余部分内联分组(节点分组)。

When NOT to extract

何时无需提取

  • One HTTP call with no logic. A sub-workflow that's just
    Execute Workflow → HTTP Request → return
    adds a boundary for nothing. Inline it.
  • 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.
  • Performance-critical hot paths. Each sub-workflow call adds latency (small, but real). For high-throughput workflows, profile before adding boundaries.
  • 仅包含一个无额外逻辑的HTTP调用。一个仅包含
    Execute Workflow → HTTP Request → return
    的子工作流只会增加无意义的边界。保持内联即可。
  • 与调用方的特定结构紧密耦合。如果该逻辑块仅接收调用方生成的深度嵌套输入,提取它只是将耦合转移到其他地方。应先修复数据结构。
  • 性能关键的热路径。每次子工作流调用都会增加延迟(虽小但确实存在)。对于高吞吐量工作流,添加边界前需先进行性能分析。

Search-before-build protocol

构建前搜索流程

When the user describes something multi-step or generic-sounding:
1. search_workflows with relevant tags and/or query (e.g. `tags: ['subworkflow']`, a domain tag, the operation keyword)
2. If candidates appear, fetch get_workflow_details on the top 1-3
3. Confirm fit by reading the inputs/outputs and (briefly) the body
4. If a fit exists → use it. Tell the user "I found `<name>`. Using that."
5. If no fit exists → build new and tag it (`subworkflow`, domain, `tool`) so the next search finds it
The "tell the user" step matters. They benefit from knowing what's already in their library.
If 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/MCP_ACCESS_PER_WORKFLOW.md
.
当用户描述的内容是多步骤或通用场景时:
1. 使用相关标签和/或查询搜索工作流(例如`tags: ['subworkflow']`、领域标签、操作关键词)
2. 如果找到候选结果,获取前1-3个工作流的详细信息(`get_workflow_details`)
3. 通过阅读输入/输出结构和(简要)工作流主体确认是否匹配
4. 如果找到匹配项 → 使用它。告知用户“我找到了`<name>`,将使用该子工作流。”
5. 如果没有匹配项 → 构建新的子工作流并添加标签(`subworkflow`、领域标签、`tool`),以便后续搜索能找到它
“告知用户”这一步很重要。他们会从中了解到库中已有的资源。
如果你预期能找到的工作流未出现,最常见的原因是未启用每个工作流的MCP访问权限。详情请参阅
n8n-workflow-lifecycle-official
中的
references/MCP_ACCESS_PER_WORKFLOW.md

Sub-workflow inputs and outputs

子工作流的输入与输出

Sub-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.
子工作流由
Execute Workflow Trigger
节点触发。触发器声明输入模式。调用方通过
Execute Workflow
传入数据,子工作流返回最后一个节点的输出结果。

Always use "Define Below" with explicit fields

始终使用“Define Below”模式并配置明确字段

The
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.
Shape:
ts
const subTrigger = trigger({
    type: 'n8n-nodes-base.executeWorkflowTrigger',
    config: {
        parameters: {
            workflowInputs: {
                values: [
                    { name: 'list_of_ids', type: 'array' },
                    { name: 'include_transcript', type: 'boolean' },
                    { name: 'session_id', type: 'string' },
                ],
            },
        },
    },
})
Each 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.<field>
from anywhere downstream.
Pick 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.
Execute Workflow Trigger
有两种输入模式。默认使用“Define Below”模式并配置类型化字段。只有这种模式允许Agent工具(通过
fromAi()
)和任何结构化调用方传入值。如果没有声明字段,Agent将没有可填充的模式,子工作流也无法作为
toolWorkflow
正常使用。
示例结构:
ts
const subTrigger = trigger({
    type: 'n8n-nodes-base.executeWorkflowTrigger',
    config: {
        parameters: {
            workflowInputs: {
                values: [
                    { name: 'list_of_ids', type: 'array' },
                    { name: 'include_transcript', type: 'boolean' },
                    { name: 'session_id', type: 'string' },
                ],
            },
        },
    },
})
每个声明的输入都会成为调用方可填充的类型化参数。在工作流内部,可以通过
$json.list_of_ids
等方式访问,或在下游任何位置通过
$('When Executed by Another Workflow').first().json.<field>
访问。
需谨慎选择类型(
string
number
boolean
array
object
)。模型在填充Agent工具参数时会使用这些类型,用户在配置调用方时也会依赖这些类型。

Exception 1: passthrough mode for binary

例外1:处理二进制数据时使用直通模式

If the sub-workflow needs to receive binary (image, file, PDF),
Define Below
doesn't work because typed fields are JSON only. Switch to passthrough:
ts
const subTrigger = trigger({
    type: 'n8n-nodes-base.executeWorkflowTrigger',
    config: {
        parameters: {
            inputSource: 'passthrough',
        },
    },
})
In 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.
For sub-workflows that need binary AND are called by an agent, see
n8n-binary-and-data-official
references/AGENT_TOOL_BINARY.md
(agent tools can't pass binary directly).
如果子工作流需要接收二进制数据(图片、文件、PDF),“Define Below”模式无法使用,因为类型化字段仅支持JSON。此时切换到直通模式:
ts
const subTrigger = trigger({
    type: 'n8n-nodes-base.executeWorkflowTrigger',
    config: {
        parameters: {
            inputSource: 'passthrough',
        },
    },
})
在直通模式下,子工作流会原样接收调用方的所有项,包括
binary
槽。缺点:没有类型化输入模式,因此Agent工具无法通过
fromAi()
传入参数。此模式适用于由其他工作流(而非Agent)调用且需要传递二进制数据的子工作流。
对于既需要接收二进制数据又要被Agent调用的子工作流,请参阅
n8n-binary-and-data-official
中的
references/AGENT_TOOL_BINARY.md
(Agent工具无法直接传递二进制数据)。

Exception 2: passthrough for sub-workflows with no inputs

例外2:无输入的子工作流使用直通模式

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.
When using passthrough specifically for the no-input case:
  • 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.
  • 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.
Agent-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/TOOLS.md
("zero
fromAi
parameters") applies directly.
“Define Below”模式要求至少声明一个字段。如果子工作流确实不需要任何输入(如“列出活跃凭证”工具、“当前计数”查询、任何零参数操作),则无法配置空模式,因此只能使用直通模式。
当为无输入场景使用直通模式时:
  • 在工作流主体开头添加一个“Set(编辑字段)”节点,设置为“Keep Only Set”模式且不配置任何字段。这会清除调用方的JSON数据,避免下游节点意外读取调用方传入的任意字段。如果不这样做,工作流主体会静默接收调用方转发的任何字段。
  • 在触发器上添加便签,说明该子工作流不需要输入。未来的读者(以及将其重新配置为Agent工具的人员)需要知道使用直通模式不是为了处理二进制数据,而是因为该子工作流设计为无输入。
无输入场景下仍可作为Agent工具使用:
toolWorkflow
接受输入映射为空的子工作流。Agent只需决定是否调用即可。
n8n-agents-official
中的
references/TOOLS.md
(“零
fromAi
参数”)模式直接适用。

Other conventions

其他约定

  • Document inputs and outputs in the workflow
    description
    .
    Field names, types, purpose. The description is what callers (humans and agents) read for the contract.
  • 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.
  • 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.
  • Use a final Set / 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 <thing>
    .
  • 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.
    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.
For sub-workflows wired as agent tools specifically, see
n8n-agents-official
references/SUBWORKFLOW_AS_TOOL.md
.
  • 在工作流的
    description
    中记录输入与输出
    。包括字段名称、类型和用途。描述是调用方(用户和Agent)了解契约的依据。
  • 返回一致的结构。对于预期的失败(如解析错误),返回
    { success: false, error: '...' }
    而非抛出错误。调用方无需包装错误输出即可进行分支处理。
  • 一旦有调用方,输入模式即为契约。添加可选字段是安全的。重命名或删除字段需谨慎:枚举所有调用方(通过
    search_workflows
    搜索子工作流名称+手动检查),在同一变更中迁移所有调用方,并在发布前通过
    validate_workflow
    get_workflow_details
    验证。此处的静默故障难以检测,因为n8n不会对未识别的输入字段报错。子工作流只会将其视为
    undefined
    ,而调用方毫不知情。
  • 使用最终的Set/编辑字段节点定义返回结构。这是可选的,但有时是必需的(当最后一个计算节点包含冗余字段时),对子工作流而言是良好实践,即使并非严格必需。它在边界处明确了返回契约,读者只需查看一个节点即可了解API。这是
    n8n-expressions-official
    中Set节点反模式的合理例外:子工作流最后一个节点的隐式消费者是所有调用方,因此Set节点作为明确的API边界是合理的。将其命名为
    Return
    Return <thing>
  • 返回自然结构,而非存储结构。拥有数据表、S3文件或任何存储层的子工作流应向调用方隐藏其存储表示。数组返回为数组,对象返回为对象,日期返回为ISO字符串,无论底层存储是JSON序列化文本还是其他内部格式。返回契约是接口,存储布局是实现细节
常见错误:子工作流有“新鲜”路径(刚生成的数据,自然结构)和“缓存”路径(刚从
_object
列读取的数据,仍为序列化字符串)。错误做法:将新鲜路径序列化以“匹配”缓存路径。正确做法:解析缓存路径,使两者都返回自然结构。调用方无需知道获取的是哪种路径。
专门作为Agent工具的子工作流,请参阅
n8n-agents-official
中的
references/SUBWORKFLOW_AS_TOOL.md

Calling sub-workflows:
Execute Workflow
modes

调用子工作流:
Execute Workflow
模式

Two settings on the caller-side
Execute Workflow
node beyond inputs/workflowId:
  • 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.
  • 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.
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/aggregate 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.
For the polling-after-fire-and-forget pattern, see
references/SUBWORKFLOW_PATTERNS.md
"Fire-and-forget parallelization".
调用方的
Execute Workflow
节点除了输入和workflowId外,还有两个设置:
  • mode
    默认值为
    'all'
    :子工作流
    运行一次
    ,接收所有N个项作为输入。项仍会像其他工作流一样逐节点流转。设置
    mode: 'each'
    会使子工作流独立运行N次,每个项对应一次执行。对于主体仅正常处理项的子工作流,两种模式效果相同。差异在于当子工作流主体假设仅处理一个项时(每次执行的聚合、“这是要操作的唯一客户”逻辑、应针对每个输入触发一次的最终写入)。
    mode: 'each'
    符合该假设,
    mode: 'all'
    会破坏该假设。当需要逐项迭代时,优先使用
    mode: 'each'
    而非子工作流内部的Loop Over Items节点。
  • **
    waitForSubWorkflow
    **默认值为
    true
    。设置
    options.waitForSubWorkflow: false
    会触发调用并立即继续执行,子工作流在后台运行。调用方的下游节点不会收到返回数据。
mode: 'each'
+
waitForSubWorkflow: false
n8n提供的唯一真正并行化方式:触发N次子工作流执行,无需等待,并发运行(仍受限于每个实例的并发限制和每次调用的开销)。适用于“启动N个独立任务,稍后轮询/聚合结果”的场景。例如:为每个项启动一个长时间运行的任务,在数据表中跟踪每个任务的状态,然后循环直到所有行标记为完成或超时。
关于即发即弃后的轮询模式,请参阅
references/SUBWORKFLOW_PATTERNS.md
中的“即发即弃并行化”部分。

Reference files

参考文件

FileRead when
references/SUBWORKFLOW_PATTERNS.md
mode: 'all'
vs
'each'
default, splitting by input shape (binary/passthrough vs Define Below), fire-and-forget parallelization with Data Table polling
references/NAMING_AND_DISCOVERY.md
Naming and tagging a new sub-workflow, searching for existing ones, the tag convention
文件阅读场景
references/SUBWORKFLOW_PATTERNS.md
mode: 'all'
vs
'each'
默认值、按输入结构拆分(二进制/直通vs Define Below)、使用数据表轮询的即发即弃并行化
references/NAMING_AND_DISCOVERY.md
新子工作流的命名与标签、搜索现有子工作流、标签约定

Anti-patterns

反模式

Anti-patternWhat goes wrongFix
Duplicating the same date-parsing nodes in three workflowsBug fixes happen in two places, miss the thirdExtract to a single
Parse <format> date
sub-workflow (tag
subworkflow
) once
Building a new sub-workflow without searchingLibrary grows duplicates, and future searches find bothAlways
search_workflows
first
Sub-workflow named/described as pure that quietly writes to a log tableCallers can't reason about retry or idempotency, side effect ambushes themEither make the side effect part of the contract (rename, document, return its result) or move it out
Sub-workflow with no
description
Won't be found in future searches, nobody knows what it doesSet
description
with input/output shape and purpose
Sub-workflow named
Helper 3
Name doesn't tell anyone what it doesVerb-first descriptive name (
Parse RFC2822 date
), see
n8n-workflow-lifecycle-official
NAMING_CONVENTIONS.md
Untagged sub-workflowWon'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
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")
Passthrough trigger for a zero-input sub-workflow without a Set-to-clear node and explanatory stickyBody silently reads stray fields from whatever the caller forwarded; future readers think passthrough is for binaryAdd a
Set
("Keep Only Set", no fields) at the top of the body and a sticky on the trigger noting no inputs are expected
Sub-workflow called as an agent tool that expects binary inputAgent tools can't pass binary directlySee
n8n-binary-and-data-official
AGENT_TOOL_BINARY.md
for the right pattern
30-node workflow with no extractionHard to read, hard to test, hard to replaceExtract logical sections into sub-workflows
反模式问题所在修复方案
在三个工作流中重复相同的日期解析节点漏洞修复可能遗漏其中一处,导致副本产生差异提取为单个
Parse <format> date
子工作流(标签
subworkflow
未搜索就构建新子工作流库中产生重复项,后续搜索会找到多个结果始终先执行
search_workflows
命名/描述为纯逻辑的子工作流却悄悄写入日志表调用方无法判断是否可重试或幂等,副作用超出预期要么将副作用纳入契约(重命名、文档说明、返回结果),要么将其移出
子工作流无
description
后续搜索无法找到,无人知晓其用途设置
description
,包含输入/输出结构和用途
子工作流命名为
Helper 3
名称无法说明其功能使用动词开头的描述性名称(如
Parse RFC2822 date
),详情请参阅
n8n-workflow-lifecycle-official
中的
NAMING_CONVENTIONS.md
子工作流未添加标签无法通过
tags
过滤找到,未来无法检索
创建后立即通过
update_workflow
addTags
添加标签(
subworkflow
、领域标签、
tool
Execute Workflow Trigger
设置为直通模式,但并非处理二进制数据或刻意设计为无输入
无类型化模式导致Agent工具无法通过
fromAi()
填充参数,结构化调用方无法干净地传入值
使用“Define Below”模式并声明
workflowInputs.values
(每个字段包含名称+类型)。例外情况是接收二进制数据的子工作流和确实无输入的子工作流(见“例外2”)
无输入子工作流使用直通模式,但未添加清除用的Set节点和说明性便签工作流主体会静默读取调用方转发的任意字段;未来读者会认为直通模式是为了处理二进制数据在工作流主体顶部添加一个“Set(仅保留已设置字段,无字段)”节点,并在触发器上添加便签说明无需输入
作为Agent工具调用的子工作流期望接收二进制输入Agent工具无法直接传递二进制数据请参阅
n8n-binary-and-data-official
中的
AGENT_TOOL_BINARY.md
获取正确模式
30个节点的工作流未进行任何提取难以阅读、测试和替换将逻辑部分提取为子工作流