Zie619/n8n-workflows · error · Error
monthly_searches data is missing or not an array from Loop O
Error message
monthly_searches data is missing or not an array from Loop Over Google Keywords node.
What it means
Thrown by the 'Format G Data' Code node when $('Loop Over Google Keywords').json (i.e. $node["Loop Over Google Keywords"].json) is absent, or its monthly_searches property is missing or not an array. The node cross-joins the loop node's monthly_searches records with rows from 'Add Second Tier G Keyword Data' and batches the result for a bulk NocoDB import, so it hard-requires an array of {year, month, search_volume} records.
Source
Thrown at workflows/Splitout/1642_Splitout_Code_Automation_Webhook.json:623
"id": "LAbGsn1RMARiq5Gy",
"name": "NocoDB Token account"
}
},
"executeOnce": false,
"retryOnFail": true,
"typeVersion": 3,
"notes": "This nocoDb node performs automated tasks as part of the workflow."
},
{
"id": "1fdaf0fc-5c11-406f-93fb-b4a7fd3b6eed",
"name": "Format G Data",
"type": "n8n-nodes-base.code",
"position": [
-240,
400
],
"parameters": {
"jsCode": "// Get the monthly search data from the \"Loop Over Google Keywords\" node\nconst loopData = $node[\"Loop Over Google Keywords\"].json;\nif (!loopData || !loopData.monthly_searches || !Array.isArray(loopData.monthly_searches)) {\n throw new Error(\"monthly_searches data is missing or not an array from Loop Over Google Keywords node.\");\n}\nconst monthlySearches = loopData.monthly_searches;\n\n// Get all items from the \"Add Second Tier G Keyword Data\" node\nconst secondTierItems = $items(\"Add Second Tier G Keyword Data\");\n\nif (!secondTierItems || secondTierItems.length === 0) {\n throw new Error(\"No data found in Add Second Tier G Keyword Data node.\");\n}\n\nconst results = [];\n\n// Loop through each second-tier item\nsecondTierItems.forEach(itemWrapper => {\n const item = itemWrapper.json;\n // Validate that the required properties exist on the second-tier item.\n if (!item.keyword || item.Id === undefined) {\n throw new Error(\"A second tier item is missing 'keyword' or 'Id'.\");\n }\n \n // For each monthly search record, combine with the second-tier data\n monthlySearches.forEach(record => {\n // Validate that each monthly record has the required properties.\n if (record.year === undefined || record.month === undefined || record.search_volume === undefined) {\n throw new Error(\"A monthly search record is missing 'year', 'month', or 'search_volume'.\");\n }\n \n results.push({\n json: {\n keyword: item.keyword,\n google_keyword_id: item.Id,\n year: record.year,\n month: record.month,\n search_volume: record.search_volume,\n unique_id: `${record.year}-${record.month}-${item.keyword}`\n }\n });\n });\n});\n\n// Chunk the results into batches of 1000 items each\nconst batchSize = 1000;\nconst batchedResults = [];\n\nfor (let i = 0; i < results.length; i += batchSize) {\n // Create a batch containing up to batchSize items\n const batchItems = results.slice(i, i + batchSize).map(item => item.json);\n batchedResults.push({\n json: {\n batch: batchItems\n }\n });\n}\n\nreturn batchedResults;\n"
},
"typeVersion": 2,
"alwaysOutputData": false,
"notes": "This code node performs automated tasks as part of the workflow."
},
{
"id": "7d654cf7-1223-4f10-8026-997f5418402e",
"name": "Format YT Data",
"type": "n8n-nodes-base.code",
"position": [
-220,
980
],
"parameters": {
"jsCode": "// Get the monthly search data from the \"Loop Over Google Keywords\" node\nconst loopData = $node[\"Loop Over YT Keywords\"].json;\nif (!loopData || !loopData.monthly_searches || !Array.isArray(loopData.monthly_searches)) {\n throw new Error(\"monthly_searches data is missing or not an array from Loop Over YT Keywords node.\");\n}\nconst monthlySearches = loopData.monthly_searches;\n\n// Get all items from the \"Add Second Tier G Keyword Data\" node\nconst secondTierItems = $items(\"Add Second Tier YT Keyword Data\");\n\nif (!secondTierItems || secondTierItems.length === 0) {\n throw new Error(\"No data found in Add Second Tier YT Keyword Data node.\");\n}\n\nconst results = [];\n\n// Loop through each second-tier item\nsecondTierItems.forEach(itemWrapper => {\n const item = itemWrapper.json;\n // Validate that the required properties exist on the second-tier item.\n if (!item.keyword || item.Id === undefined) {\n throw new Error(\"A second tier item is missing 'keyword' or 'Id'.\");\n }\n \n // For each monthly search record, combine with the second-tier data\n monthlySearches.forEach(record => {\n // Validate that each monthly record has the required properties.\n if (record.year === undefined || record.month === undefined || record.search_volume === undefined) {\n throw new Error(\"A monthly search record is missing 'year', 'month', or 'search_volume'.\");\n }\n \n results.push({\n json: {\n keyword: item.keyword,\n google_keyword_id: item.Id,\n year: record.year,\n month: record.month,\n search_volume: record.search_volume,\n unique_id: `${record.year}-${record.month}-${item.keyword}`\n }\n });\n });\n});\n\n// Chunk the results into batches of 1000 items each\nconst batchSize = 1000;\nconst batchedResults = [];\n\nfor (let i = 0; i < results.length; i += batchSize) {\n // Create a batch containing up to batchSize items\n const batchItems = results.slice(i, i + batchSize).map(item => item.json);\n batchedResults.push({\n json: {\n batch: batchItems\n }\n });\n}\n\nreturn batchedResults;\n"
},
"typeVersion": 2,
"notes": "This code node performs automated tasks as part of the workflow."View on GitHub (pinned to 94007c1445)
Solutions
- Pin the output of 'Loop Over Google Keywords' and confirm the active item actually has monthly_searches as an array; log Object.keys(loopData || {}).
- If zero-volume keywords legitimately lack the field, default it instead of throwing: const monthlySearches = Array.isArray(loopData?.monthly_searches) ? loopData.monthly_searches : []; and skip empty items.
- If the loop can emit nothing, set alwaysOutputData: true on it or gate this node behind an IF that checks the loop produced data.
- Align the field name with the live API response (rename in the loop node's Set step if the API changed).
Example fix
// before
const loopData = $node["Loop Over Google Keywords"].json;
if (!loopData || !loopData.monthly_searches || !Array.isArray(loopData.monthly_searches)) {
throw new Error("monthly_searches data is missing or not an array from Loop Over Google Keywords node.");
}
// after - tolerate zero-volume keywords, fail only on structural breakage
const loopData = $('Loop Over Google Keywords').first().json ?? {};
const monthlySearches = Array.isArray(loopData.monthly_searches) ? loopData.monthly_searches : [];
if (monthlySearches.length === 0 && Object.keys(loopData).length > 0) {
// keyword exists but has no volume data: nothing to import for it
return [];
}
if (Object.keys(loopData).length === 0) {
throw new Error('Loop Over Google Keywords produced no item at all.');
} Defensive patterns
Strategy: validation
Validate before calling
const loopData = $('Loop Over Google Keywords').first().json ?? {};
const monthlySearches = Array.isArray(loopData.monthly_searches) ? loopData.monthly_searches : [];
if (monthlySearches.length === 0) {
// zero-volume keyword or empty loop output: nothing to import, don't fail the batch
return [];
} Type guard
const hasMonthlySearches = (d) => d != null && Array.isArray(d.monthly_searches) && d.monthly_searches.every(r => 'year' in r && 'month' in r && 'search_volume' in r);
Try / catch
try {
return buildBatches(monthlySearches, secondTierItems);
} catch (e) {
throw new Error(`Format G Data failed: ${e.message}; loopData keys: ${Object.keys(loopData).join(',')}`);
} Prevention
- Set alwaysOutputData on loop nodes feeding $node lookups, or gate this node behind an IF on the loop's item count.
- Prefer $('Node Name').first() over $node['Node Name'].json — it fails with a clearer error when the node produced nothing.
- Treat missing monthly_searches as 'no data' (skip) rather than structural failure; only throw on truly empty upstream.
When it happens
Trigger: The keyword API returned a keyword without monthly_searches (no volume data), the loop node emitted zero items (alwaysOutputData: false means empty output yields nothing, so $node[...] is undefined), the field is named differently (monthly_search_volume, searches), or monthly_searches came back as an object keyed by month instead of an array.
Common situations: DataforSEO/SEMrush-style keyword endpoints that omit monthly_searches for zero-volume keywords; loop-over-items emitting no item on empty batches; API schema version bump renaming fields; test runs where the loop node was skipped.
Related errors
- monthly_searches data is missing or not an array from Loop O
- PII column names are missing in the input data.
- Approved quantity must be greater than 0
- The video ID parameter is empty.
- One or more input arrays are empty. Check your previous node
AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15).
Data as JSON: /api/errors/955d592b2f73f866.
Report an issue: GitHub.