Kod i sub-workflow w n8n
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: JavaScript i Python
Dział zatytułowany „Code Node: JavaScript i Python”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ć.
Dwa tryby uruchamiania (kluczowa decyzja)
Dział zatytułowany „Dwa tryby uruchamiania (kluczowa decyzja)”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.
Format zwracanych danych
Dział zatytułowany „Format zwracanych danych”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: { ... } }.
Run Once for All Items (JavaScript)
Dział zatytułowany „Run Once for All Items (JavaScript)”// Pobranie wszystkich itemów narazconst 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 } }];Run Once for Each Item (JavaScript)
Dział zatytułowany „Run Once for Each Item (JavaScript)”// $json to bieżący item - bez pętliconst imie = $json.firstName;const nazwisko = $json.lastName;
// Zwróć pojedynczy obiekt (n8n sam zbuduje listę)return { json: { pelneImie: `${imie} ${nazwisko}`, email: $json.email.toLowerCase() }};A Python?
Dział zatytułowany „A Python?”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 - Pythonreturn { "json": { "pelneImie": _json["firstName"] + " " + _json["lastName"] }}Kiedy kod, a kiedy node'y
Dział zatytułowany „Kiedy kod, a kiedy node'y”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.
Dlaczego domyślnie node'y
Dział zatytułowany „Dlaczego domyślnie node'y”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
fetchw kodzie - Workflow ma czytać ktoś nietechniczny
- Wystarczy proste wyrażenie
{{ }}w polu node'a - "Kusi Cię" przepisanie połowy workflow na jeden wielki skrypt
Metody i zmienne wbudowane
Dział zatytułowany „Metody i zmienne wbudowane”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. |
Krótkie przykłady
Dział zatytułowany „Krótkie przykłady”// 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 nazwieconst klient = $('Pobierz klienta').first().json;
// Wyciągnij listę e-maili z zagnieżdżonej strukturyconst maile = $jmespath($json, "uzytkownicy[*].kontakt.email");Dane statyczne workflow (static data)
Dział zatytułowany „Dane statyczne workflow (static data)”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.
Jak to działa
Dział zatytułowany „Jak to działa”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.
Przykład: zapamiętaj ostatnie przetworzone ID
Dział zatytułowany „Przykład: zapamiętaj ostatnie przetworzone ID”// Pobierz "pamięć" workflowconst staticData = $getWorkflowStaticData('global');
// Odczytaj ostatnie ID (przy pierwszym uruchomieniu będzie undefined)const ostatnieId = staticData.lastId || 0;
// Przetwórz tylko nowe rekordyconst nowe = $input.all().filter(i => i.json.id > ostatnieId);
// Zapisz najwyższe ID na następny razif (nowe.length > 0) { staticData.lastId = Math.max(...nowe.map(i => i.json.id));}
return nowe;Sub-workflow - modularność przez Execute Workflow
Dział zatytułowany „Sub-workflow - modularność przez Execute Workflow”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.
Dwa node'y, które tworzą parę
Dział zatytułowany „Dwa node'y, które tworzą parę”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.
Jak to zbudować - krok po kroku
Dział zatytułowany „Jak to zbudować - krok po kroku”- 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.
- 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.
- 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).
- 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.
- 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.
Dedykowany error workflow + Error Trigger
Dział zatytułowany „Dedykowany error workflow + Error Trigger”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.
Ustawienia na poziomie node'a
Dział zatytułowany „Ustawienia na poziomie node'a”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. |
Stop And Error - świadome przerwanie
Dział zatytułowany „Stop And Error - świadome przerwanie”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.
Sprawdź się
Dział zatytułowany „Sprawdź się”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.
- Dlaczego w trybie Run Once for Each Item masz od razu dostęp do
$jsonbez pętli, a w Run Once for All Items musisz sam iterować po$input.all()? Jeśli nie masz pewności - Dwa tryby uruchamiania. - 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. - 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.
- 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ń
- Stwórz nowy workflow "Suma zamówień" i dodaj Execute Sub-workflow Trigger jako pierwszy node.
- Dodaj Code Node w trybie Run Once for All Items, który sumuje pole
kwotaze wszystkich przychodzących itemów i zwraca jeden item z polamiliczbaZamowienisumaPLN- wzoruj się na przykładzie w sekcji Format zwracanych danych. - Dodaj Stop And Error, jeśli lista wejściowa jest pusta - świadome przerwanie zamiast zwrócenia sumy 0.
- 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. - 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/$todayjako 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.
made with ❤️ by aitomate.pl - Łukasz Podgórski