n8n logon8n Automation Hub
Debugging notes

My n8n Code Node Kept Saying "A json Property Isn't an Object"

Debugging notes · n8n workflow · September 2, 2026

The error pointed at one node, on one item, with a message that sounded like a typo in the code. The code had no typo. The node just wasn't running in the mode the code was written for.

Workflow at a glance
  1. A paper-trading bot workflow runs a shell script, parses its output, translates it via Groq, then posts it to Telegram
  2. The translation step's Code node started failing with A 'json' property isn't an object [item 0]
  3. The node's mode was set to "Run Once for Each Item," but its code ended with return [{ json: { text } }]; — an array
  4. In per-item mode, n8n treats whatever the code returns as the item itself, then looks for a .json property on it — an array has no such property, hence "isn't an object"
  5. Fix: rewrote the node to run once for all items, looping over $input.all() and returning one array of items — matching the return shape the code already used
  6. Confirmed fixed: the node has run clean on every execution since, twenty-minute schedule, no reruns needed
Diagram of the Polymarket bot workflow with the Code node that threw the json-property error highlighted

Node diagram reconstructed from the live workflow via the n8n API.

A translation step that only sometimes existed

The workflow behind this bug runs every 20 minutes: it SSHes into a script that produces a trading-bot status line, parses the result, sends it through Groq for a Ukrainian translation, and posts whichever text comes out to Telegram. The translation step is meant to degrade gracefully — if the Groq call fails or comes back empty, the node should fall back to the original untranslated text rather than break the whole run.

The Code node responsible for that fallback logic was small:

let text;
try {
  text = $json.choices[0].message.content.trim();
  if (!text) throw new Error('empty');
} catch (e) {
  text = $('Parse Result').item.json.text;
}
return [{ json: { text } }];

Every path through that code ends in the same place: a text string, wrapped in an object, wrapped in an array. On the surface, that looks like exactly the shape n8n Code nodes are supposed to return.

The error that didn't match the code

Executions started failing at this node with: A 'json' property isn't an object [item 0]. Reading the code line by line, there was no obvious way for text to end up as anything other than a string, and { json: { text } } is unambiguously an object with a json key pointing at another object. Nothing in the visible logic explained the message.

The missing piece wasn't in the code — it was in the node's configuration, one field over from the code editor: the node's execution mode was set to "Run Once for Each Item," not the default "Run Once for All Items." The two modes expect the code to return completely different shapes.

Why the return shape mattered more than the return value

In "Run Once for All Items" mode, the code runs a single time with access to every input item, and is expected to return an array of items — exactly the [{ json: {...} }] shape this code produced. In "Run Once for Each Item" mode, n8n instead calls the code once per item and expects each call to return a single item object directly — { json: {...} }, with no surrounding array.

With the mode set to per-item but the code still returning an array, n8n took the returned array as if it were the item itself, then looked for a .json property on it. Arrays don't have a .json property — [{ json: { text } }].json is undefined, not an object — which is exactly the validation failure the error message describes. The object was there; it was just nested one level deeper than the mode n8n was running in expected to find it.

The fix: match the mode to the return shape

Rather than reshape the per-item code to drop its array wrapper, the node was switched to "Run Once for All Items" and rewritten to loop explicitly:

const groqResults = $input.all();
const originals = $('Parse Result').all();
const out = [];
for (let i = 0; i < groqResults.length; i++) {
  let text = (originals[i] && originals[i].json && originals[i].json.text) || '';
  try {
    const content = groqResults[i].json && groqResults[i].json.choices && groqResults[i].json.choices[0] && groqResults[i].json.choices[0].message && groqResults[i].json.choices[0].message.content;
    if (content && content.trim()) text = content.trim();
  } catch (e) {}
  out.push({ json: { text } });
}
return out;

The fallback logic is identical — try the Groq translation, fall back to the original text on any failure — but now every input item is looked up explicitly by index instead of relying on the implicit single-item $json the per-item mode had been providing, and the final return out matches the all-items mode the node now actually runs in.

What confirmed it was actually fixed

The workflow runs on a 20-minute schedule with no manual triggering involved, so the fix was left to prove itself against real traffic rather than a one-off test. Every scheduled run since has completed with a clean Telegram message and no validation error at this node — the kind of bug that, once the mode/shape mismatch is understood, doesn't come back, because there's no remaining code path where the two can drift apart again.

The broader lesson generalizes past this one workflow: a Code node's two execution modes are not interchangeable defaults with the same contract. If a node is failing with a validation error about the shape of what it returned, checking the mode dropdown is often faster than re-reading code that already looks correct.

A Code node throwing an error about a "json" property that looks perfectly fine in the editor?

I debug n8n Code node execution-mode mismatches and data-shape errors that don't show up by re-reading the code alone.