Módulo 3: Items y las variables integradas de n8n
4. El item actual con `$json`
Descripción
Al terminar esta lección vas a entender la variable más usada de todo n8n y, al mismo tiempo, la que más preguntas produce en los foros. Vas a saber qué es $json exactamente —no una aproximación, sino su definición literal—, cuál es su relación precisa con $input.item, en qué modo de ejecución es válida y en cuál no, y qué pasa cuando la usas en el modo equivocado. Vas a ver por qué la misma variable que escribes todos los días en las expresiones {{ }} de cualquier campo se comporta distinto dentro de un nodo Code. Y vas a conocer a su hermana menos afortunada: $binary, que existe en las expresiones y no está disponible en el nodo Code.
Esto importa por una razón muy práctica. $json es la variable que traes puesta: si vienes de armar workflows con expresiones, llevas meses escribiendo {{ $json.customer_name }} en los campos de los nodos, y ese reflejo es tan fuerte que se traslada al nodo Code sin pensarlo. En modo Run Once for Each Item, ese reflejo es correcto y te ahorra escribir. En modo Run Once for All Items —que es el modo por defecto, el que trae el desplegable cuando abres un nodo nuevo— el mismo reflejo produce undefined, y ahí empieza a perderse el tiempo revisando nombres de campo que están perfectamente escritos.
Conexión con el módulo: la lección 3 te dio $input completo, incluyendo $input.item. Esta lección toma ese acceso concreto y estudia su atajo, que es la forma en que de verdad vas a escribir el código. Es la última pieza de la capa 1 —el dato que tienes en las manos— y con ella cierras la lectura de la entrada. La lección 5 sale de la capa 1 hacia el resto del workflow. Del Módulo 1 se apoya en la lección 5, que ya te adelantó que $json "no tiene un significado útil" en modo All Items; aquí vas a ver por qué, con la fuente oficial y con el síntoma exacto que produce.
Tres letras que significan una cadena entera
Empecemos por la definición, que es literal y está documentada:
$jsondevuelve los datos JSON de entrada del nodo actual, para el item actual. Es un atajo de$input.item.json.
Esa segunda frase es toda la lección. $json no es una variable independiente: es un nombre corto para una cadena de tres eslabones que ya conoces.
$input → la bandeja de entrada del nodo actual
$input.item → el sobre que estoy procesando ahora
$input.item.json → la carta que hay dentro de ese sobre
$json ≡ $input.item.json
Léelo de derecha a izquierda y verás por qué el atajo existe. Casi siempre lo que quieres es la carta: los campos del pedido de Cumbre. Escribir $input.item.json.customer_name cada vez es tedioso, y $json.customer_name dice exactamente lo mismo.
La analogía: es la diferencia entre decir "el contenido del paquete que tengo en las manos en este momento" y decir "esto". Cuando solo estás atendiendo un paquete, "esto" es inequívoco.
Y ahí está, exactamente, la condición que hace válido el atajo: "esto" solo es inequívoco si hay un paquete en las manos.
La condición: el modo por item
La tabla oficial de referencia de n8n marca casi todos los accesos como disponibles en el nodo Code sin más comentarios. Con $json hace una excepción explícita, y es la única de toda la tabla:
$json— Atajo de$input.item.json. Datos JSON entrantes de un nodo. Disponible en el nodo Code cuando se ejecuta una vez por cada item.
Vamos a desarmar por qué, porque el "por qué" es lo que hace que no se te olvide.
En modo Run Once for Each Item, tu código corre una vez por cada item que entra. En cada una de esas corridas hay un item en curso, bien definido: el que le toca a esa vuelta. $input.item apunta a él, y $json apunta a su contenido. Todo cierra.
En modo Run Once for All Items, tu código corre una sola vez para el lote entero. No hay un item en curso, porque los tienes todos a la vez. Preguntar por "el item actual" es como preguntarle al profesor que tiene los doscientos exámenes sobre el escritorio cuál está corrigiendo: ninguno en particular, o todos. La pregunta no tiene una respuesta útil.
Por eso la regla es corta y conviene memorizarla:
$jsones del modo por item. En modo All Items, se lee del arreglo:$input.all()y un bucle.
Qué pasa exactamente si lo usas en el modo equivocado
Esta es la parte que a la gente le cuesta encontrar, porque el síntoma es confuso.
Escribir $json.customer_name en un nodo Code en modo Run Once for All Items no suele producir un error rojo con un mensaje claro. Lo que produce es undefined, o un error sobre leer una propiedad de algo que no existe, dependiendo de tu versión y de qué estés haciendo con el valor. Y el undefined es lo peligroso: se propaga silenciosamente por el resto del script y el problema aparece más adelante, cuando un nodo posterior recibe un campo vacío.
El diagnóstico equivocado que hace casi todo el mundo la primera vez es este: "el campo debe llamarse distinto". Se abre el panel de entrada, se verifica que el campo se llama customer_name, se vuelve al código, se prueba con comillas, con corchetes, con mayúsculas. Media hora después alguien pregunta cuál es el modo del nodo.
Cómo detectarlo en cinco segundos: si tu script está en modo All Items y contiene $json en cualquier lugar, ese es el problema. Es una búsqueda de texto, no un razonamiento.
Y una recomendación honesta: verifica el comportamiento exacto en tu instancia. Escribe un nodo Code en modo All Items con return [{ json: { test: $json } }];, ejecútalo, y anota qué ves. Puede ser undefined, puede ser un objeto vacío, puede ser un error. Saber cuál de los tres es el tuyo convierte un diagnóstico de media hora en uno de cinco segundos, porque vas a reconocer el síntoma.
Ejemplo trabajado: la misma lógica, los dos caminos
Requerimiento de Cumbre: "a cada pedido, agrégale total_units con la suma de las cantidades de sus líneas, y is_priority en verdadero si llega a veinte unidades."
Es una transformación estrictamente uno a uno: cada pedido se resuelve mirando solo sus propios campos, y entran tantos como salen. Es el caso ideal del modo por item, y verlo lado a lado con el otro modo es la mejor forma de fijar el papel de $json.
Versión A — Run Once for Each Item, con $json:
// ============================================================
// Nodo: Code — "Flag priority orders"
// Modo: Run Once for Each Item
//
// ENTRADA: un pedido de Cumbre con line_items
// SALIDA: el mismo pedido, con total_units e is_priority
// SUPUESTOS: quantity es número. line_items puede faltar o venir vacío.
// REGLA: is_priority = 20 unidades o más
// ============================================================
// --- ACTO 1: leer ---------------------------------------------
const order = $json; // el contenido del item actual
// --- ACTO 2: procesar -----------------------------------------
const lines = order.line_items || []; // guarda: si el campo falta, uso lista vacía
let totalUnits = 0;
for (const line of lines) {
totalUnits = totalUnits + line.quantity;
}
// --- ACTO 3: devolver -----------------------------------------
return {
json: {
...order, // conservo todos los campos originales
total_units: totalUnits,
is_priority: totalUnits >= 20,
},
};
Versión B — Run Once for All Items, sin $json:
// ============================================================
// Nodo: Code — "Flag priority orders"
// Modo: Run Once for All Items
//
// ENTRADA: N pedidos de Cumbre con line_items
// SALIDA: los mismos N, con total_units e is_priority
// SUPUESTOS: quantity es número. line_items puede faltar o venir vacío.
// REGLA: is_priority = 20 unidades o más
// ============================================================
// --- ACTO 1: leer ---------------------------------------------
const items = $input.all();
// --- ACTO 2: procesar -----------------------------------------
const output = [];
for (const item of items) {
const order = item.json; // ← aquí va lo que en la versión A era $json
const lines = order.line_items || [];
let totalUnits = 0;
for (const line of lines) {
totalUnits = totalUnits + line.quantity;
}
output.push({
json: {
...order,
total_units: totalUnits,
is_priority: totalUnits >= 20,
},
});
}
// --- ACTO 3: devolver -----------------------------------------
return output;
Qué esperar. Las dos versiones producen exactamente el mismo panel de salida. Con la semilla de cinco pedidos del Módulo 1, salen 5 items, cada uno con todos sus campos originales más los dos nuevos:
| order_id | customer_name | total_units | is_priority |
|---|---|---|---|
| ORD-2041 | Luna Coffee | 18 | false |
| ORD-2042 | North Bakery | 5 | false |
| ORD-2043 | Luna Coffee | 10 | false |
| ORD-2044 | Sunrise Market | 40 | true |
| ORD-2045 | Andes Deli | 0 | false |
Verifica en la vista JSON que total_units aparece sin comillas —es un número— y que is_priority aparece como true o false sin comillas —es un booleano de verdad, no el texto "true"—. Ese detalle importa: si fuera texto, un nodo Filter que compare contra verdadero se comportaría de forma rara.
Ahora compara las dos versiones línea por línea. El cuerpo de la lógica es idéntico: las líneas que calculan totalUnits no cambian ni un carácter. Lo único que cambia es el andamiaje:
| Versión A (Each Item) | Versión B (All Items) | |
|---|---|---|
| Cómo llego al pedido | $json | item.json, dentro de un bucle |
| Bucle externo | No hay | for (const item of items) |
| Acumulador de salida | No hay | const output = [] |
| Qué devuelvo | Un objeto | Un arreglo |
La equivalencia mecánica que conviene fijar es esta: $json en el modo por item es item.json dentro del bucle en el modo All Items. Cuando traduzcas un script de un modo al otro, esa es la sustitución que haces.
$json en las expresiones y $json en el nodo Code
Aquí hay un matiz que confunde a mucha gente y vale la pena aclarar bien, porque explica por qué el reflejo de las expresiones se traslada mal al código.
Cuando escribes {{ $json.customer_name }} en el campo de un nodo Edit Fields, un Filter o un correo, estás usando la misma variable. Y ahí siempre funciona. ¿Por qué?
Porque los nodos de n8n procesan cada item por separado, de forma automática. Es el comportamiento de fondo del producto que vimos en la lección 2: si a un nodo de correo le llegan cinco items, manda cinco correos, y cuando evalúa la expresión de cada uno, "el item actual" es el que le toca en esa vuelta. Es decir: el editor de expresiones vive siempre en algo equivalente al modo por item. No hay un modo "todos los items" para un campo de formulario.
El nodo Code es el único lugar de n8n donde tú eliges ese comportamiento. Y como el valor por defecto del desplegable es Run Once for All Items, la primera vez que abres un nodo Code estás, sin saberlo, en el único contexto de n8n donde $json no aplica.
Dicho de otra forma: no es que $json se comporte distinto en el nodo Code. Es que el nodo Code es el único sitio donde puedes salir del contexto en el que $json tiene sentido.
Esa es, en mi experiencia, la explicación que hace que el tema deje de dar problemas. No es una excepción arbitraria: es la consecuencia directa de que el nodo Code tenga dos modos y el resto de n8n tenga uno solo.
$binary: la hermana que no viajó
Ya que estamos con los atajos, conviene cerrar el par.
$json es el atajo de $input.item.json. Su hermana simétrica es $binary, atajo de $input.item.binary, que da acceso al compartimento de archivos del item actual —el que estudiaste en la lección 2—.
Y aquí viene el dato que casi ningún tutorial menciona: $binary no está disponible dentro del nodo Code. La tabla oficial de referencia de nodos previos lo marca con una cruz, mientras que $json lleva su marca condicional y todos los métodos de $input llevan marca positiva. Es el único de la lista que sencillamente no está.
// ✅ En una expresión, dentro del campo de un nodo
{{ $binary['orders-file'].fileName }}
// ❌ En un nodo Code: no disponible
const fileName = $binary['orders-file'].fileName;
Qué hacer entonces si necesitas datos binarios desde código. Dos caminos, en orden de preferencia:
Uno: no los toques desde el nodo Code. La enorme mayoría de las manipulaciones de archivos tienen un nodo dedicado —Extract from File, Convert to File, Read/Write File From Disk— y ese nodo hace el trabajo mejor y sin que tú manejes bytes.
Dos: si de verdad necesitas el contenido, usa la ruta documentada. El recetario oficial tiene una página dedicada a obtener el búfer de datos binarios desde el nodo Code, y ese es el camino soportado. No es tema de esta guía, pero conviene que sepas dónde está.
Y lo que sí es tema de esta guía, porque lo vas a encontrar: el compartimento binary sigue estando en el item aunque $binary no esté disponible. Puedes leerlo y copiarlo perfectamente con $input.item.binary o con item.binary dentro de un bucle:
// Modo: Run Once for Each Item
// Transformo los datos y conservo el archivo adjunto
return {
json: { ...$json, reviewed: true },
binary: $input.item.binary, // el sobre completo sí lo tengo
};
Que el atajo no exista no significa que el compartimento desaparezca. Es una distinción fina y es exactamente el tipo de cosa que separa un diagnóstico de dos minutos de una tarde perdida.
Cuándo usar $json y cuándo $input.item
Con todo lo anterior, la elección entre los dos es sencilla y se resuelve con una pregunta.
¿Necesito la carta o el sobre completo? La carta —los campos de negocio— es
$json. Es el 95% de los casos. El sobre completo —para llegar abinaryo para inspeccionar la estructura— es$input.item.
// Modo: Run Once for Each Item
$json; // la carta: { order_id: 'ORD-2041', customer_name: 'Luna Coffee', ... }
$json.order_id; // 'ORD-2041'
$input.item; // el sobre: { json: {...}, binary: {...}, pairedItem: ... }
$input.item.json; // la carta otra vez: idéntico a $json
$input.item.binary; // el compartimento de archivos
Un error que vale la pena anticipar, porque es la trampa clásica del Módulo 1 aplicada aquí: no confundas los niveles al devolver.
// ❌ Un nivel de más: mete el sobre dentro del compartimento
return { json: $input.item };
// ✅ Devuelve el contenido tal cual
return { json: $json };
// ✅ O, equivalente
return { json: $input.item.json };
La primera versión produce una salida donde cada item tiene un campo llamado json que contiene los datos, y todos los nodos siguientes se rompen porque ahora hay que llegar a los campos con $json.json.order_id. Es el error de "meter el item completo dentro de json" que ya viste en el Módulo 1, y $input.item es justo la variable que invita a cometerlo.
Un compañero de viaje: $itemIndex
Cerramos con una variable pequeña que suele aparecer junto a $json y que la lección 6 desarrolla: $itemIndex.
Qué es. Un número que dice la posición del item que se está procesando dentro de la lista de items de entrada. Empieza en 0, como todos los índices.
Para qué sirve en modo por item. Como cada corrida es independiente y no sabe nada de las demás, $itemIndex es la única pista que tienes sobre dónde estás dentro del lote.
// Modo: Run Once for Each Item
return {
json: {
...$json,
// La posición de este pedido en el lote. Útil para depurar
// y para dar un orden estable a los registros de salida.
position_in_batch: $itemIndex,
},
};
Qué esperar. Con cinco pedidos, los items de salida llevan position_in_batch con valores 0, 1, 2, 3 y 4 respectivamente.
Y la limitación, que ya adelantó el Módulo 1: $itemIndex te dice tu posición, pero no cuántos items hay en total. Para decir "3 de 5" necesitas el 5, y el 5 solo lo tienes en modo All Items con $input.all().length. Es el ejemplo más limpio de la asimetría entre los dos modos.
Errores comunes
Usar $json en modo All Items (práctico). Qué pasa: escribes $json.customer_name en un nodo Code recién creado —que por defecto está en Run Once for All Items— y obtienes undefined o un error sobre una propiedad de algo indefinido. Empiezas a dudar del nombre del campo, que está bien escrito. Por qué pasa: $json es el atajo de $input.item.json, y en modo All Items no hay un item en curso al que apuntar. Es el único acceso de la tabla oficial con una condición de modo explícita. Cómo detectarlo: búsqueda de texto. Si tu script está en modo All Items y contiene $json, ese es el problema; no hace falta razonar más. Cómo corregirlo: si la transformación es genuinamente uno a uno, cambia el modo a Run Once for Each Item y el script queda más corto. Si necesitas ver el lote entero, quédate en All Items y sustituye $json por item.json dentro de un bucle sobre $input.all().
Traer el reflejo de las expresiones sin traducirlo (conceptual). Qué pasa: alguien que lleva meses escribiendo {{ $json.campo }} en los campos de los nodos abre su primer nodo Code y escribe lo mismo, y no entiende por qué ahora falla algo que siempre funcionó. Por qué pasa: en las expresiones no existe el concepto de modo —n8n procesa item por item siempre— así que $json nunca falla ahí. El nodo Code es el único lugar de n8n donde puedes elegir el otro comportamiento, y su valor por defecto es justamente ese. Cómo detectarlo: si tu intuición dice "esto siempre funcionó", pregúntate si "siempre" era dentro de un campo de formulario. Cómo corregirlo: adopta el hábito de mirar el desplegable Mode antes de escribir la primera línea, y de escribir el modo en el comentario de cabecera del script para que se vea sin abrir el formulario.
Devolver $input.item donde iba $json (práctico). Qué pasa: escribes return { json: $input.item }; y la salida queda con un nivel de más: cada item tiene un campo llamado json que contiene tus datos. Todos los nodos siguientes dejan de encontrar los campos. Por qué pasa: $input.item es el sobre completo y $json es su contenido; poner un sobre dentro del compartimento de otro sobre produce un sobre anidado. Cómo detectarlo: el punto 3 de la verificación del Módulo 1 —si en la vista JSON de la salida aparece la palabra json como campo, es esto—. Cómo corregirlo: { json: $json } o, equivalente, { json: $input.item.json }.
Buscar $binary en el nodo Code (práctico). Qué pasa: tu workflow procesa un archivo, escribes $binary en el nodo Code para leerlo y obtienes un error de variable no definida, o el autocompletado no lo ofrece. Por qué pasa: es el único acceso de la tabla oficial que aparece marcado como no disponible en el nodo Code, y casi ningún tutorial lo menciona. Cómo detectarlo: si tu script menciona $binary, ese es el punto. Cómo corregirlo: el compartimento sí existe en el item, aunque el atajo no. Léelo con $input.item.binary en modo por item, o con item.binary dentro del bucle en modo All Items. Y si lo que necesitas es el contenido del archivo, revisa primero si hay un nodo dedicado que resuelva tu caso sin código.
Perder los campos originales al usar $json (práctico). Qué pasa: tu nodo agrega un campo y, al ver la salida, descubres que los pedidos perdieron todo lo demás. Por qué pasa: escribiste return { json: { total_units: totalUnits } }; en lugar de incluir el resto. El nodo Code devuelve exactamente lo que construyes; no conserva nada por su cuenta. Cómo detectarlo: compara la lista de columnas del panel de entrada con la del de salida. Cómo corregirlo: la notación de tres puntos: return { json: { ...$json, total_units: totalUnits } };. Y si tu item traía un archivo, recuerda copiar también binary: $input.item.binary.
Suponer que $itemIndex sirve para saber el tamaño del lote (conceptual). Qué pasa: se intenta construir un campo del estilo "pedido 3 de 5" en modo por item, y el 5 no está por ningún lado. Por qué pasa: $itemIndex da la posición, no el total, y el modo por item no tiene acceso al conjunto por diseño. Cómo detectarlo: si necesitas un dato agregado del lote —el total, el promedio, la cantidad— estás en el modo equivocado. Cómo corregirlo: pásate a modo All Items, donde $input.all().length te da el total, o calcula el total en un nodo anterior y pásalo dentro de cada item.
Ejercicios
Ejercicio 1 — Traduce en las dos direcciones. Te dan este script en modo All Items. (a) Reescríbelo en modo por item usando $json. (b) Después di si la traducción es completamente equivalente o si se pierde algo.
// Nodo: Code — "Normalize customer names"
// Modo: Run Once for All Items
const output = [];
for (const item of $input.all()) {
const order = item.json;
output.push({
json: {
...order,
customer_name: order.customer_name.trim(),
shipping_city: order.shipping_city.toUpperCase(),
},
});
}
return output;
Ver solución
(a) La versión en modo por item:
// ============================================================
// Nodo: Code — "Normalize customer names"
// Modo: Run Once for Each Item
//
// ENTRADA: un pedido de Cumbre con customer_name y shipping_city
// SALIDA: el mismo pedido, con esos dos campos normalizados
// SUPUESTOS: los dos campos son texto y siempre están presentes
// ============================================================
const order = $json;
return {
json: {
...order,
customer_name: order.customer_name.trim(),
shipping_city: order.shipping_city.toUpperCase(),
},
};
Fíjate en la mecánica: desapareció el bucle, desapareció el acumulador, item.json se volvió $json, el push se volvió return directo, y los corchetes del return se fueron. Cinco líneas de andamiaje eliminadas y ni un carácter de la lógica cambiado.
(b) Qué se pierde. En cuanto a resultado, nada: las dos versiones producen items idénticos. Pero hay tres diferencias reales que conviene conocer:
Primera, y a favor de la versión por item: el enlace entre items de entrada y de salida se mantiene automáticamente, porque el modo por item siempre produce un item por cada entrada. Es la regla de "igual cantidad" de la lección 2. En la versión All Items, como se construyen objetos nuevos, estrictamente convendría poner pairedItem.
Segunda, a favor de la versión por item: si un pedido tiene un dato corrupto y revienta, en modo por item las demás corridas pueden seguir; en modo All Items falla el nodo entero y no sale nada.
Tercera, a favor de la versión All Items: es la única que puede crecer. El día que el requerimiento pida "y descarta los duplicados" o "y agrega un resumen", la versión por item hay que reescribirla.
Y un problema que las dos comparten y que ninguna resuelve: order.customer_name.trim() revienta si customer_name no existe o no es texto. Con los pedidos del canal whatsapp de Cumbre, que traen campos vacíos con frecuencia, eso es plausible. La guarda sería (order.customer_name || '').trim().
Por qué funciona: la traducción entre modos es mecánica y ya la practicaste en el Módulo 1. Lo nuevo aquí es ver que la elección no es solo de comodidad: arrastra consecuencias sobre el enlace de items, sobre el manejo de errores y sobre la capacidad de crecer.
Ejercicio 2 — Diagnostica cuatro scripts. Para cada uno, di si funciona, y si no, cuál es el problema exacto y el síntoma que produce.
// A — Modo: Run Once for All Items
return [{ json: { customer: $json.customer_name } }];
// B — Modo: Run Once for Each Item
return { json: $input.item };
// C — Modo: Run Once for Each Item
const fileName = $binary['orders-file'].fileName;
return { json: { ...$json, attachment: fileName } };
// D — Modo: Run Once for Each Item
return {
json: { ...$json, position: $itemIndex, batch_size: $input.all().length },
};
Ver solución
A — No funciona. $json en modo All Items no apunta a ningún item, porque en ese modo no hay item en curso. Síntoma: customer sale undefined, o el nodo da un error sobre leer una propiedad de algo indefinido, según la versión. Corrección: cambiar el modo a por item, o leer del arreglo: return $input.all().map((item) => ({ json: { customer: item.json.customer_name } }));
B — Funciona, pero produce una salida rota. $input.item es el sobre completo, así que la salida queda con un nivel de más: cada item tiene un campo json que contiene los datos. Síntoma: sin error, pero todos los nodos siguientes dejan de encontrar los campos y hay que llegar a ellos con $json.json.order_id. Corrección: return { json: $json }; o return { json: $input.item.json };
C — No funciona. $binary no está disponible en el nodo Code, según la tabla oficial. Síntoma: error de variable no definida. Corrección: el compartimento sí existe, solo falta el atajo: const fileName = $input.item.binary['orders-file'].fileName;. Y conviene una guarda, porque no todos los items tienen adjuntos.
D — Funciona a medias, y es el más interesante. $itemIndex está bien: en modo por item devuelve la posición del item en curso. El problema es $input.all().length: estás pidiendo el tamaño del lote desde un modo que, por diseño, no tiene acceso al conjunto. Síntoma: depende de la versión, y en el mejor de los casos devuelve un número que no significa lo que crees. Corrección: si necesitas el tamaño del lote, cambia a modo All Items —donde $input.all().length es exactamente lo que quieres— o calcula el tamaño en un nodo anterior y pásalo dentro de cada item.
Por qué funciona: de los cuatro, solo A y C fallan de forma visible. B corre en verde y entrega una estructura incorrecta, y D corre en verde y entrega un número dudoso. Es otra vez la misma asimetría del Módulo 1: el color verde no es evidencia de nada, y el reflejo de comparar la salida contra lo que esperabas sigue siendo el mejor control que tienes.
Ejercicio 3 — Comprueba el síntoma en tu instancia. Este ejercicio se resuelve con n8n abierto y su resultado es tuyo, no mío. Crea un nodo Code después de tu semilla de pedidos de Cumbre y ejecútalo cuatro veces con estas variantes, anotando exactamente qué ves en el panel de salida:
(a) Modo Run Once for All Items, con return [{ json: { test: $json } }];
(b) Modo Run Once for All Items, con return [{ json: { test: $itemIndex } }];
(c) Modo Run Once for Each Item, con return { json: { test: $json.order_id } };
(d) Modo Run Once for Each Item, con return { json: { test: $input.all().length } };
Anota las cuatro respuestas junto con tu versión de n8n.
Ver solución
No hay una respuesta única, y ese es el punto. Lo que sí puedo decirte es qué esperar y qué significa cada resultado.
(a) Es el caso que la documentación marca como no disponible. Lo más probable es que veas undefined, un objeto vacío o un error. Anota el texto exacto, porque ese es el síntoma que vas a reconocer el día que te toque diagnosticar un nodo ajeno.
(b) $itemIndex en modo All Items es el mismo tipo de pregunta sin respuesta: no hay un item en curso, así que no hay una posición que devolver. Lo que veas —un cero, un undefined, un error— es informativo, y en cualquier caso no es un dato sobre el que construir lógica.
(c) Este debería funcionar sin sorpresas: sale un item por cada pedido, con test conteniendo su order_id. Es el uso canónico de $json y el control de que tu instancia se comporta como dice la documentación.
(d) Este es el más revelador. La documentación no dice qué hace $input.all() en modo por item, así que el resultado te dice algo sobre tu versión. Puede devolver 1, puede devolver el tamaño real del lote, puede fallar. Sea cual sea, la recomendación no cambia: no construyas lógica de producción sobre un comportamiento que la documentación no promete.
Por qué funciona: los cuatro casos juntos te dan algo que ninguna tabla puede darte, que es memoria visual del síntoma. Cuando dentro de tres meses veas un undefined inexplicable en un nodo Code, la pregunta "¿en qué modo está?" te va a salir sola, porque ya viste esa pantalla. Y el hábito de anotar la versión al lado convierte tus notas en algo que sigue siendo útil cuando n8n cambie.
Resumen y siguiente paso
En esta lección desarmaste $json, la variable más usada de n8n. Su definición es literal y explica todo lo demás: $json es un atajo de $input.item.json, es decir, el contenido del item que se está procesando en este momento. Tres eslabones —la bandeja, el sobre en curso, la carta— comprimidos en tres letras.
De esa definición sale su única condición, que es la única salvedad de modo que hace la tabla oficial de n8n: $json es válido en modo Run Once for Each Item. En modo Run Once for All Items tu código corre una sola vez para el lote entero, no hay un item en curso, y preguntar por "el actual" no tiene respuesta útil. El síntoma no es un error claro sino undefined, y el diagnóstico equivocado que hace todo el mundo la primera vez es dudar del nombre del campo. La detección es una búsqueda de texto: si el script está en modo All Items y contiene $json, ese es el problema.
Viste también por qué el reflejo de las expresiones se traslada mal. En los campos de un nodo, {{ $json.campo }} siempre funciona porque los nodos de n8n procesan item por item automáticamente: el editor de expresiones vive siempre en algo equivalente al modo por item. El nodo Code es el único lugar de n8n donde puedes salir de ese contexto, y su valor por defecto es justamente el que deja a $json sin sentido.
Y cerraste el par de atajos con la nota que casi ningún material trae: $binary, el atajo simétrico para el compartimento de archivos, no está disponible en el nodo Code. El compartimento sigue existiendo en el item —se lee con $input.item.binary o con item.binary dentro del bucle— pero el atajo no. Que falte el atajo no significa que falte el dato, y esa distinción ahorra tardes enteras.
Antes de avanzar deberías poder: escribir de memoria la equivalencia exacta de $json; decir qué síntoma produce en modo All Items y cómo se detecta en cinco segundos; explicar por qué en las expresiones nunca falla; y nombrar la variable que sí da acceso al compartimento binario desde el nodo Code.
Con esto cierras la capa 1. Sabes leer todo lo que entra a tu nodo, de todas las formas posibles. La lección 5 sale por la puerta del costado: $('Nombre del nodo'), el teléfono interno del despachador. Vas a poder leer la salida de cualquier nodo del workflow, no solo del que está conectado a tu entrada, con la misma familia de métodos que ya conoces —.all(), .first(), .last()— más dos que solo tienen sentido cuando hablas de otro nodo: .item, que trae el item correspondiente siguiendo el hilo de pairedItem que aprendiste en la lección 2, e .itemMatching(), que es su versión explícita y la que la documentación recomienda dentro del nodo Code. Es la lección que más amplía lo que tu código puede hacer, y también la que tiene la trampa más tonta de todo el módulo: si alguien renombra un nodo, tus referencias dejan de funcionar.
Recursos
- Root-level variables — n8n Docs — la definición literal de
$jsoncomo atajo de$input.item.json, y la de$binary,$itemIndexy las demás variables de nivel raíz. - Reference previous nodes — n8n Docs — la tabla con la columna "Available in Code node?", donde
$jsonlleva la salvedad de modo y$binaryaparece marcado como no disponible. - Understand n8n's data structure — n8n Docs — por qué los nodos de n8n procesan cada item por separado, que es la razón de fondo por la que
$jsonnunca falla en las expresiones. - Using the Code node — n8n Docs — los dos modos con sus nombres exactos y el hecho de que
Run Once for All Itemses el valor por defecto. - Get the binary data buffer — n8n Docs — la ruta soportada para trabajar con el contenido de un archivo desde el nodo Code, ya que
$binaryno está disponible. - Expressions — n8n Docs — el otro entorno donde vive
$json, y el que el Módulo 4 compara de frente con el nodo Code.