Zie619/n8n-workflows · error · Error
Invalid row structure
Error message
Invalid row structure
What it means
Literal Error thrown inside the row-mapper of "Parse data from Google Analytics" (1480_Googleanalytics_Code_Automation_Webhook.json:204). Unlike errors 64-66 (envelope checks), this fires per-row: a rows array exists but at least one row lacks dimensionValues[0].value or has an empty/absent metricValues array. The upstream GA4 node requests dimension unifiedScreenName and 4 metrics, so a well-formed row must carry exactly that; malformed rows appear when the report includes aggregated placeholder rows or when the response belongs to a different report configuration.
Source
Thrown at workflows/Googleanalytics/1480_Googleanalytics_Code_Automation_Webhook.json:204
"listName": "other"
}
]
},
"additionalFields": {}
},
"typeVersion": 2,
"notes": "This googleAnalytics node performs automated tasks as part of the workflow."
},
{
"id": "f8dac36b-9e8a-407f-b923-b4cea368f1bc",
"name": "Parse data from Google Analytics",
"type": "n8n-nodes-base.code",
"position": [
880,
460
],
"parameters": {
"jsCode": "function transformToUrlString(items) {\n // Debug logging\n console.log('Input items:', JSON.stringify(items, null, 2));\n \n // Check if items is an array and has content\n if (!Array.isArray(items) || items.length === 0) {\n console.log('Items is not an array or is empty');\n throw new Error('Invalid data structure');\n }\n\n // Check if first item exists and has json property\n if (!items[0] || !items[0].json) {\n console.log('First item is missing or has no json property');\n throw new Error('Invalid data structure');\n }\n\n // Get the analytics data\n const analyticsData = items[0].json;\n \n // Check if analyticsData has rows\n if (!analyticsData || !Array.isArray(analyticsData.rows)) {\n console.log('Analytics data is missing or has no rows array');\n throw new Error('Invalid data structure');\n }\n \n // Map each row to a simplified object\n const simplified = analyticsData.rows.map(row => {\n if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {\n console.log('Invalid row structure:', row);\n throw new Error('Invalid row structure');\n }\n \n return {\n page: row.dimensionValues[0].value,\n pageViews: parseInt(row.metricValues[0].value) || 0,\n activeUsers: parseInt(row.metricValues[1].value) || 0,\n viewsPerUser: parseFloat(row.metricValues[2].value) || 0,\n eventCount: parseInt(row.metricValues[3].value) || 0\n };\n });\n \n // Convert to JSON string and encode for URL\n return encodeURIComponent(JSON.stringify(simplified));\n}\n\n// Get input data and transform it\nconst urlString = transformToUrlString($input.all());\n\n// Return the result\nreturn { json: { urlString } };"
},
"typeVersion": 2,
"notes": "This code node performs automated tasks as part of the workflow."
},
{
"id": "ed880442-c92e-4347-b277-e8794aea6fbc",
"name": "Parse GA data",
"type": "n8n-nodes-base.code",
"position": [
1240,
460
],
"parameters": {
"jsCode": "function transformToUrlString(items) {\n // Debug logging\n console.log('Input items:', JSON.stringify(items, null, 2));\n \n // Check if items is an array and has content\n if (!Array.isArray(items) || items.length === 0) {\n console.log('Items is not an array or is empty');\n throw new Error('Invalid data structure');\n }\n\n // Check if first item exists and has json property\n if (!items[0] || !items[0].json) {\n console.log('First item is missing or has no json property');\n throw new Error('Invalid data structure');\n }\n\n // Get the analytics data\n const analyticsData = items[0].json;\n \n // Check if analyticsData has rows\n if (!analyticsData || !Array.isArray(analyticsData.rows)) {\n console.log('Analytics data is missing or has no rows array');\n throw new Error('Invalid data structure');\n }\n \n // Map each row to a simplified object\n const simplified = analyticsData.rows.map(row => {\n if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {\n console.log('Invalid row structure:', row);\n throw new Error('Invalid row structure');\n }\n \n return {\n page: row.dimensionValues[0].value,\n pageViews: parseInt(row.metricValues[0].value) || 0,\n activeUsers: parseInt(row.metricValues[1].value) || 0,\n viewsPerUser: parseFloat(row.metricValues[2].value) || 0,\n eventCount: parseInt(row.metricValues[3].value) || 0\n };\n });\n \n // Convert to JSON string and encode for URL\n return encodeURIComponent(JSON.stringify(simplified));\n}\n\n// Get input data and transform it\nconst urlString = transformToUrlString($input.all());\n\n// Return the result\nreturn { json: { urlString } };"
},
"typeVersion": 2,
"notes": "This code node performs automated tasks as part of the workflow."
},View on GitHub (pinned to 94007c1445)
Solutions
- Read the console output — 'Invalid row structure:' prints the offending row verbatim.
- Re-open the upstream googleAnalytics node and confirm dimensionsGA4 = [unifiedScreenName] and metricsGA4 still lists the 4 metrics in the original order.
- Skip bad rows instead of throwing: change the map to a filter+map so structurally invalid rows are logged and dropped.
- If every row is invalid, the upstream report config drifted — fix the GA node, not the parser.
- Add an execution-data check: if >50% of rows are skipped, fail loudly with a descriptive Error('GA report shape drifted') instead of the generic message.
Example fix
// before
const simplified = analyticsData.rows.map(row => {
if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {
throw new Error('Invalid row structure');
}
...
});
// after
const simplified = analyticsData.rows
.filter(row => {
const ok = !!row?.dimensionValues?.[0]?.value && row.metricValues?.length >= 4;
if (!ok) console.log('Skipping malformed row:', JSON.stringify(row));
return ok;
})
.map(row => ({
page: row.dimensionValues[0].value,
pageViews: parseInt(row.metricValues[0].value) || 0,
activeUsers: parseInt(row.metricValues[1].value) || 0,
viewsPerUser: parseFloat(row.metricValues[2].value) || 0,
eventCount: parseInt(row.metricValues[3].value) || 0
})); Defensive patterns
Strategy: type-guard
Validate before calling
const rows = ($input.all()[0]?.json?.rows ?? []);
const badRows = rows.filter(r => !isGaRow(r));
if (badRows.length) console.log('Malformed rows:', JSON.stringify(badRows.slice(0, 3))); Type guard
const isGaRow = (r) => !!r?.dimensionValues?.[0]?.value && Array.isArray(r.metricValues) && r.metricValues.length >= 4; // 4 metrics configured upstream
Try / catch
const simplified = rows
.filter(isGaRow)
.map(r => ({
page: r.dimensionValues[0].value,
pageViews: parseInt(r.metricValues[0].value) || 0,
activeUsers: parseInt(r.metricValues[1].value) || 0,
viewsPerUser: parseFloat(r.metricValues[2].value) || 0,
eventCount: parseInt(r.metricValues[3].value) || 0
})); Prevention
- Filter-then-map: one bad row should never abort a whole report.
- Keep the parser's expected metric count derived from the GA node config, not a magic number in two places.
- Count and log skipped rows to detect report-shape drift early.
When it happens
Trigger: A row where dimensionValues is empty or its first entry has no value (GA4 can return rows for '(not set)' dimensions with unusual shapes), a row with metricValues missing or length 0, or the upstream node's dimension/metric list changed so rows no longer contain unifiedScreenName + 4 metrics.
Common situations: Upstream GA4 node edited (dimension renamed, metrics reduced) without updating the parser; response contains an '(other)'/summary row; mixed report types flowing into the parser after a workflow merge.
Related errors
- Invalid row structure
- Invalid data structure
- Invalid data structure
- Invalid data structure
- Invalid data structure
AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15).
Data as JSON: /api/errors/b0630e947ae3e4d7.
Report an issue: GitHub.