Первый workflow: ручной запуск → редактирование полей → код

Я два года считал цену вручную в таблице: количество умножить на цену за единицу, прибавить доставку, потом разделить обратно, чтобы получить стоимость за штуку. Двести пакетов, цена за единицу один юань восемьдесят пять фэней, доставка шестьдесят, в сумме четыреста тридцать юаней, за штуку два юаня пятнадцать фэней. Числа несложные, проблема в том, что я дважды пропустил доставку, а коммерческое предложение клиенту уже было отправлено.

Позже этот алгоритм переехал в n8n, три узла, меняешь одно число и запускаешь снова, за две секунды новое значение. Эта статья — те три шага от пустого холста до рабочего запуска, включая ту ошибку, когда я в первый раз посчитал 430 как 37060.

Предварительно нужно только две вещи: n8n уже установлен и открывается (если не установлен, смотрите«Как установить n8n»), холст, панель узлов и журнал выполнений знакомы (если нет, откройте«Обзор интерфейса»). Уметь писать код не нужно, блок с кодом можно просто скопировать целиком.

Три узла, каждый отвечает за одно

Самая частая ошибка новичков — воспринимать три узла как «три шага». Это не шаги, это три роли.

  • Manual Trigger (ручной запуск): создаёт одну пустую запись данных и передаёт её дальше. Он не обращается к базе данных, не принимает запросы, не следит за временем — это чисто «вы нажали один раз, он один раз выполнился». В первом рабочем процессе он нужен, чтобы сократить переменные до одной — вопрос «откуда берутся данные» пока можно не рассматривать.
  • Edit Fields (редактирование полей): добавляет поля в item или изменяет существующие. Количество записей не меняется: сколько вошло, столько и вышло. В старых руководствах он назывался Set, переименован он уже давно, в панели узлов версии 2.43.7 написано Edit Fields (Set), по обоим именам можно найти.
  • Code (код): единственный шаг, который может изменить количество записей. Одна запись может быть разбита на несколько, несколько могут быть сжаты в одну. Поэтому его возвращаемое значение должно быть массивом — это его главное отличие от других узлов и место, где возникает больше всего ошибок.

Мой экземпляр — версии 2.43.7, интерфейс переключён на упрощённый китайский. Нужно предупредить: китайский интерфейс переводит только описательный текст и кнопки, названия узлов и параметров остаются на английском. Если вы ищете «编辑字段», вы ничего не найдёте, нужно искать edit fields. Те руководства в интернете, где написано «узел Set» или «Keep Only Set Fields узла Set», точно так же не соответствуют интерфейсу 2.x — не сомневайтесь в себе.

Что на самом деле передаётся между узлами

Если не разобраться в этом, каждую последующую ошибку можно будет только угадывать.

Все данные, циркулирующие между узлами в n8n, — это массив, и каждый элемент в массиве выглядит так:

[
  { "json": { "qty": 200 } },
  { "json": { "qty": 150 } }
]

Внешние квадратные скобки — это «пакет», внутри каждый объект является единицей данных, n8n называет это item. Настоящие бизнес-поля вложены в json этот ключ. Поэтому «обработать 200 заказов» в этой модели означает не цикл из 200 итераций, а однократную передачу узлу 200 item, и узел сам обрабатывает их по одному.

Такая архитектура имеет два следствия. Хорошая новость: узлы естественным образом работают пакетно, писать циклы не нужно. Плохая новость: когда пишешь код, на руках у тебя массив, а не отдельный объект —row.qty такую запись нельзя напрямую применить к массиву, сначала нужно извлечь первый элемент.

От пустого холста до трёх узлов

  1. В правом верхнем углу списка рабочих процессов — «新建工作流», или в адресной строке сразу ввести /workflow/new. Я привык ко второму способу, быстрее.
  2. На пустом холсте нажми в правом верхнем углу ту +(можно также нажать плюс в центре холста), появится панель «Что произойдёт дальше?».
  3. В поле поиска введи manual trigger, выбери Manual Trigger.
  4. Наведи курсор на маленькую точку справа от узла, появится +, нажми на неё, найди edit fields, выбери Edit Fields (Set). Добавляйте этим способом — связи соединятся сами, вручную перетаскивать не нужно.
  5. Повторите ещё раз, найдите code, выберите Code.
  6. Имя узла по умолчанию на английском. Двойной щелчок по заголовку позволяет изменить его; я поменял на китайский: ручной запуск, заполнение формы расчёта, вычисление суммы. Когда узлов много, английские имена вроде Trigger, Set, Code при беглом просмотре рябят в глазах; переименование на китайский — чисто для экономии зрения, на функциональность никак не влияет.
n8n 添加节点面板里搜索 edit fields,结果为 Edit Fields (Set) 节点
Поиск edit fields выдаёт только один результат. Строка мелким шрифтом под ним Modify, add, or remove item fields ясно описывает его назначение: только изменять поля, не создавать данные.

Редактирование полей: добавить четыре поля к одной записи данных

Двойной щелчок по «Заполнение формы расчёта» открывает панель; из параметров нужно изменить только два места.

Mode Оставьте Manual Mapping— это способ заполнения через форму. Другой вариант JSON Output — писать JSON напрямую, удобнее для вложенных структур; на начальном этапе пока не используйте.

Fields to Set добавьте четыре строки. В каждой строке слева — имя поля, в середине — выпадающий список типа, справа — значение:

Имя поля Тип 值
product строка вакуумный компрессионный пакет 60x80cm
qty число 200
unit_price число 1.85
freight число 60

Выпадающий список типа по умолчанию — «строка», но qty, unit_price и freight нужно вручную переключить на «число». Интерфейс вас не остановит: если ошибётесь, он не выдаст ошибку, а молча сохранит 200 как "200". Насколько это больно — покажу на расчёте чуть позже.

n8n 编辑字段节点参数:Mode 为 Manual Mapping,四个字段 product、qty、unit_price、freight
Четыре поля, три из них — числа. Имена полей — через подчёркивание, а не camelCase, потому что позже в коде их надо склеивать row.unit_price, не хватит одного символа — получите undefined.

Самый нижний на панели Include Other Input Fields переключатель по умолчанию включён, что означает «поля на входе, которые я не трогал, выводятся как есть». На входе этого сценария пусто, так что включён он или выключен — без разницы. Но в сценарии с данными выключить его — значит отрезать все поля от предыдущего шага, оставив только четыре, заполненные вручную, — и если потом в коде не получится взять product, не думайте, что код написан с ошибкой.

Узел кода: взять одну, посчитать раз, вернуть одну

Дважды щёлкните «计算金额».Mode Оставить Run Once for All Items(весь массив выполняется один раз),Language Оставить JavaScript. Затем вставьте следующий фрагмент в область редактирования:

const row = items[0].json;
const amount = row.qty * row.unit_price + row.freight;
const unitCost = Number((amount / row.qty).toFixed(2));

return [{
  json: {
    product: row.product,
    qty: row.qty,
    amount,
    unitCost,
    summary: row.product + ' ' + row.qty + ' 个,含运费合计 ' + amount + ' 元,单个成本 ' + unitCost + ' 元',
  },
}];

Разберу построчно, только те места, которые могут укусить.

items[0].json Берётся первый элемент. Здесь только одна запись данных, поэтому достаточно взять [0]. Для случая с несколькими записями нужно писать items.map(...), это тема следующей статьи.items — старый способ записи, в 2.x ещё работает; новый способ — $input.first().json, оба варианта на этом экземпляре дали совершенно одинаковый результат. Я оставил items, потому что в режиме «Run Once for All Items» у вас на руках и так весь массив, и это имя честнее.

toFixed(2) Это не для красоты. 430 делить на 200 ровно 2.15, выглядит аккуратно; если заменить на стоимость доставки 55, получится 2.175, при вычислении с плавающей точкой может выйти 2.1749999999999998. Записать это в таблицу или отправить клиенту — такой хвост выглядит некрасиво. Внешний слой Number() Это преобразование его из строки обратно в число —toFixed возвращается строка, и без преобразования unitCost в таблице будет выровнен по левому краю.

Последний фрагмент return — это ключевой момент.Возвращается массив, каждый элемент обёрнут в слой json. n8n по этому ключу json различает «это бизнес-данные» и «это бинарное вложение». Если формат неверный, узел сразу подсветится красным.

n8n 代码节点参数:Mode 为 Run Once for All Items,Language 为 JavaScript
В выпадающем списке Mode два варианта, выберешь не тот — и весь код бесполезен. Разница описана ниже во 2-м пункте «Пять ошибок, которые реально встретятся».

Запусти один раз, посмотри вывод

Нажми оранжевую кнопку «执行» под холстом. Через одну-две секунды рамки трёх узлов станут зелёными, в правом верхнем углу каждого узла появится галочка, а на соединительной линии будет отмечено «1 个项目».

n8n 三节点工作流执行完成,连线上标注 1 个项目,节点带绿色勾
«1 个项目» на линии — это сколько записей прошло по этой линии. Когда позже добавишь узел разбиения, здесь будет 3, 5, 10 — сразу видно, на каком шаге данные размножились.

Дважды кликни «计算金额», в правой области вывода переключись на вкладку JSON:

n8n 代码节点输出的 JSON:amount 430、unitCost 2.15 与拼好的 summary
430 — это 200×1.85+60, 2.15 — это 430÷200. Если посчитать вручную и сходится, значит три узла соединены правильно.

Кстати, о том, что легко понять неправильно: кнопка «执行» запускает только по нажатию. Рабочий процесс Manual Trigger не запускается сам по себе после публикации — у него нет механизма «самозапуска». Для запуска по расписанию нужно заменить его на триггер типа Schedule Trigger.

Приложение: JSON всего рабочего процесса

Если лень кликать по узлам, можно импортировать целиком. Скопируй следующий фрагмент и нажми на пустом месте холста Ctrl+V, три узла сразу попадут на холст. Можно и через меню: в правом верхнем углу ⋯ → Импорт → Импорт из файла, сначала сохраните содержимое в .json。

Этот фрагмент я сам вставил в пустой workflow и прогнал: три узла, две связи, позиции совпадают, после выполнения amount тоже 430.

{
  "name": "第一个工作流",
  "nodes": [
    {"parameters":{},"id":"m1","name":"手动触发","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[-368,0]},
    {"parameters":{"assignments":{"assignments":[
      {"id":"a1","name":"product","value":"真空压缩袋 60x80cm","type":"string"},
      {"id":"a2","name":"qty","value":200,"type":"number"},
      {"id":"a3","name":"unit_price","value":1.85,"type":"number"},
      {"id":"a4","name":"freight","value":60,"type":"number"}
    ]},"includeOtherFields":true,"options":{}},"id":"m2","name":"填写报价单","type":"n8n-nodes-base.set","typeVersion":3.5,"position":[-144,0]},
    {"parameters":{"jsCode":"const row = items[0].json;nconst amount = row.qty * row.unit_price + row.freight;nconst unitCost = Number((amount / row.qty).toFixed(2));nnreturn [{n  json: {n    product: row.product,n    qty: row.qty,n    amount,n    unitCost,n    summary: row.product + ' ' + row.qty + ' 个,含运费合计 ' + amount + ' 元,单个成本 ' + unitCost + ' 元',n  },n}];","mode":"runOnceForAllItems"},"id":"m3","name":"计算金额","type":"n8n-nodes-base.code","typeVersion":2,"position":[80,0]}
  ],
  "connections": {
    "手动触发": {"main": [[{"node": "填写报价单", "type": "main", "index": 0}]]},
    "填写报价单": {"main": [[{"node": "计算金额", "type": "main", "index": 0}]]}
  },
  "settings": {"executionOrder": "v1"}
}

Как понять, что действительно всё работает

Четыре пункта, все проверяются самостоятельно, без любого из них нельзя считать, что готово.

  1. Три узла в зелёных рамках с галочкой, на обеих связях отмечено «1 элемент». Если число не 1, значит, промежуточный узел изменил количество, нужно вернуться и проверить.
  2. В JSON на выходе узла кода amount равен 430, unitCost равен 2.15.
  3. В истории выполнений можно найти этот запуск. Ручной запуск тоже оставляет запись, не думайте, что в список попадают только задачи по расписанию.
  4. Измените freight с 60 на 100 и запустите снова, amount должен стать 470. Изменить один вход и увидеть, как меняется выход — этот пункт жёстче предыдущих трёх: увидеть 430 один раз может быть совпадением.

Четвёртый пункт я рекомендую делать каждый раз. Между «запустилось один раз» и «работает правильно» есть разница.

Пять ошибок, которые реально возникнут

1. Code doesn't return items properly

Вернулась строка. Я на практике написал всего одну строку return "共 430 元";, узел сразу становится красным, в правом нижнем углу появляется это уведомление.

n8n 代码节点返回字符串时报错:Code doesn't return items properly
Красная рамка, белый восклицательный знак, одна строка английской ошибки. Первые два узла всё ещё зелёные, значит ошибка именно в последнем.

Интересно то, чтоreturn { amount: 430 } такая запись без обёртки в массив и без jsonработает. Узел Code автоматически добавит ключ json и квадратные скобки, это поведение описано в документации. Так что эта ошибка не про «недостаточно стандартный формат», а про «то, что ты возвращаешь, вообще не данные» — строку, число, null обернуть нельзя.

2. items is not defined [line 1]

Mode выбран Run Once for Each Item, а в коде по-прежнему используется items. В режиме по одному элементу n8n подаёт за раз только один элемент, текущий называется $json, для получения количества нужно писать $json.qty. Разница между двумя режимами в одной фразе: весь массив за один прогон или каждый элемент за отдельный прогон. Перед написанием кода сначала посмотри на этот выпадающий список.

3. В одном workflow допускается только один узел "Manual Trigger"

Добавляю ещё один триггер в workflow, где триггер уже есть. В этот раз я ошибся ещё глупее: вставил JSON с триггером на холст существующего workflow, всплыло «无法插入节点», а под ним эта строка. Чтобы попробовать для сравнения, сначала создай новый workflow и потом вставляй.

4. Выводится undefined, ошибки нет

В Edit Fields поле называется unit_price, а в коде написано row.unitPrice. Разница в один символ — регистр и подчёркивание, n8n это не проверяет, JS тоже, в результате undefined передаётся дальше, и в summary появляется «含运费合计 undefined 元». Такую ошибку искать сложнее всего, потому что она не подсвечивается красным, не издаёт звука — просто число неверное.

5. amount превращается в "37060"

Тип поля выбран string. Шестьдесят и "60" в JS выглядят одинаково, но + при встрече со строкой переключается на конкатенацию: 370 плюс "60" даёт "37060", а не 430.

n8n 编辑字段里 freight 类型选成字符串后,代码输出 amount 变成 37060
Во входе слева значок перед freight меняется с # на T, справа amount становится "37060" в кавычках, unitCost следом становится 185.3. Весь workflow не выдаёт ошибку и уверенно пишет «合计 37060 元».

Пятый пункт — самый ценный в этой статье. Когда я впервые получил это число, я полминуты смотрел на 37060, прежде чем понял, что это конкатенация строк — потому что код выглядит абсолютно нормально, а проблема в выпадающем списке типа в предыдущем узле.

Три границы узла Code

Узел Code выполняется в собственном процессе n8n, но в sandbox: не может читать локальные файлы, не может отправлять HTTP-запросы. Для работы с файлами используйте узел Read/Write File From Disk, для запросов — узел HTTP Request. Это не урезанная функциональность, а сведение к минимуму того, «что может натворить кусок JS, вставленный наугад».

Но три вещи, которые он не блокирует, придётся контролировать самому.

Не вставляйте наугад JSON workflow, полученный от других.Вставить его — значит выполнить чужой JS на своём экземпляре. На моём экземпляре вставка JSON сразу создаёт узлы, без подтверждения и без вопроса об источнике. Если действительно нужно попробовать, сначала закиньте в пустой тестовый экземпляр, просмотрите код и только потом решайте, запускать ли.

Не хардкодьте секреты в коде.Рабочий процесс можно экспортировать целиком, отправить коллеге, вставить в чат. Жёстко прописанный в коде api key уедет вместе с JSON. Нужно вынести его в узел учётных данных, чтобы n8n хранил его в зашифрованном виде.

Self-hosting позволяет устанавливать сторонние npm-пакеты.По умолчанию выключено, нужно вручную включить переменную окружения. Включение открывает лазейку для произвольного выполнения кода в узле Code. На экземпляре для одного пользователя — как угодно, на общем экземпляре я рекомендую держать это выключенным всегда.

Краткое резюме

Три узла, одна цепочка: триггер создаёт данные, Edit Fields дополняет данные, Code вычисляет данные. Запоминать нужно не то, как кликать по этим трём узлам, а ту форму «массив из json». Дальше IF, Merge, циклы, ветки ошибок — всё это вариации данной формы.

Код — только когда без него никак. Эту смету можно посчитать и через Edit Fields с выражениями,{{ $json.qty * $json.unit_price + $json.freight }} одна строка — и на один узел меньше. Я в итоге выбрал Code, потому что дальше нужно одновременно считать смету по трём вариантам, и нагромождение выражений в узле сложнее поддерживать, чем код. Если считаешь только одно число — не лезь в код.

Насчёт той ловушки с типами — советую прямо сейчас намеренно поменять freight на строку и прогнать один раз, своими глазами увидеть, как 430 превращается в 37060. Десять раз прочитать текст — не то же самое, что один раз ошибиться самому.

Идём дальше

Предыдущая статья —«n8n, Zapier, Make: как выбрать». Как выглядят узлы, связи и выполнение в JSON,эта статьяразбирала. Если однажды сервис не запустится, порт займётся, смотри«Как диагностировать ошибки запуска n8n и занятость портов». Установка смотриэту статью, если интерфейс незнаком, откройэту статью. Оглавление всего курса находитсяздесь。

Справочные материалы

  • Manual Trigger: запускается только при нажатии Execute, автоматический запуск не предусмотрен; в одном workflow допускается только один
  • Edit Fields (Set): два режима — Manual Mapping и JSON Output, роль Include in Output
  • Code: различия двух Mode, параметры языка, отсутствие доступа к файловой системе и возможность отправки HTTP-запросов
  • Understand n8n's data structure: происхождение вложенных массивов в json и пояснение автоматического добавления ключа json в узле Code
  • Используйте встроенные ярлыки: встроенные переменные и методы в выражениях, какие из них не применимы в узле Code

Последнее обновление: 28 октября 2026 г., использовалась версия n8n 2.43.7.

THE END