{"record":{"id":"955d592b2f73f866","repo":"Zie619/n8n-workflows","slug":"monthly-searches-data-is-missing-or-not-an-array-f","errorCode":null,"errorMessage":"monthly_searches data is missing or not an array from Loop Over Google Keywords node.","messagePattern":"monthly_searches data is missing or not an array from Loop Over Google Keywords node\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workflows/Splitout/1642_Splitout_Code_Automation_Webhook.json","lineNumber":623,"sourceCode":"          \"id\": \"LAbGsn1RMARiq5Gy\",\n          \"name\": \"NocoDB Token account\"\n        }\n      },\n      \"executeOnce\": false,\n      \"retryOnFail\": true,\n      \"typeVersion\": 3,\n      \"notes\": \"This nocoDb node performs automated tasks as part of the workflow.\"\n    },\n    {\n      \"id\": \"1fdaf0fc-5c11-406f-93fb-b4a7fd3b6eed\",\n      \"name\": \"Format G Data\",\n      \"type\": \"n8n-nodes-base.code\",\n      \"position\": [\n        -240,\n        400\n      ],\n      \"parameters\": {\n        \"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\"\n      },\n      \"typeVersion\": 2,\n      \"alwaysOutputData\": false,\n      \"notes\": \"This code node performs automated tasks as part of the workflow.\"\n    },\n    {\n      \"id\": \"7d654cf7-1223-4f10-8026-997f5418402e\",\n      \"name\": \"Format YT Data\",\n      \"type\": \"n8n-nodes-base.code\",\n      \"position\": [\n        -220,\n        980\n      ],\n      \"parameters\": {\n        \"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\"\n      },\n      \"typeVersion\": 2,\n      \"notes\": \"This code node performs automated tasks as part of the workflow.\"","sourceCodeStart":605,"sourceCodeEnd":641,"githubUrl":"https://github.com/Zie619/n8n-workflows/blob/94007c1445d9258a7da116646b79473e7c7c3282/workflows/Splitout/1642_Splitout_Code_Automation_Webhook.json#L605-L641","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\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}\n\n// after - tolerate zero-volume keywords, fail only on structural breakage\nconst loopData = $('Loop Over Google Keywords').first().json ?? {};\nconst monthlySearches = Array.isArray(loopData.monthly_searches) ? loopData.monthly_searches : [];\nif (monthlySearches.length === 0 && Object.keys(loopData).length > 0) {\n  // keyword exists but has no volume data: nothing to import for it\n  return [];\n}\nif (Object.keys(loopData).length === 0) {\n  throw new Error('Loop Over Google Keywords produced no item at all.');\n}","handlingStrategy":"validation","validationCode":"const loopData = $('Loop Over Google Keywords').first().json ?? {};\nconst monthlySearches = Array.isArray(loopData.monthly_searches) ? loopData.monthly_searches : [];\nif (monthlySearches.length === 0) {\n  // zero-volume keyword or empty loop output: nothing to import, don't fail the batch\n  return [];\n}","typeGuard":"const hasMonthlySearches = (d) =>\n  d != null && Array.isArray(d.monthly_searches) &&\n  d.monthly_searches.every(r => 'year' in r && 'month' in r && 'search_volume' in r);","tryCatchPattern":"try {\n  return buildBatches(monthlySearches, secondTierItems);\n} catch (e) {\n  throw new Error(`Format G Data failed: ${e.message}; loopData keys: ${Object.keys(loopData).join(',')}`);\n}","preventionTips":["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."],"tags":["n8n","code-node","validation","keyword-research","loop","data-shape"],"backgroundTag":null,"analyzedSha":"94007c1445d9258a7da116646b79473e7c7c3282","analyzedAt":"2026-08-15T04:10:37.591Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}