n8n内置方法与变量详解:用表达式把工作流做瘦

发布时间:2026/10/9 21:58:14
n8n内置方法与变量详解:用表达式把工作流做瘦
做自动化流程做了这么长时间我越来越觉得n8n真正拉开差距的地方不在你看过多少节点类型、下载过多少模板而在于你对内置方法和变量的理解深度。很多人把工作流搭起来能跑就万事大吉数据在里面转了一圈又一圈字段名改一下、格式转一下全在手动拖节点去凑合节点倒是拖了十几个其实表达式里三五行代码就能把活干完。这篇就专门把n8n的内置方法和变量掰开揉碎讲一遍顺便给一个接地气的实战案例适合正在用n8n搭流程、却总觉得流程越搭越臃肿的人。先说个我一直坚持的观点工作流的复杂度不是看节点数量而是看数据在节点之间流动时需要被处理多少次。如果你能在一个表达式里完成清洗、转换、统计就不必为每件事单独拖一个节点。内置方法和变量就是你在表达式里最顺手的工具也是把工作流从“能跑”推到“跑得漂亮”的关键。1. 先把底子打好内置方法、变量和表达式的边界1.1 表达式是工作流的“胶水”在n8n里所有带花括号的输入框都支持表达式。表达式会在工作流运行到该节点时被计算把动态值替换进去。打个比方你在HTTP Request节点里写URLhttps://api.example.com/users/{{ $json.id }}这个{{ $json.id }}就是表达式。没有它你可能得用Set节点先取一次id再拼一个字符串。这种拼接场景我再熟悉不过了尤其是对接十几个外部系统的时候页面路径、查询参数、请求头全都要靠表达式动态生成表达式的作用就是当工作流的胶水把静态配置和动态数据黏在一起。表达式除了能引用数据还能直接调用方法。方法就是挂在数据上的函数比如字符串的.toUpperCase()、数组的.filter()。方法和变量往往结合在一起用因为你总得先有一个变量保存数据再在数据上调用方法做处理。我见过不少从低代码平台转过来的朋友总觉得n8n的表达式是个“玄学”其实它的核心就是JavaScript的一个子集。你不需要成为编程高手但知道有哪些方法可用、哪些变量可读就已经能解决大部分场景。剩下的就是遇到问题查表达式编辑器里的自动补全补全会告诉你当前对象支持哪些操作。1.2 内置方法一半是JavaScript家底一半是n8n专属n8n表达式里可以直接使用大多数JavaScript原生方法。字符串、数组、对象、数值、日期你能想到的基础操作都能做。此外n8n自己也内置了一些辅助对象比如$now表示当前时间$items可以跨节点取数据$jmesPath提供JMESPath查询能力。我在实际项目中用得最多的是原生方法和少量n8n专属变量掌握规律后基本不需要再回头翻文档。这里要区分“方法”和“属性”。abc.length是属性不需要括号abc.toUpperCase()是方法需要括号有的方法还要传参数。很多人表达式报错就是括号没写或参数没给全。另一个容易忽略的点是表达式和普通JavaScript一样区分大小写touppercase()写错了肯定报错。好在n8n的表达式编辑器有代码提示你输入一个点号之后能看到当前对象支持的所有方法和属性这是n8n比较好用的地方我建议你多利用这个提示比死记硬背效率高得多。变量可以理解为有名字的数据容器。在n8n里变量分几种全局变量、环境变量、当前节点数据和上一个节点数据。它们的作用域不同后面会专门讲。理解变量的关键在于理解“生命周期”谁创建了它、谁能读到它、它什么时候消失。把这个问题想清楚很多奇怪的报错就能一眼看穿。2. 高频内置方法拆解字符串、数组、日期和数据转换2.1 字符串方法字段清洗的第一道工序做自动化最烦的就是数据字段不规整。比如用户提交的姓名带空格、电话号带有横线、邮箱大小写不统一这些都需要清洗。n8n的字符串方法完全可以胜任trim()去掉首尾空格。如{{ $json.name.trim() }}。toUpperCase()/toLowerCase()转换大小写。如{{ $json.email.toLowerCase() }}。replace()替换指定内容支持正则表达式。split()按分隔符拆成数组。如{{ $json.tags.split(,) }}注意split结果是一个数组后面可以继续调用数组方法。substring()/slice()截取子串适合从身份证号或订单号里提取片段。includes()、startsWith()、endsWith()做条件判断在IF节点或Filter节点里很常用。我举一个很典型的例子。假设Webhook传入的$json.mobile是 138-1234-5678 带空格带横线你想把存进CRM的号码统一成纯数字。表达式写{{ $json.mobile.trim().replace(/-/g, ) }}这个表达式就同时完成了去空格和去横线不需要额外拖两个节点。关键是replace(/-/g, )里的正则写法很多人容易把/-/g写成/-/那样只会替换第一个匹配项替换不干净。踩过一次坑你就记住了。而且要注意n8n表达式的求值顺序是从左到右先执行trim得到干净的字符串再交给replace处理所以顺序别写反。如果先replace再trim也问题不大但这个场景下先trim更符合数据清洗的逻辑层次。2.2 数组方法是批量处理的灵魂字符串方法解决的是“单个字段怎么处理”的问题数组方法解决的是“一批数据怎么处理”的问题。从数据库取出来的订单、从Webhook收到的批量事件在n8n里都会变成数组。JavaScript数组方法简直是为这种场景量身定做的filter()按条件筛选。如{{ $json.orders.filter(order order.status paid) }}。map()对每一项做转换。如把订单列表映射成金额数组{{ $json.orders.map(order order.amount) }}。find()找第一个满足条件的项。reduce()累加汇总。比如算总金额{{ $json.orders.reduce((sum, order) sum order.amount, 0) }}。join()把数组拼成字符串。比如把所有订单号用逗号拼起来{{ $json.orders.map(o o.id).join(,) }}。length这是数组的属性返回元素个数。统计已支付订单数量时很常用。需要注意这些方法在表达式中的写法跟普通JavaScript几乎一样但n8n的表达式中一行能写完的逻辑尽量一行写完。箭头函数里的参数名可以随便起比如order、o、item都行但要避开n8n内置变量名不然容易把自己绕晕。数组方法最有价值的地方是和统计指标结合。比如你想知道最近一小时的订单里金额大于100的订单有几单、总金额是多少完全可以在一个表达式里完成{{ $json.orders.filter(o o.amount 100).reduce((sum, o) sum o.amount, 0) }}这个表达式先筛出大于100的再累加金额。换成节点实现你至少得拖一个Filter节点再拖一个Code节点才能完成同样的事情。而且节点之间传数据还会产生额外的序列化和解析开销表达式直接在当前上下文里算完运行速度也更稳。2.3 日期与数值格式化别硬背会用就行日期处理是另一个高频场景。n8n里常用的变量和方法有这么几个$now返回当前时间的Date对象new Date($json.timestamp)把时间戳转成Date对象.toISOString()转成ISO格式字符串适合存数据库.getFullYear()、.getMonth()、.getDate()分别取年月日Date.now()获取当前毫秒时间戳。数值处理则经常用到Number()、parseFloat、toFixed(2)、Math.round这些。举个例子从数据库查到的created_at是13位时间戳你想在日报里展示成2025-06-01 14:30这种格式。n8n表达式里没有固定的formatDate全局函数但你可以用JS自己拼{{ new Date($json.created_at).toISOString().slice(0, 16).replace(T, ) }}这个写法利用了ISO字符串的固定结构先截前16位得到2025-06-01T14:30再把T替换成空格简单粗暴实测有效。如果你只是想取当前日期用于文件名可以直接{{ $now.toISOString().slice(0, 10) }}数值处理方面有个坑必须提醒toFixed(2)会把数字变成字符串比如3.14159会变成3.14。如果你后面还要做数值计算记得用Number()把它转回数字不然字符串拼接会出现“3.14 1 3.141”这种诡异结果。我一开始就在这上面吃过亏统计报表里小数点和金额算错位排查了半天才发现是类型问题。2.4 JSON转换与数据清洗外部系统接口经常返回字符串格式的JSON。尤其是用HTTP Request节点接收响应时n8n一般会自动解析JSON但有时候你拿到的字段是嵌套字符串。比如某个接口返回data字段是字符串里面又包了一层JSON这个时候就需要JSON.parse(){{ JSON.parse($json.data).orderId }}反之往外部系统发送数据时某些字段需要把对象变成字符串就用JSON.stringify(){{ JSON.stringify($json.items) }}这两个方法几乎是每个n8n项目都会用到的。有一个常见坑JSON.parse()如果遇到非法的JSON字符串会直接让工作流报错。所以最好在解析前确认来源可靠或者用IF节点先做格式校验。数据清洗是一个系统性的活儿我的习惯是所有进入工作流的数据先不要急着存到目标系统而是在入口节点后面用一个“清洗表达式”统一把字段名、格式、大小写处理干净。这样后面的所有节点都基于清洗后的数据工作维护起来省心很多。3. 变量实操静态、动态、全局、环境的作用域与生命周期3.1 n8n里的变量到底分几层n8n的变量体系看起来有点乱其实只要分清几个层级就好。变量类型创建位置作用范围典型用法全局变量Settings Variables所有工作流保存API地址、公共参数环境变量.env或系统环境变量所有工作流由部署环境决定保存密钥、数据库连接串静态数据Set节点里手写的值当前节点及下游固定配置字段动态数据表达式中引用的数据当前执行上下文$json、$node等表格里的“作用范围”是核心。全局变量和环境变量都能跨工作流访问但环境变量的值来自部署环境全局变量则在n8n界面上维护。两者在表达式中的读取方式也不同全局变量用$vars.变量名环境变量用$env.变量名。注意不同n8n版本可能略有差异旧一点的版本全局变量是$global新版本有的会统一到$vars你写完后可以看表达式编辑器是否有自动补全提示来判断这比死记版本号靠谱得多。3.2 全局变量与环境变量的配置路径全局变量的配置位置在n8n主界面的Settings Variables有些版本在“账户”或“管理”菜单下面。点新建变量填入Key和Value即可。创建后在任何工作流的表达式里都能直接引用{{ $vars.apiBaseUrl }} /v1/orders环境变量需要修改部署n8n的服务器配置。如果你用Docker部署环境变量写在docker-compose.yml里如果用二进制部署写在启动命令或系统环境变量里。n8n还提供了一个配置项控制节点是否能访问环境变量具体名字在不同版本里略有变化如果你发现{{ $env.xxx }}取不到值先检查部署配置里是否放行了环境变量访问。Credentials本质上并不是变量它是n8n统一管理的凭据但很多人会混淆因为Credentials里也可以保存一些连接信息。我的建议是密钥类的信息放在Credentials业务配置类的信息放在全局变量或环境变量不要把两者混在一起。全局变量适合存“会经常变动的业务参数”比如Webhook地址、默认阈值、状态枚举值这样别人接手你的工作流时不用去每个节点里翻找写死的值。3.3 表达式中的变量引用与安全取值在表达式里最常见的变量是$json它表示当前节点收到的最新一条数据。$json是一个对象你可以直接点字段名取字段。如果你需要取上一个节点的数据用$node[节点名称].json.字段名如果你在循环里需要取更早的数据可以用$items(节点名称, 索引)。这些引用方式我几乎每天都会用熟练之后对数据在流程里的定位会非常清晰。这里必须提醒一个安全取值的问题。当字段不存在时表达式会返回undefined如果后续调用了方法就会报Cannot read properties of undefined。解决方法是使用可选链操作符?.和空值合并操作符??{{ $json.user?.name ?? 匿名用户 }}这个表达式会安全地取user.name如果user不存在或name不存在就返回匿名用户。这在处理外部接口数据时非常重要因为外部返回的字段结构真的不可控。我几乎所有读取嵌套字段的表达式都会加上?.和??宁可多打几个字符也绝不拿工作流的稳定性去赌字段一定存在。3.4 循环处理中的变量覆盖陷阱n8n有Loop节点和Split In Batches节点处理批量数据时很有用但也有一个常见的陷阱进入循环后当前节点的$json会被循环项覆盖。如果你在循环内还想去取循环之前的某个值直接取$json就会取到循环项而不是外层数据。我的做法是进入循环之前用一个Set节点把外层需要引用的值保存到全局变量里或者用跨节点引用方式。n8n也提供了$parent之类的上下文访问但不同版本支持程度不一样如果你需要兼容多个版本最好还是用Set节点显式保存。这个坑我踩过两次之后现在写循环之前都会先看一眼“作用域”想清楚这个值在循环里还能不能拿到再决定要不要提前保存。说实话这个习惯帮我少加了好几个小时的班。4. 实战用内置方法和变量把一份数据清洗报告跑起来4.1 先设计一个完整场景理论讲再多不如跑一个完整流程。我设计了一个很常见的工作流每天上午从数据库拉取前一天的全部订单清洗字段统计销售额和订单量生成一份Markdown格式的日报发送到企业微信机器人。这个流程会用到前面提到的字符串方法、数组方法、日期处理、变量引用能一次把知识点串起来。实际项目里你可能用PostgreSQL、MySQL、HTTP接口或者Google Sheets拿数据核心思路都一样先拿到原始数据再做清洗再做统计最后输出。这个流程不需要复杂的业务假设很适合当作一个模板来改。整体节点编排大概是定时触发节点后接一个查询数据的节点再是一个清洗字段的节点然后是统计节点最后生成文本和发送。4.2 节点编排与关键表达式第一步是定时触发。Cron节点里设置每天上午9点执行表达式0 0 9 * * *即可。第二步从数据库查出订单数据这一步返回的数据是数组每项代表一条订单。第三步用表达式把每条订单的字段清洗成目标格式。清洗阶段我习惯把“处理后的订单数据”先映射成统一结构。比如{{ $json.map(o ({ id: o.id, customerName: o.customer_name.trim(), amount: Number(o.amount), status: o.status.toLowerCase() })) }}这个表达式把原始字段customer_name转成驼峰、去掉空格金额转成数值类型状态统一小写。看起来有点长但在一次表达式中完成了结构转换后面的统计节点可以非常清爽。映射操作最怕的就是某些订单字段缺失所以在实际使用时我会加上?.兜底比如o.customer_name?.trim()确保单条脏数据不会让整个流程崩掉。统计阶段我用另一个表达式节点去汇总清洗后的数据{{ JSON.stringify({ totalOrders: $json.length, paidOrders: $json.filter(o o.status paid).length, totalAmount: $json.reduce((sum, o) sum o.amount, 0).toFixed(2) }) }}这里把每个统计指标变成了一个对象再通过JSON.stringify转成字符串方便后续拼报告。变量在这里起了核心作用统计结果在下一个节点生成报告正文时直接作为变量被引用。生成报告阶段再写一个表达式把统计结果和记录明细拼成Markdown。发送阶段用HTTP Request节点POST到企业微信机器人的地址请求体里填入拼接好的markdown。这里的Webhook地址我不会写死在工作流里而是放在全局变量$vars.reportWebhook以后换地址只改一处不用到处翻节点。4.3 优化效果与维护心得如果完全用节点堆同样的清洗和统计至少需要Set、IF、Aggregate、Code六个以上节点而且中间还夹着大量字段映射逻辑检查问题的时候要在节点之间跳来跳去。用表达式做核心逻辑都集中在两三个表达式节点里节点数量少了一大半运行速度也更快因为少了很多次节点间数据传递。维护上团队协作时有个小技巧给表达式节点取个清楚的名字同时在描述里写一行注释说明这段表达式做了什么。n8n表达式本身不太方便写多行注释所以节点描述就是你的文档。接手你工作流的人看到“清洗订单字段”“统计指标汇总”这样的节点名再看到描述里的说明基本能直接读懂流程。这件事看起来不起眼但等你三个月后再回去维护自己的老流程就会感谢当初认真写描述的自己。5. 常见问题排查与调试实录5.1 高频问题速查表我把自己和周围朋友在n8n里碰到过的问题整理了一下大部分都能在这里找到症结。问题现象可能原因解决办法表达式变成了纯文本输入框不支持表达式或没写双花括号确认输入框类型是否支持表达式加完整{{ }}报错xxx is not a function当前值的类型不是预期类型先用typeof或Array.isArray()确认类型字段取到undefined字段名写错或数据嵌套层级不对用表达式编辑器实时预览逐步点开数据结构全局变量改了但工作流里没变化已部署的工作流还在用旧配置或全局变量缓存保存并重新执行工作流必要时重启n8n$env取不到值部署环境没有放行表达式访问环境变量检查n8n部署配置中与环境变量访问相关的开关循环内取不到外层数据Loop节点把$json覆盖成了循环项循环前先用Set节点保存外层值或用跨节点引用toFixed之后数字计算异常toFixed返回的是字符串后续计算前用Number()转回数值表达式中含有中文引号手动输入时用了中文标点检查、、{{ }}是否是英文状态这个表我建议截图存一份。很多问题不是逻辑多难就是一些看起来很琐碎的细节但排查起来会花不少时间。尤其是中文引号那个问题我自己都犯过好几次表达式一跑就报语法错误眼睛盯了半天才发现输入法偷偷把引号换成了全角真是防不胜防。5.2 表达式调试三板斧n8n的调试体验其实相当不错关键是你得会用自带的能力。第一表达式编辑器支持实时预览你输入表达式后下面会显示计算结果不用等整个工作流跑一遍就能验证。第二在节点之间临时加一个Execute Command或Code节点把关键变量的值通过日志输出能看到运行到该节点时的真实数据。第三点击每个节点右上角的“执行”按钮可以只运行该节点及之前的部分观察输入输出。我在调试复杂表达式时还有个习惯先在一个Code节点里把数据简化只保留我要看的几个字段再逐步把表达式加上去。这样能避免数据结构太复杂时表达式报错却找不到问题在哪一行。具体来说我会先用日志输出整个$json看看哪些字段是真正存在的再对照表达式里的字段名基本能定位是拼写问题还是层级问题。5.3 总有一个坑是你想提前知道的如果非要让我挑一个最值得提前说的那就是“先验证后套用”。我刚开始用内置方法时特别喜欢写长表达式一行写完清洗加统计加格式化结果某个字段类型稍微一变整个步骤报错还得从一长串表达式里慢慢拆。后来我学乖了复杂逻辑先用Code节点分步写出验证没问题后再把可以合并的表达式合并把Code节点删掉。实际效果是工作流既简洁又不会一脚踩空。还有一个经验跟变量有关尽量少用“魔法数字”和“魔法字符串”。比如某个状态的合法值叫paid就不要在多个节点里各写各的而是在全局变量里定义一个$vars.paidStatus统一引用。这样业务状态一变你只需要改一个地方。这个习惯我发现越到项目后期价值越大因为你永远不知道今天用的值明天会不会因为业务规则调整而改变。说到底内置方法提升的是执行效率变量的规范化提升的是维护效率两个都值得认真对待。从最初只会拖节点到现在能用一个表达式解决大半个流程我觉得n8n最迷人的地方在于它给了一块可以自由书写的“画布”而内置方法和变量就是画布上最趁手的工具。我在实际项目中还会把这种方式扩展到更多场景比如审批流、报表生成、告警推送思路都是一脉相承的。希望这篇分享能帮你少走一些我走过的弯路把工作流搭得更瘦、跑得更稳。