
做了两年多n8n工作流我见过太多人卡在“会用节点”但“不会写表达式”这个阶段。节点拖拖拽拽谁都会但一旦遇上数据清洗、条件分支、动态参数这类场景立刻露怯——要么在Code节点里写一堆for循环要么把表达式糊成一长串让人看都不想看。其实n8n内置方法和变量体系早就把这块路铺好了只是很少有人系统讲清楚它们之间的关系。这篇教程就专门拆这两个东西n8n里到底有哪些内置方法、哪些变量它们各自在什么场景下发力以及怎么组合起来设计一套真正“如虎添翼”的工作流。适合刚接触n8n表达式的新手也适合写了几个工作流但总觉得代码块太多、想精简的老手。1. 从一次“翻车”说起为什么变量与内置方法是n8n的命门先讲个我自己踩过的坑。早先帮一个客户搭客户线索自动同步流程从Webhook接收表单数据经过数据清洗最后写入表格。图倒是很漂亮但我在“数据格式化”节点里写表达式时直接写了{{$json.body.email}}——结果一执行对方发过来的字段名是Email大小写对不上整条数据流直接断掉。后来改成{{ $json.body.Email || $json.body.email }}才解决。这件事给我的教训很直接n8n节点的拼装只是骨架变量与内置方法才是让数据真正“流动”起来的血液。如果你不理解每个节点输出什么、表达式能拿到什么、内置方法怎么把一个数据变成另一个结构那工作流做再复杂也只是空中楼阁。1.1 变量体系全览全局、环境、工作流与节点级n8n里的变量不是一个笼统的概念它其实分四个层级搞清楚这四个层级你就能知道“在这个位置到底该用什么”。第一层是环境变量也就是$env。这些变量通常定义在部署层面比如用Docker跑n8n时通过N8N_ENCRYPTION_KEY、WEBHOOK_URL这类环境变量注入。在表达式里可以通过$env.MY_VARIABLE读取。我一般把API密钥、数据库连接串、第三方服务的端点地址放在这里而不是硬编码进工作流因为一旦要换环境改一处就行。第二层是全局变量对应n8n的Settings里的Variables功能。这一层最适合放“跨工作流共享的配置”比如统一的官方电话、企业统一的社会信用代码、固定的折扣比例。用{{ $vars.discount_rate }}就能在任何工作流里引到比每个流程里单独写死要优雅得多。第三层是工作流级静态数据也就是$getWorkflowStaticData()。这玩意儿很多人不熟悉它允许你在同一个工作流的多次执行之间保存少量数据比如记录上次同步的游标、累计计数、去重用的ID集合。它不像数据库那样强大但解决“我这次执行需要知道上次执行到哪儿”这种问题简直不要太合适。第四层才是大家天天打交道的节点级数据$json、$node、$input、$items这些。它们描述的是当前这条数据流上这个节点能从上游拿到什么、命令能访问哪个节点的输出。这一层是n8n内置方法和变量的主战场后面的实操大部分都在这里进行。1.2 内置方法到底是什么一张图拆解n8n数据流转我经常在交流群里看到有人问“内置方法是什么”其实用一句话说内置方法就是n8n表达式引擎里预置好的一批函数和对象让你不用写完整JavaScript也能完成取值、转换、判断、格式化这些常见动作。你可以把n8n的数据流想象成一条传送带。传送带上的每一件“包裹”就是一条Item包裹里装着json数据、二进制数据、还有索引信息。n8n的表达式就是你在传送带旁边伸出一只手通过$json取出当前包裹里的某个字段用$node去翻之前经过的某个节点留下的记录再用JSON.parse这类内置方法把包裹里一坨字符串解开重新打包。整个过程中你不需要懂JS也能做但如果你稍微理解一点JavaScript再叠加n8n预置的这些方法能发挥的余地会成倍增加。理解这条“传送带”很关键因为n8n里超过70%的工作流bug都不是节点配置错了而是使用者根本没搞明白当前节点上下文里到底能拿到哪些数据。比如你用“Edit Fields”节点做了字段映射下一个节点里$json的内容就和上游完全不一样你用了Loop节点每个循环里$json其实是数组里的一项而不是整个数组。这些经验变量和内置方法体系里都有对应的规则只是没人给你串讲所以总觉得磕磕绊绊。2. 内置方法核心函数拆解每个表达式背后的逻辑说实话n8n的官方文档把内置方法的语法写得很全但读起来像天书。这里我不罗列全部只挑日常工作中使用频率最高的那一批告诉你它们到底是干嘛的、为什么这么设计、怎么用才不踩坑。2.1 常用内置方法的定位与用法先讲最基础的“四大金刚”$json、$node、$input、$items。$json是当前节点输入数据的JSON部分绝大多数表达式的主力。记住一个细节在遇到分支节点之后$json的字段结构取决于你连接的是哪个出口而不是上游节点。很多新人从IF节点的false出口连出来结果还在attempt用true出口的字段debug半天才反应过来。$node[节点名]用来访问指定节点的输出。这个非常强大你可以在当前节点里引用流程中任意一个节点的执行结果。比如在“聚合”节点后面你想看看最初Webhook节点收到了什么原始数据直接用$node[Webhook].json.body就能拿到。但要注意如果这个节点在本次执行中没有被运行比如被分支跳过了拿到的就是undefined不会报错但会一路传播空值。$input代表当前节点的直接上游输出适合在写“这个节点到底吃了什么”时快速查看。它等价于在大多数场景下的$json区别在于$input更明确地指向“进入当前节点的数据”而$json有时候会被n8n在某些节点内部重新赋值。我习惯在Code节点里用$input.first().json取第一条在表达式里直接用$json各取所长。$items则是当前上下文可访问的全部Item数组配合索引使用。比如$items(0).json.name就是当前批次第一条数据的name字段。这在带有多条数据的循环或批量处理场景下特别有用你可以从不同索引的数据里拼出新的结构。除了这几个对象n8n还内置了一批方法比如$now返回当前时间、$executionId返回当前执行ID、$workflow返回工作流信息、$runIndex返回当前是第几次运行重试时很有用。它们看起来不起眼但组合起来就厉害了。比如我经常用$executionId拼进错误报警消息里方便快速追溯到具体某次运行日志。2.2 表达式函数速查字符串、数组、日期必备n8n表达式里可以直接调用一整套JavaScript内置函数这也是很多人觉得“n8n难道不是零代码吗”的疑问来源。其实它更像“低代码”简单场景拖节点复杂逻辑写表达式内置方法就是给你提供了一条平滑的过渡地带。字符串处理三件套toUpperCase、toLowerCase、replace。比如把用户输入的邮箱统一转小写再判断直接{{ $json.email.toLowerCase() }}。replace是正则的好帮手{{ $json.phone.replace(/[^0-9]/g, ) }}能把电话里的空格、横杠全部剥掉只留下数字这个我在电话号码清洗场景里用了无数次。数组处理就更多了map、filter、reduce、find。在n8n的表达式编辑器里你可以对一个数组字段做{{ $json.orders.map(o o.price).reduce((a,b) ab, 0) }}把子订单的价格全部累加出来。很多人在这一步就转向Code节点了其实用表达式完全能做而且可读性不差。日期处理上n8n在内置方法里封装了一个DateTime对象比如{{ DateTime.fromISO($json.start_time).plus({days: 1}).toISO() }}比原生JavaScript的Date对象好用得多。在写“计算30天后的截止日期”“判断当前是否在工作时间内”这些逻辑时用DateTime能省一半时间。还有一个容易忽略的宝藏JSON.parse和JSON.stringify。当你从Webhook收到一大段JSON字符串时没有parse之前它就是一个文本你没办法优雅地取里面的字段当你想把一个对象传给HTTP Request的body时不stringify就会得到[object Object]这样的天坑。这两个函数几乎出现在我每一个工作流里属于内置方法中的“生存技能”。3. 从“会写”到“会设计”变量与内置方法驱动的高阶工作流掌握了基础语法接下来进入真正好玩的部分怎么在工作流里灵活组合变量和内置方法设计出健壮、可维护、别人看了都竖大拇指的流程。这里我从三个最常见也最实用的场景展开。3.1 场景一Webhook 变量做动态条件分支假设你要做一个线索分发流程外部系统通过Webhook推送新线索字段包括company_size公司规模、industry行业、lead_score评分。你需要根据不同条件把线索分给不同销售组。第一步在Webhook节点后加一个“Switch”节点。Switch节点支持多条规则但很多人在这里只用固定等于遇到“大于某个数”就不知道怎么办。其实n8n的Switch完全可以写表达式作为规则值。例如规则1{{ $json.lead_score 80 $json.company_size 500 }}→ 输出“大客户组”规则2{{ $json.industry SaaS }}→ 输出“SaaS组”规则3默认 → “普通组”第二步在分支里需要使用同一个全局变量来标记线索来源。比如在Settings里定义了一个变量lead_source 流量通道你可以在后续的消息节点中通过{{ $vars.lead_source }}引用这样以后改来源名称只需要改设置不用每个分支都去改文案。这个场景的坑在于表达式里的大小写和空格。company_size是下划线lead_score也是下划线如果外部系统的字段名是companySize你直接用$json.company_size铁定拿不到。我一般在Webhook后面会跟一个“Edit Fields”节点把所有字段名统一成自己这边好用的命名规范再做判断。这一步相当于给下游建了一条干净的数据管道后续所有节点都省心。3.2 场景二循环聚合与数据清洗批量处理数据时n8n有两种套路一种是用Split Out把数组拆成多行再用Loop节点逐条处理另一种是把整个数组直接丢给Code节点一次性搞定。用内置方法可以把这两种套路揉在一起做得很漂亮。比如你要从一个接口拉回一批订单每条订单里包含items数组你需要把所有的items取出来、计算每件商品的税费最后汇总成一个新的数组。我的做法是先用Split Out节点把订单拆开拿到每条订单的items子数组后再用一个“Code”节点配合n8n的$input.all()方法遍历。Code节点里可以这么写const results []; for (const item of $input.all()) { const orderItems item.json.items || []; for (const product of orderItems) { results.push({ order_id: item.json.order_id, product_name: product.name, tax: product.price * 0.06, total: product.price * 1.06 }); } } return results;注意这里我用了item.json.items而不是item.json[items]效果一样但后者在处理字段名带特殊字符时更稳。这种写法比嵌套好几个Loop节点要简洁太多也方便调试。如果你不想用Code节点纯表达式也能做但可读性会差一些。比如在“Aggregate”节点里你可以对$json.items做{{ $json.items.length }}取数量用{{ $json.items.map(i i.price).reduce((a,b)ab,0) }}汇总金额。这些都是内置方法直接支持的。数据清洗的另一块是空值和类型。现实世界的数据永远没有理想的干净订单金额可能是字符串100也可能是数字100还可能是null。我在Code节点的开头永远会加上一行防御性代码const price Number(item.json.price) || 0;这样无论来的是字符串、整数、浮点数还是没值都能得到一个安全的数字。这个习惯帮我省了太多排查时间。3.3 场景三HTTP请求中的动态参数与Token刷新这是最容易暴露变量使用水平的场景。你要调用一个第三方API发送POST请求body里要带上当前时间戳、上游传下来的用户ID还要在Authorization头里带上一个动态更新的access_token。很多人的第一版是在HTTP Request节点的Headers里直接填死Token。但Token是会过期的一过期整个自动化就死给你看。合理做法是使用n8n的Credentials功能把API的密钥信息统一管理然后在HTTP节点里通过{{ $credentials.apiKey }}引用。body里的动态参数用表达式做比用节点拼接更高效。比如{ user_id: {{ $json.user_id }}, timestamp: {{ DateTime.now().toMillis() }}, sign: {{ $json.user_id - $env.SIGN_KEY }} }这里DateTime.now().toMillis()是n8n内置方法里面的一个类方法直接取当前Unix时间戳省得自己再调原生Date函数。$env.SIGN_KEY则是从环境变量里读取的签名密钥不会出现在工作流内容里安全性好得多。如果要实现“Token快过期时自动刷新”我一般是用一个独立的子工作流或者在主流程前面加一个“Execute Workflow”节点去单独刷新Token把结果存储到工作流静态数据$getWorkflowStaticData()里。后面每个HTTP请求前先用{{ $getWorkflowStaticData().token }}读出来如果为空或过期再进行刷新。这比在每个请求里重复调用登录接口要省太多时间也避免API限流把你整个流程卡死。4. 常见问题与避坑实录接触n8n的这两年多我在变量和内置方法上踩过的坑、帮别人排查过的问题没有一百个也有八十个。这里挑几个最高频的整理成速查表并附带排查思路希望能帮你少走弯路。4.1 变量作用域与命名冲突现象同一个变量名在A节点能取到值在B节点取到undefined或者拿到了一个很奇怪的值。原因n8n的节点级数据是作用在Item上的不是全局可随意访问的。更隐蔽的问题是变量命名冲突你从Webhook拿到的字段叫status自己在Edit Fields里也给一个字段取名叫status然后又用$vars定义了一个全局变量status。三个来源、三个值表达式的优先级会让你根本分不清拿的是哪个。解决思路我给自己定了一个很简单粗暴的命名约定$json字段尽量全小写下划线且不与任何变量同名。$vars里的全局变量统一加前缀比如app_或site_。环境变量$env的命名用全大写加下划线。这样一眼就能在表达式里区分出来{{ $json.order_id }} // 当前item的订单号 {{ $vars.app_name }} // 全局应用名 {{ $env.SIGN_KEY }} // 环境变量签名密钥这套约定听起来简单实战中极其管用。尤其是工作流规模变大以后避免踩到命名冲突就是在给自己省时间。4.2 空值与类型转换的坑现象{{ $json.name }}明明有值但显示出来是null或者你想把一个数字拼到字符串里结果出现了undefined。原因n8n表达式对undefined和null的处理有时候会让人迷惑。当一个字段不存在时表达式直接返回undefined而不是空字符串。在模板字符串里就会变成你好undefined。另外很多外部API返回的数字是字符串比如10你拿来做大于0判断时会发现JavaScript的隐式类型转换并不总是按你预期走。排查步骤我一般按三步走先用“Show Data”节点把当前节点的完整输出打出来确认字段名和值到底长什么样。在表达式里主动做类型转换数字就Number(...)字符串就String(...)数组就Array.isArray(...)先判断一下。对可能出现空值的地方统一兜底比如{{ $json.age || 0 }}或者{{ $json.email || 未填写 }}。这个习惯在数据来源混杂的流程里特别有价值。你永远不知道上游系统什么时候会抽风不发某个字段提前兜底好过上线之后半夜收报警。4.3 排查技巧与调试姿势n8n的调试手段其实很够用只是很多人不会用。我这里分享三个我每天都会用到的调试姿势。第一个是“Show Data”节点大法。很多初学者觉得这个节点多此一举其实它是排查问题的最好起点。每连完一个节点加一个Show Data看一眼输出结构确认字段名大小写、嵌套层级、数据类型都对了再进行下一步。我自己的习惯是复杂流程里每隔两三个节点就放一个Show Data调试完再一次性删掉。第二个是表达式编辑器里的实时预览。n8n的表达式编辑框右侧会实时显示当前执行环境下这个表达式的值不需要跑完整条流程。写表达式时养成习惯在提交之前先在预览栏看一眼结果是否符合预期。这一步能帮你当场发现类型错误而不是等到执行到那个节点才爆红。第三个是Code节点里临时加日志。有时候表达式觉得没问题实际跑起来却不对我就会在Code节点里写一个console.log(JSON.stringify($input.all()))然后看n8n的执行日志。这比盲猜快得多尤其是上游数据来自第三方API时先确认原始数据长什么样再谈后面的字段映射才有意义。我这里再补一个很多人会踩的坑节点名称带空格时$node[Node Name]这种引用方式中括号内部必须用双引号不能省。我见过太多人在表达式里写$node[Salesforce].json然后怎么都取不到值就是因为没有给节点名加上引号。5. 进阶心得把工作流当代码来维护写了这么多场景和问题最后还想聊一个比较抽象但很重要的心得把n8n工作流当成真正的代码项目来维护而不是“拖出来的图”。这听起来有点反直觉因为n8n的卖点就是可视化但在变量和内置方法用多了以后你会发现工作流的复杂度主要来自数据流逻辑而不是节点连线。我在团队里推行的做法有三条。第一条是给关键节点写备注尤其是那些用了复杂表达式或者内置方法的节点。备注里写清楚这个表达式在算什么、数据来源是什么、返回值类型是什么三个月后你回来看工作流依然能快速恢复上下文。第二条是做版本管理。n8n企业版本身支持版本历史但如果是自托管或者社区版我有一个土办法对非常重要的生产工作流每次大改动前手动导出一份JSON存档。哪怕只是改了一行表达式也存档一次。成本极低但能在上线后出问题时一键回滚这比什么都重要。第三条是统一的错误处理。千万别让错误一路抛到底。用“IF”节点或者n8n的“Error Trigger”在关键步骤把异常截住配合变量$executionId拼出包含完整上下文的错误消息发送到企业微信、钉钉或者Slack。这样出了问题你第一时间看到的是有效信息而不是一堆干巴巴的执行失败日志。说回变量和内置方法我个人在实际操作中的最大体会是它们不是零代码的敌人反而是让n8n变得更强的那块跳板。你不需要成为JavaScript高手只要掌握十几个常用的内置方法、分清楚四个变量层级、养成数据验证的习惯就已经能覆盖九成以上的自动化场景。剩下的一成等真正遇到再学也不迟——但你现在掌握的这套体系会让你遇到时完全不慌。