Zie619/n8n-workflows · error · Error
Invalid row structure
Error message
Invalid row structure
What it means
The row-level guard in 'Parse - Get Page Engagement This Week': while mapping analyticsData.rows, a row is rejected when row.dimensionValues[0].value is falsy or row.metricValues is empty/missing. GA4 can return rows whose dimension value is the reserved string '(not set)' (truthy, passes) but also rows with undefined dimension values when a dimension is requested but unavailable.
Source
Thrown at workflows/Googleanalytics/0475_Googleanalytics_Code_Automate_Scheduled.json:527
"credentials": {
"googleAnalyticsOAuth2": {
"id": "8OdVzOGJqhJ3ti8k",
"name": "KBB Google Analytics account"
}
},
"typeVersion": 2,
"notes": "This googleAnalytics node performs automated tasks as part of the workflow."
},
{
"id": "15f3edcb-2e31-4faa-8db2-62da69bbfe8d",
"name": "Parse - Get Page Engagement This Week",
"type": "n8n-nodes-base.code",
"position": [
1040,
740
],
"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": "46cd21cd-c7f4-45cb-a724-db8a122f9de3",
"name": "Parse - Get Page Engagement Prior Week",
"type": "n8n-nodes-base.code",
"position": [
1440,
740
],
"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 // Filter out invalid rows and map each valid row to a simplified object\n const simplified = analyticsData.rows\n .filter(row => {\n // Check if row is valid and its properties exist\n const isValid = row \n && row.dimensionValues \n && row.dimensionValues[0] \n && row.dimensionValues[0].value \n && row.metricValues \n && row.metricValues.length > 0;\n \n if (!isValid) {\n console.log('Ignoring invalid or null row:', row);\n }\n return isValid;\n })\n .map(row => ({\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 // 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 } };\n"
},
"typeVersion": 2,
"notes": "This code node performs automated tasks as part of the workflow."
},View on GitHub (pinned to 94007c1445)
Solutions
- Check the logged 'Invalid row structure:' line to see the offending row shape.
- Filter before mapping (like the sibling prior-week node already does): rows.filter(r => r.dimensionValues?.[0]?.value && r.metricValues?.length).map(...).
- Default missing dimensions to '(not set)' if those rows must be kept.
- Use optional chaining on metricValues[1..3] (metricValues[1]?.value) so fewer-metric rows don't throw either.
Example fix
// before
const simplified = analyticsData.rows.map(row => {
if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {
throw new Error('Invalid row structure');
}
return { page: row.dimensionValues[0].value, /* ... */ };
});
// after (skip bad rows, as the prior-week node does)
const simplified = analyticsData.rows
.filter(row => row.dimensionValues?.[0]?.value && row.metricValues?.length)
.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 validRows = analyticsData.rows.filter(r => r?.dimensionValues?.[0]?.value && r?.metricValues?.length);
Type guard
function isValidGa4Row(row) {
return Boolean(row?.dimensionValues?.[0]?.value && Array.isArray(row?.metricValues) && row.metricValues.length > 0);
} Prevention
- Filter rows before mapping (as the prior-week node does) instead of throwing inside map().
- Use optional chaining on every metric index (metricValues[1]?.value).
- Map (not set)/(other) dimension values explicitly if they must be retained.
- Log skipped-row counts so silent data loss is visible.
When it happens
Trigger: A GA4 report row where the first dimension (page path) is missing/undefined — e.g. querying ga:pageTitle or a custom dimension that has no value for some rows — or metricValues omitted for a row. One bad row kills the whole map() and the run.
Common situations: Report includes (other)/(not set) groupings; querying a custom dimension not populated on all events; GA4 data-model change adding rows with sparse values.
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/4229882375a4acbe.
Report an issue: GitHub.