{"record":{"id":"80206d84d54a73c4","repo":"Zie619/n8n-workflows","slug":"monthly-searches-data-is-missing-or-not-an-array-f-80206d","errorCode":null,"errorMessage":"monthly_searches data is missing or not an array from Loop Over YT Keywords node.","messagePattern":"monthly_searches data is missing or not an array from Loop Over YT Keywords node\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workflows/Splitout/1642_Splitout_Code_Automation_Webhook.json","lineNumber":638,"sourceCode":"        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.\"\n    },\n    {\n      \"id\": \"67848762-a140-4c63-b8ca-e20331135741\",\n      \"name\": \"Bulk Import G Monthly Search Volume\",\n      \"type\": \"n8n-nodes-base.httpRequest\",\n      \"position\": [\n        0,\n        400\n      ],\n      \"parameters\": {\n        \"url\": \"{{ $env.API_BASE_URL }}\",\n        \"method\": \"POST\",\n        \"options\": {\n          \"batching\": {\n            \"batch\": {","sourceCodeStart":620,"sourceCodeEnd":656,"githubUrl":"https://github.com/Zie619/n8n-workflows/blob/94007c1445d9258a7da116646b79473e7c7c3282/workflows/Splitout/1642_Splitout_Code_Automation_Webhook.json#L620-L656","documentation":"Same guard as the Google variant, but in the 'Format YT Data' Code node: it reads $node[\"Loop Over YT Keywords\"].json and requires monthly_searches to be a non-empty-capable array before cross-joining with 'Add Second Tier YT Keyword Data'. Thrown when that loop node's current item lacks a valid monthly_searches array. Note the code's comments still say 'Google' — it is a copy of the G node with names swapped.","triggerScenarios":"YouTube-keyword rows returned by the data provider without monthly_searches, the 'Loop Over YT Keywords' node emitting no item (its output empty), or the field arriving as an object/string. Also triggered if the node name in the workflow was renamed so $node[\"Loop Over YT Keywords\"] resolves to stale/undefined data.","commonSituations":"Copy-pasted node where only some names were updated (comments still reference Google), zero-volume YouTube keywords, renamed loop nodes breaking $node references, or API schema drift on the search-volume endpoint.","solutions":["Verify the 'Loop Over YT Keywords' node name matches the $node reference exactly and that its pinned output has monthly_searches as an array.","Default missing monthly_searches to [] and return [] for zero-volume keywords instead of failing the whole run.","Set alwaysOutputData: true on the loop node or add an IF guard so Format YT Data only runs when the loop emitted data.","Clean up the stale Google comments/naming (google_keyword_id is written for YT rows too) to prevent future mix-ups."],"exampleFix":"// before\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}\n\n// after\nconst loopData = $('Loop Over YT Keywords').first().json ?? {};\nconst monthlySearches = Array.isArray(loopData.monthly_searches) ? loopData.monthly_searches : [];\nif (monthlySearches.length === 0) {\n  return []; // no volume data for this keyword: nothing to bulk import\n}","handlingStrategy":"validation","validationCode":"const loopData = $('Loop Over YT Keywords').first().json ?? {};\nif (!Array.isArray(loopData.monthly_searches)) {\n  console.warn(`monthly_searches missing/not array. Keys: ${Object.keys(loopData).join(', ')}`);\n  return [];\n}","typeGuard":"const isMonthlyArray = (d) => Array.isArray(d?.monthly_searches);","tryCatchPattern":null,"preventionTips":["After copying a node, update every $node/$() reference AND the comments — stale 'Google' references in the YT node invite wrong-node lookups.","Lock the expected response schema of the keyword API with a pinned example and re-verify after provider version bumps.","Return [] for zero-volume keywords instead of throwing so one bad keyword cannot kill the bulk import."],"tags":["n8n","code-node","validation","keyword-research","youtube","loop"],"backgroundTag":null,"analyzedSha":"94007c1445d9258a7da116646b79473e7c7c3282","analyzedAt":"2026-08-15T04:10:37.591Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}