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

  1. Check the logged 'Invalid row structure:' line to see the offending row shape.
  2. Filter before mapping (like the sibling prior-week node already does): rows.filter(r => r.dimensionValues?.[0]?.value && r.metricValues?.length).map(...).
  3. Default missing dimensions to '(not set)' if those rows must be kept.
  4. 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

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


AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15). Data as JSON: /api/errors/4229882375a4acbe. Report an issue: GitHub.