Przejdź do głównej zawartości

Kod i sub-workflow w n8n

Moduł 5 · Poziom: średnio zaawansowanyCzas czytania: ~22 minWymaga: Moduły 0-4

Tu zaczyna się "wysoki sufit" n8n. Gdy gotowe node'y nie wystarczają, sięgasz po Code Node (JavaScript lub Python), pamiętasz stan między uruchomieniami dzięki danym statycznym, dzielisz logikę na modularne sub-workflow i - co najważniejsze w produkcji - robisz automaty odporne na błędy. Ten moduł łączy programowanie z dyscypliną niezawodności.

Code Node to "okno na kod" w środku wizualnego workflow. Pozwala wykonać własny skrypt w JavaScript lub Python, gdy żaden gotowy node nie robi dokładnie tego, czego potrzebujesz. Otrzymuje dane wejściowe jako listę itemów i musi zwrócić dane w tym samym formacie - żeby kolejne node'y mogły z nich korzystać.

Zaraz po dodaniu node'a wybierasz w polu Mode jeden z dwóch trybów. To nie kosmetyka - zmienia, ile razy uruchamia się Twój kod i jak dostajesz dane.

Run Once for All Items

Kod wykonuje się raz dla całej paczki danych. Dostajesz wszystkie itemy naraz przez $input.all() i sam zwracasz tablicę itemów. Idealne do: agregacji, sortowania, łączenia, liczenia sum, deduplikacji - gdy musisz "widzieć" cały zbiór.

Run Once for Each Item

Kod wykonuje się osobno dla każdego itemu. Bieżący item masz wprost w $json, a zwracasz pojedynczy obiekt. Idealne do: prostych przekształceń pól item po itemie - kod jest krótszy i czytelniejszy.

n8n wymaga, by dane między node'ami miały postać listy itemów, a każdy item to obiekt z kluczem json. To dlatego nie zwracasz "gołego" stringa czy liczby - zawsze opakowujesz dane w { json: { ... } }.

// Pobranie wszystkich itemów naraz
const items = $input.all();
// Liczymy łączną wartość zamówień
let suma = 0;
for (const item of items) {
suma += item.json.kwota;
}
// Zwróć JEDEN item z podsumowaniem (tablica itemów)
return [
{
json: {
liczbaZamowien: items.length,
sumaPLN: suma
}
}
];
// $json to bieżący item - bez pętli
const imie = $json.firstName;
const nazwisko = $json.lastName;
// Zwróć pojedynczy obiekt (n8n sam zbuduje listę)
return {
json: {
pelneImie: `${imie} ${nazwisko}`,
email: $json.email.toLowerCase()
}
};

Code Node obsługuje też Python (uruchamiany w przeglądarce przez Pyodide). Logika jest taka sama, ale zmienne wbudowane mają prefiks podkreślenia zamiast dolara: _input.all() zamiast $input.all(), a bieżący item to _json. Zwracasz słownik z kluczem "json".

# Run Once for Each Item - Python
return {
"json": {
"pelneImie": _json["firstName"] + " " + _json["lastName"]
}
}

Złota zasada n8n: najpierw node'y, kod dopiero gdy naprawdę trzeba. Code Node jest potężny, ale każdy fragment kodu to coś, co musisz utrzymać, przetestować i wytłumaczyć następnej osobie. Wizualny node mówi sam za siebie; pętla w JavaScript - już niekoniecznie.

Wizualne node'y są samodokumentujące - widać na kanwie, co się dzieje. Są też testowane przez n8n, mają obsługę paginacji, ponawiania i błędów "w pakiecie". Kod tego wszystkiego nie dostaje za darmo: to Ty odpowiadasz za każdy edge case (przypadek brzegowy - nietypową sytuację na granicy, którą łatwo przeoczyć).

Sięgnij po Code Node, gdy…

  • Logika jest zbyt złożona na łańcuch node'ów Set/IF/Filter
  • Musisz przekształcić strukturę danych nietypowo (zagnieżdżenia, grupowanie)
  • Potrzebujesz obliczeń, których nie ma wśród funkcji wyrażeń
  • Łączysz/agregujesz wiele itemów w jeden wynik (lub odwrotnie)
  • Robisz to raz, w jednym miejscu - zamiast pięciu node'ów obok siebie

Zostań przy node'ach, gdy…

  • Istnieje gotowy node robiący dokładnie to (Set, IF, Filter, Merge, Sort)
  • Chcesz wywołać API - użyj HTTP Request, nie fetch w kodzie
  • Workflow ma czytać ktoś nietechniczny
  • Wystarczy proste wyrażenie {{ }} w polu node'a
  • "Kusi Cię" przepisanie połowy workflow na jeden wielki skrypt

n8n daje Ci zestaw zmiennych i metod dostępnych zarówno w Code Node, jak i w wyrażeniach {{ }}. Znajomość tych kilku skraca kod o połowę i pozwala odwoływać się do danych z dowolnego miejsca workflow.

Zmienna / metoda Do czego służy
$input.all() Wszystkie itemy wejściowe bieżącego node'a (tablica). Podstawa trybu "Run Once for All Items".
$input.first() / $input.last() Pierwszy / ostatni item z wejścia - gdy interesuje Cię tylko jeden.
$input.item Bieżący item w trybie "Run Once for Each Item".
$json Skrót do danych bieżącego itemu (czyli $input.item.json). Najczęściej używana zmienna.
$node["Nazwa"].json / $('Nazwa') Dane z konkretnego, wcześniejszego node'a po jego nazwie. Pozwala sięgnąć "w bok", nie tylko do poprzednika.
$now Aktualna data i czas jako obiekt Luxon DateTime (wygodne formatowanie i arytmetyka dat).
$today Dzisiejsza data (początek dnia) jako Luxon DateTime.
$workflow Metadane workflow: m.in. $workflow.id, $workflow.name, $workflow.active.
$execution Kontekst wykonania: m.in. $execution.id oraz $execution.mode (np. manual / trigger).
$vars Zmienne środowiskowe instancji (Variables) - wartości współdzielone, np. adres API per środowisko.
$jmespath(obj, "...") Zapytanie po danych JSON składnią JMESPath - wygodne wyciąganie zagnieżdżonych pól.
// Sformatuj datę zamówienia (Luxon)
const data = $now.toFormat('yyyy-MM-dd HH:mm');
// Czy uruchomienie jest produkcyjne, czy testowe?
const tryb = $execution.mode; // 'manual' podczas testów
// Pobierz dane z node'a sprzed kilku kroków po nazwie
const klient = $('Pobierz klienta').first().json;
// Wyciągnij listę e-maili z zagnieżdżonej struktury
const maile = $jmespath($json, "uzytkownicy[*].kontakt.email");

Domyślnie każde wykonanie workflow startuje "na czysto" - nic nie pamięta z poprzedniego razu. Czasem jednak potrzebujesz pamięci między uruchomieniami: np. ostatnie pobrane ID, znacznik czasu ostatniej synchronizacji albo licznik. Do tego służą dane statyczne workflow.

W Code Node wywołujesz $getWorkflowStaticData(type) i dostajesz zwykły obiekt, na którym czytasz i zapisujesz wartości. Zakres podajesz argumentem:

  • $getWorkflowStaticData('global') - pamięć współdzielona w obrębie całego workflow.
  • $getWorkflowStaticData('node') - pamięć przypisana do konkretnego node'a.
// Pobierz "pamięć" workflow
const staticData = $getWorkflowStaticData('global');
// Odczytaj ostatnie ID (przy pierwszym uruchomieniu będzie undefined)
const ostatnieId = staticData.lastId || 0;
// Przetwórz tylko nowe rekordy
const nowe = $input.all().filter(i => i.json.id > ostatnieId);
// Zapisz najwyższe ID na następny raz
if (nowe.length > 0) {
staticData.lastId = Math.max(...nowe.map(i => i.json.id));
}
return nowe;

Gdy ten sam fragment logiki (walidacja, wysyłka powiadomienia, zapis do bazy) powtarza się w wielu miejscach, wydziel go do osobnego sub-workflow i wywołuj jak funkcję. Jedna zmiana w jednym miejscu - zamiast poprawiania pięciu kopii. To podstawa utrzymywalnych, dużych automatyzacji.

Execute Sub-workflow Trigger

Start sub-workflow - "When Executed by Another Workflow". Odbiera dane przekazane przez rodzica i definiuje, jakich wejść oczekuje.

Execute Sub-workflow

Node w workflow-rodzicu, który wywołuje sub-workflow, przekazuje mu itemy i odbiera wynik z powrotem do dalszych kroków.

Execute Sub-workflow wywołuje poniższy sub-workflow:

Powtarzalną logikę wydzielasz do sub-workflow i wywołujesz ją jak funkcję - jedna zmiana zamiast pięciu kopii.

  1. Stwórz sub-workflow i dodaj jego trigger - Załóż nowy workflow. Jako pierwszy node dodaj Execute Sub-workflow Trigger ("When Executed by Another Workflow"). To on przyjmie dane od rodzica.
  2. Zbuduj wewnątrz powtarzalną logikę - Dodaj kroki, które chcesz uwspólnić - np. walidację e-maila, wzbogacenie danych, wysyłkę na Slacka. Zakończ node'em, którego wyjście chcesz zwrócić rodzicowi.
  3. W workflow-rodzicu dodaj Execute Sub-workflow - W miejscu, gdzie ma zadziałać uwspólniona logika, wstaw node Execute Sub-workflow i wskaż utworzony sub-workflow (z listy lub po ID).
  4. Przekaż dane do środka - Itemy wpinane do node'a Execute Sub-workflow trafiają do sub-workflow jako jego dane wejściowe. Mapuj tylko to, co sub-workflow naprawdę potrzebuje - utrzymasz "kontrakt" czysty.
  5. Odbierz wynik i jedź dalej - Wyjście sub-workflow wraca do node'a Execute Sub-workflow w rodzicu, a kolejne node'y korzystają z niego jak ze zwykłych danych. Gotowe - masz "funkcję" w n8n.

Obsługa błędów - automaty muszą być przewidywalne

Dział zatytułowany „Obsługa błędów - automaty muszą być przewidywalne”

W produkcji nie pytasz, "czy coś się wysypie", tylko "kiedy". API zwróci 500, sieć mrugnie, dane przyjdą w złym formacie. Różnica między hobbystycznym a profesjonalnym automatem to nie brak błędów - to zaplanowana reakcja na nie. n8n daje trzy poziomy: workflow, node i jawne przerwanie.

Zbuduj jeden workflow, którego start to node Error Trigger. Następnie w ustawieniach swoich workflow (Settings → Error Workflow) wskaż go jako workflow obsługi błędów. Od tej chwili każda nieobsłużona awaria uruchomi ten workflow i przekaże mu szczegóły błędu - możesz wysłać alert na Slacka/e-mail, zalogować incydent albo utworzyć zgłoszenie. Jedno miejsce, które "pilnuje" wszystkiego.

Każda nieobsłużona awaria (czerwona ścieżka) odpala wspólny error workflow, który wysyła alert i loguje incydent.

Pojedynczy node możesz uodpornić w jego zakładce Settings:

Ustawienie Co robi
On Error → Stop Workflow Domyślne. Błąd node'a zatrzymuje całe wykonanie (i uruchamia error workflow, jeśli ustawiony).
On Error → Continue Wcześniej "Continue On Fail". Workflow leci dalej mimo błędu node'a - używaj świadomie, gdy pojedyncza porażka nie powinna walić całości.
On Error → Continue (using error output) Node zyskuje osobne wyjście błędów - kierujesz tamtędy nieudane itemy i obsługujesz je inną ścieżką.
Retry On Fail Automatyczne ponawianie po błędzie - ratuje przy chwilowych problemach sieci/API.
Max. Tries Maksymalna liczba prób (łącznie z pierwszą). Nie ustawiaj absurdalnie wysoko.
Wait Between Tries (ms) Odstęp między próbami - daje API czas "dojść do siebie" i łagodzi rate limity.

Czasem chcesz celowo przerwać workflow, gdy dane nie spełniają warunku (np. brak wymaganego pola). Służy do tego node Stop And Error: rzuca błąd z własnym komunikatem, zatrzymuje wykonanie i - jeśli masz error workflow - uruchamia obsługę. To lepsze niż "cicha" kontynuacja na błędnych danych.

Zanim ruszysz dalej, odpowiedz sobie na te pytania - na głos albo w dwóch zdaniach na kartce. Jeśli przy którymś się zawahasz, wróć do podlinkowanej sekcji.

  1. Dlaczego w trybie Run Once for Each Item masz od razu dostęp do $json bez pętli, a w Run Once for All Items musisz sam iterować po $input.all()? Jeśli nie masz pewności - Dwa tryby uruchamiania.
  2. Co dokładnie stanie się, jeśli Code Node zwróci samą liczbę albo string zamiast listy obiektów z kluczem json? Jeśli nie masz pewności - Format zwracanych danych.
  3. Masz do zbudowania prosty warunek "jeśli pole X jest puste, ustaw wartość domyślną" - sięgasz po Code Node czy zostajesz przy node'ach Set/IF? Uzasadnij wybór. Jeśli nie masz pewności - Kiedy kod, a kiedy node'y.
  4. Jaka jest różnica między ustawieniem node'a On Error → Continue a Continue (using error output) - i kiedy przyda Ci się to drugie? Jeśli nie masz pewności - Ustawienia na poziomie node'a.
Mini-zadanie: zbuduj sub-workflow liczący sumę zamówień
  1. Stwórz nowy workflow "Suma zamówień" i dodaj Execute Sub-workflow Trigger jako pierwszy node.
  2. Dodaj Code Node w trybie Run Once for All Items, który sumuje pole kwota ze wszystkich przychodzących itemów i zwraca jeden item z polami liczbaZamowien i sumaPLN - wzoruj się na przykładzie w sekcji Format zwracanych danych.
  3. Dodaj Stop And Error, jeśli lista wejściowa jest pusta - świadome przerwanie zamiast zwrócenia sumy 0.
  4. W osobnym workflow-rodzicu dodaj Manual Trigger, node Edit Fields tworzący kilka testowych itemów z polem kwota, a potem Execute Sub-workflow wskazujący workflow z kroku 1.
  5. Uruchom rodzica i sprawdź w podglądzie wyniku node'a Execute Sub-workflow, czy suma się zgadza.

Co warto zapamiętać z tego modułu

  • Code Node ma dwa tryby: "Run Once for All Items" (cały zbiór, $input.all()) i "Run Once for Each Item" (pojedynczy item, $json).
  • Zawsze zwracaj dane w formacie itemów: { json: { ... } } (Python: "json" i prefiks _).
  • Najpierw node'y, kod dopiero gdy naprawdę trzeba - mniej kodu to mniej rzeczy do utrzymania.
  • Zmienne wbudowane ($input, $json, $now/$today jako Luxon, $jmespath) skracają kod o połowę.
  • Static data ($getWorkflowStaticData) daje pamięć między uruchomieniami - zapis utrwala się tylko po udanym wykonaniu produkcyjnym.
  • Sub-workflow (Execute Sub-workflow + jego trigger) to "funkcje" n8n - wydzielaj powtarzalną logikę.
  • Obsługa błędów na trzech poziomach: error workflow (Error Trigger), ustawienia node'a (On Error, retry) i Stop And Error.

Częste pytania

Który tryb Code Node wybrać - "All Items" czy "Each Item"?

Jeśli przekształcasz każdy rekord niezależnie (zmiana pól, formatowanie) - wybierz Run Once for Each Item: kod jest krótszy, a bieżący item masz w $json. Jeśli musisz widzieć cały zbiór naraz (suma, sortowanie, łączenie, deduplikacja) albo zmieniasz liczbę itemów - wybierz Run Once for All Items i pracuj na $input.all().

Czy mogę zainstalować pakiet npm w Code Node?

W n8n Cloud nie - dostępne są tylko wbudowane moduły dopuszczone przez n8n (jak crypto). Na instancji self-hosted możesz włączyć moduły zmiennymi środowiskowymi NODE_FUNCTION_ALLOW_BUILTIN (wbudowane Node.js) oraz NODE_FUNCTION_ALLOW_EXTERNAL (pakiety npm zainstalowane obok n8n). Wiele typowych potrzeb (daty, zapytania JSON) pokrywają jednak wbudowane $now/Luxon i $jmespath.

Dlaczego moje static data nie zapamiętuje wartości podczas testów?

Bo dane statyczne utrwalają się tylko po udanym wykonaniu produkcyjnym (np. uruchomienie przez trigger w aktywnym workflow). Ręczne uruchomienia z edytora nie zapisują zmian - dlatego klikając "Test" w kółko, nie zobaczysz przyrastającej pamięci. Włącz workflow i sprawdź na realnym wykonaniu.

Kiedy wydzielić logikę do sub-workflow?

Gdy ten sam fragment powtarza się w kilku workflow, gdy kanwa robi się za duża do ogarnięcia, albo gdy chcesz mieć jeden testowalny "klocek" (np. "wyślij powiadomienie"). Dla małej logiki używanej raz sub-workflow zwykle nie warto - dodaje tylko warstwę pośrednią.

Jaka jest różnica między "Continue On Fail" a Stop And Error?

"Continue" (dawniej "Continue On Fail") sprawia, że workflow leci dalej mimo błędu node'a - używasz go, gdy pojedyncza porażka nie powinna zatrzymać całości. Stop And Error robi odwrotnie: celowo rzuca błąd i przerywa wykonanie, gdy dane nie spełniają warunku - to świadome, kontrolowane zatrzymanie zamiast cichego przepuszczenia złych danych.

Następny krok

Utknąłeś w tym module albo coś jest nieaktualne? Napisz do mnie - poprawię materiał.

made with ❤️ by aitomate.pl - Łukasz Podgórski