La maggior parte dei workflow vive di “dati che passano” tra i nodi: arrivano, vengono usati, poi spariscono. Ma spesso serve qualcosa di più: un piccolo stato che sopravvive tra un’esecuzione e l’altra, contatori, cursori di paginazione, mapping che non vuoi riscaricare ogni volta. Qui entra in gioco $workflow n8n e i Dati Statici del workflow: un meccanismo leggero per memorizzare configurazioni e micro‑stati direttamente nell’automazione, senza installare database esterni. In questa guida pratica vedremo cosa sono i dati statici del workflow n8n, come usarli in sicurezza, quando preferirli (e quando no), e come comporli con le variabili di espressione n8n ($json, $node, $env) per routing, naming e logging. Troverai esempi reali (cursore API, contatori, feature flags), consigli su idempotenza e concorrenza esecuzioni n8n, e un mini‑framework per crescere senza introdurre complessità. Se vuoi migliorare produttività e affidabilità dei tuoi flussi da marketer tecnico, questa è la base per portare governance e controllo nelle tue automazioni.
[IMG: panoramica dello scope dati in n8n: item → node → workflow static data]
Dati transitori vs stato persistito: quando serve e perché
Nel modello a item di n8n, i dati viaggiano da un nodo all’altro sotto forma di array di oggetti JSON ($json in espressioni n8n). Questi dati sono transitori: cambiano ad ogni esecuzione. A volte però serve uno “zainetto” persistente:
- Configurazioni persistenti workflow: piccole preferenze (formati, mapping campi) che non vuoi hardcodare nel nodo.
- Cursore di paginazione API: l’ultimo next_page token processato; al prossimo run riparti da lì.
- Contatori di esecuzione: quante volte hai pubblicato oggi, per canale/campagna.
- Cache leggera in automazioni: lookup/mapping in n8n a bassa volatilità (es. ID canale → nome pubblico).
- Idempotenza nei workflow: memorizza checksum o riferimenti di eventi già processati.
Differenze chiave
- Dati transitori: vivono nel run corrente (item, output nodi). Perfetti per trasformazioni veloci.
- Dati statici del workflow n8n: persistono tra run; dimensione piccola; ideali per piano di controllo e micro‑config.
Best practice n8n espressioni: separa il dato “business” (item) dal “controllo” (static data). E tieni l’input del workflow come sorgente della verità: lo stato persistito serve a ricordare “dove sei arrivato”, non a sostituire la fonte.
[IMG: tabella comparativa “transitorio vs statico” con esempi]
L’oggetto $workflow di n8n: metadati, scope e riferimenti utili
$workflow n8n è accessibile nelle espressioni e ti dà metadati dell’automazione utili per naming, logging e routing. Esempi tipici:
-
Esempi in espressioni
-
Nome workflow: {{$workflow.name}}
-
ID workflow: {{$workflow.id}}
-
Naming file/log: report{{$workflow.name}}{{ $now }}.csv
-
Tagging run: {{$workflow.id}}-{{ $env.ENVIRONMENT }}
-
Differenze con $node e $json in espressioni n8n
-
$json: il dato dell’item corrente (per esempio {{$json.email}}).
-
$node[“Nome Nodo”].json.campo: accedi a un output specifico di un nodo precedente.
-
$env: leggi variabili d’ambiente (token, ID foglio, URL API).
-
$workflow: metadati del flusso per scopi cross‑cutting (naming, logging, branch per ambiente).
Scope dei dati in n8n
- Scope globale del workflow: utile per etichette e contesto.
- Scope del singolo nodo: usalo per riferimenti puntuali o default di quel nodo.
Esempi d’uso concreti
- Routing per ambiente: If → {{$env.ENVIRONMENT}} == ‘prod’ → percorsi diversi di notifica.
- Logging coerente: Append log row → fields: workflowid: {{$workflow.id}}, executionts: {{ $now }}.
- Convenzioni di naming file/output: s3://bucket/{{ $env.BRAND }}/{{$workflow.name}}/{{ $now }}.json
Insight: usare $workflow negli output e nelle notifiche rende ogni run auto‑documentata e facile da investigare.
[IMG: screenshot placeholder con espressioni $workflow, $env, $json nel pannello proprietà]
Pattern di persistenza: cursori, contatori, mapping e feature flags
Quando memorizzare
- Cursore di paginazione API: salva next_page token ad ogni run concluso con successo. Se fallisce, non aggiornare (eviti “buchi”).
- Contatori di esecuzione: mantieni un contatore giornaliero per canale (utile per rate limiting applicativo).
- Cache di configurazioni/mapping: es. canale_id → nome; TTL logico (ricarica ogni N run/ore).
- Feature flags e versioning di flusso: interruttori per attivare/disattivare path senza modificare i nodi.
Esempio (pseudo‑JS in un Code node) per cursore API e contatore giornaliero:
// Pseudocodice per illustrare il pattern
// Lettura stato persistito (es. oggetto staticData globale)
const state = staticData.global || {}; // fallback oggetto vuoto
const today = new Date().toISOString().slice(0,10);
// Cursore API
const previousCursor = state.nextCursor || null;
// ... chiama API usando previousCursor ...
const nextCursor = $json.apiResponse?.next_cursor || null;
if (nextCursor) state.nextCursor = nextCursor;
// Contatori giornalieri per canale
state.counts = state.counts || {};
const channel = $json.channel || 'unknown';
state.counts[today] = state.counts[today] || {};
state.counts[today][channel] = (state.counts[today][channel] || 0) + 1;
// Salvataggio
staticData.global = state;
return [{ json: { previousCursor, nextCursor, processed: 1 } }];
Note operative (ciclo di vita, limiti)
- Mantieni lo stato piccolo (stringhe, numeri, array/dict compatti).
- In caso di migrazione/cleanup, prevedi reset dei dati statici (svuota l’oggetto o definisci una chiave di versione, es. state.version = 2).
- Evita dataset grandi o lookup complessi: passa a un DB/KV store esterno.
[IMG: diagramma con “API → process → update cursor → next run resumes”]
Concorrenza e idempotenza: evitare race e duplicazioni
Concorrenza esecuzioni n8n
- Se il workflow può avviarsi in parallelo (Cron sovrapposti, trigger multipli), rischi race condition sullo stato.
- Pattern di lock leggero: salva un lock con timestamp ed execution marker; se un’altra run lo vede “attivo”, si mette in attesa o esce.
Idempotenza nei workflow
- Prima di eseguire side effects (email, inserimenti), verifica in uno “storico” che l’ID sorgente non sia già stato processato.
- Usa contatori/checksum nello stato per saltare azioni ripetute.
- Gestisci retry con backoff e segna esiti (success/fail) con motivazione per safe reprocessing.
Esempio (pseudo‑JS) per lock e idempotenza:
const state = staticData.global || {};
const now = Date.now();
const lockTTLms = 5 * 60 * 1000; // 5 minuti
// Lock
if (state.lock && (now - state.lock.ts) < lockTTLms) {
// lock attivo: abbandona o riprova più tardi
return [{ json: { skipped: true, reason: 'locked' } }];
}
// Acquisisci lock
state.lock = { ts: now };
staticData.global = state;
// ... esegui lavoro idempotente ...
// es: se vedi source_event_id in state.processed, salta
state.processed = state.processed || {};
const evt = $json.source_event_id;
if (evt && state.processed[evt]) {
return [{ json: { skipped: true, reason: 'already_processed' } }];
}
// Esegui side effect e marca come processato
// ... side effect ...
state.processed[evt] = true;
delete state.lock;
staticData.global = state;
return [{ json: { ok: true } }];
Suggerimenti
- Se hai traffico elevato o necessità di garanzie forti, usa un KV store/DB con lock atomico (redis, postgres) e conserva lo stato di idempotenza lì.
- Per piccoli team e throughput basso, il lock leggero in dati statici è sufficiente.
[IMG: timeline con due esecuzioni concorrenti e lock che evita conflitti]
Espressioni e $workflow in pratica: routing, naming, logging
Esempi pratici (copiabili):
- Routing per canale/ambiente
- If → Condizione: {{$env.ENVIRONMENT}} === ‘prod’ && {{$json.channel}} === ‘linkedin’
- True branch: pubblica; False: logga come “dry‑run”.
- Naming coerente per export
- File Name: {{ $env.BRAND }}{{ $workflow.name }}{{ new Date().toISOString().slice(0,10) }}.csv
- Logging strutturato (Append Row su Google Sheets o DB)
- Fields: workflowid: {{$workflow.id}}, runts: {{ $now }}, item_id: {{$json.id}}, outcome: {{$json.status}}
Pattern per lookup/mapping in n8n
- Se mapping statico è piccolo (es. codice → canale), conservalo nei dati statici e leggilo con un Code node in avvio run.
- Aggiorna il mapping in modo controllato (flag di versione; se cambia, ricalcola al primo run).
Differenze pratiche tra $workflow, $node, $json e $env (sintesi)
- $json: payload dell’item corrente.
- $node: referenzia output di altri nodi (utile per join/merge).
- $env: configurazioni segrete/ambienti (non committare nel workflow).
- $workflow: metadati; usalo per contesto, naming, tag.
[IMG: pannello proprietà con esempi di espressioni interpolate nei campi]
Quando NON usare i Dati Statici e alternative scalabili
Evita i Dati Statici se:
- Dataset grandi o query complesse: hai bisogno di filtri, ordinamenti, ricerche—usa un DB/warehouse o un KV store.
- Team concurrency: più utenti/istanze modificano lo stato; rischi conflitti. Meglio un archivio centralizzato con lock transazionali.
- Compliance/audit: serve storico delle modifiche e tracciabilità nel tempo—usa storage versionato.
Alternative consigliate
- DB esterno (Postgres/MySQL): per cursori per sorgente, idempotenza forte, audit.
- KV store (Redis): rate limiting, lock, cache a TTL.
- File/Blob (S3/Drive): export periodici e snapshot dello stato di controllo.
- API di configurazione: mantieni le config in un servizio dedicato e leggile nel workflow.
Pulizia/reset dei Dati Statici
- Prevedi un percorso “Reset”: un Code node che azzera lo stato o incrementa una version key, forzando rigenerazione al prossimo run.
- Migrazioni tra versioni e ambienti: serializza lo stato in JSON, salvalo temporaneamente, poi re‑importalo con trasformazioni minime.
[IMG: decision tree “static data vs DB/KV” con criteri di scelta]
Esempio end‑to‑end: ingest API paginata con cursore, dedupe e log
Obiettivo: chiamare un’API paginata, ripartire dall’ultimo cursore, deduplicare item per id, e loggare risultato.
Blueprint
1) Trigger: Cron ogni 15 minuti.
2) Carica stato: Code node legge state (cursore, processed ids).
3) Chiamata API: HTTP Request → usa previousCursor.
4) Process: dedupe su item_id contro state.processed.
5) Persisti: aggiorna cursore se la chiamata è ok, marca nuovi id processati.
6) Log: Append su Sheet/DB con conteggi.
Pseudo‑JS (Code node “State Manager”):
const state = staticData.global || { version: 1, processed: {}, nextCursor: null };
const prev = state.nextCursor || null;
return [{ json: { state, prevCursor: prev } }];
Chiamata API (HTTP Request) usa {{ $json.prevCursor }} in query; a valle:
const state = $json.state;
const items = $json.apiResponse?.data || [];
const out = [];
for (const it of items) {
if (!state.processed[it.id]) {
out.push(it);
state.processed[it.id] = true;
}
}
state.nextCursor = $json.apiResponse?.next_cursor || state.nextCursor;
staticData.global = state;
return out.map(i => ({ json: i }));
Questo pattern offre persistenza stato automazioni n8n senza DB, con deduplica semplice e ripartenza affidabile.
[IMG: workflow canvas con nodi: Cron → Code(State) → HTTP → Code(Process) → Sheets(DB)]
Quick Takeaways
- Usa $workflow per metadati e contesto: naming coerente, logging e routing per ambiente.
- I Dati Statici sono perfetti per cursori, contatori, mapping piccoli e feature flags.
- Mantieni lo stato leggero e versionato; per dataset grandi passa a DB/KV esterni.
- Gestisci concorrenza con lock leggeri e idempotenza su side effects.
- Combina $json, $node, $env e $workflow per espressioni potenti e leggibili.
- Prevedi reset/migrazione dello stato e monitora con log strutturati.
Conclusione
Portare stabilità e
Scopri la consulenza →

