{
  "id": "TEMPLATE_WORKFLOW_ID",
  "name": "SEO Report Template",
  "tags": [],
  "nodes": [
    {
      "id": "f64b105d-7cd7-4dca-b4c5-231214af3b25",
      "name": "When clicking 'Test workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -5744,
        -2080
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5ebfa39d-b8c5-4229-94c5-867f2f1ed551",
      "name": "Sticky Note - External Articles",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7472,
        -4768
      ],
      "parameters": {
        "color": 5,
        "width": 1420,
        "height": 364,
        "content": "## External Articles Reporting\n**Setup Instructions:**\n1. Add article URLs in the Data Sheet;\n2. Update the report document link in node \"Import Data to Report 3\";\n3. Verify the accuracy of submitted data."
      },
      "typeVersion": 1
    },
    {
      "id": "2a128afc-8cef-4b0e-a530-ca72a2b67f5c",
      "name": "Sticky Note - Internal Articles",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7008,
        -4288
      ],
      "parameters": {
        "color": 5,
        "width": 1920,
        "height": 400,
        "content": "## Internal Articles Reporting\n**Setup Instructions:**\n1. Add article links in the Data Sheet;\n2. Update the report document link in node \"Import Data to Report 2\";\n3. Verify the accuracy of submitted data;\n4. Manually add monthly search volume in the report column."
      },
      "typeVersion": 1
    },
    {
      "id": "0d7b4aeb-571d-43da-a508-f8dfce67f592",
      "name": "Sticky Note - Organic Reach",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6640,
        -3792
      ],
      "parameters": {
        "color": 5,
        "width": 720,
        "height": 364,
        "content": "## Organic Reach Reporting\n**Setup Instructions:**\n1. Set date range in node \"Import from Google Analytics 1\";\n2. Update report document link in node \"Import to Report 1\"."
      },
      "typeVersion": 1
    },
    {
      "id": "f2b230a9-bfc3-45f5-bf9b-d8d8741f662c",
      "name": "Convert Dots to Commas 2",
      "type": "n8n-nodes-base.code",
      "position": [
        -5664,
        -3200
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n    return {\n        json: {\n            ...item.json,\n            CTR: item.json.CTR.toString().replace('.', ','),\n            \"Average Position\": item.json[\"Average Position\"].toString().replace('.', ',')\n        }\n    };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "7eeb7754-36ce-41eb-8b38-ede9c2eeb5a1",
      "name": "Export Keywords from GSC 2",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -6128,
        -3200
      ],
      "parameters": {
        "url": "https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fexample.com/searchAnalytics/query",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n  \"startDate\": \"2025-11-01\",\n  \"endDate\": \"2025-11-25\",\n  \"dimensions\": [\"query\"],\n  \"rowLimit\": 1000,\n  \"startRow\": 0,\n  \"aggregationType\": \"byProperty\",\n  \"dimensionFilterGroups\": []\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d8e71fbf-15f9-4902-8611-d4aba111924d",
      "name": "Create Table 2",
      "type": "n8n-nodes-base.code",
      "position": [
        -5888,
        -3200
      ],
      "parameters": {
        "jsCode": "return $json.rows.map(row => ({\n    \"Keyword\": row.keys[0],\n    \"Clicks\": row.clicks,\n    \"Impressions\": row.impressions,\n    \"CTR\": row.ctr,\n    \"Average Position\": row.position\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "8d4aeef9-1a87-461c-bc41-355dd97b14a4",
      "name": "Organize Table 2",
      "type": "n8n-nodes-base.set",
      "position": [
        -5456,
        -3200
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5919caa6-e750-43fc-893b-64c923090f7a",
              "name": "Keyword",
              "type": "string",
              "value": "={{$json[\"Keyword\"]}}"
            },
            {
              "id": "30fb6df1-ff2d-4911-97cd-92c36558baa8",
              "name": "Clicks",
              "type": "string",
              "value": "={{$json[\"Clicks\"]}}"
            },
            {
              "id": "cb1a7ed9-033a-4a3b-9006-9da3e74270b8",
              "name": "Impressions",
              "type": "string",
              "value": "={{$json[\"Impressions\"]}}"
            },
            {
              "id": "d8689677-7b0c-4aea-a664-217b7ca6031f",
              "name": "CTR",
              "type": "string",
              "value": "={{$json[\"CTR\"]}}"
            },
            {
              "id": "73195bc2-e3b7-4f1e-8747-4edcfe4cc324",
              "name": "Average Position",
              "type": "string",
              "value": "={{$json[\"Average Position\"]}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f68b89f1-f8b5-4dd9-a355-0942e06ece59",
      "name": "Import to Report 2",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -5216,
        -3200
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Clicks",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Clicks",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Impressions",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Impressions",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CTR",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "CTR",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Average Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Average Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "54795d4f-2b28-4a80-ac26-a9cadecd2c40",
      "name": "Sticky Note - Keyword Ranking",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6224,
        -3360
      ],
      "parameters": {
        "color": 5,
        "width": 1300,
        "height": 364,
        "content": "## Keyword Ranking\n**Setup Instructions:**\n1. Set date range for report in node \"Export Keywords from GSC 2\";\n2. Reload authorization in node \"Export Keywords from GSC 2\";\n3. Update report document link in node \"Import to Report 2\"."
      },
      "typeVersion": 1
    },
    {
      "id": "3b6f8367-5fe6-4dcc-ae26-00b2d008f9ad",
      "name": "Convert Dots to Commas 3",
      "type": "n8n-nodes-base.code",
      "position": [
        -5200,
        -2768
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n    return {\n        json: {\n            ...item.json,\n            CTR: item.json.CTR.toString().replace('.', ','),\n            \"Average Position\": item.json[\"Average Position\"].toString().replace('.', ',')\n        }\n    };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "3b2bb1e7-26a8-496f-97a2-13fa6c914038",
      "name": "Export Keywords from GSC 3",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -5664,
        -2768
      ],
      "parameters": {
        "url": "https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fexample.com/searchAnalytics/query",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n  \"startDate\": \"2025-09-01\",\n  \"endDate\": \"2025-09-30\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 1000,\n  \"startRow\": 0,\n  \"dimensionFilterGroups\": []\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0af80ad4-7657-4416-8344-90109fae1e6f",
      "name": "Create Table 3",
      "type": "n8n-nodes-base.code",
      "position": [
        -5424,
        -2768
      ],
      "parameters": {
        "jsCode": "return $json.rows.map(row => ({\n    \"Page\": row.keys[0],\n    \"Clicks\": row.clicks,\n    \"Impressions\": row.impressions,\n    \"CTR\": row.ctr,\n    \"Average Position\": row.position\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "89698e0d-6d7c-4d81-b9a0-a4c9e5773143",
      "name": "Organize Table 3",
      "type": "n8n-nodes-base.set",
      "position": [
        -4992,
        -2768
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5919caa6-e750-43fc-893b-64c923090f7a",
              "name": "Keyword",
              "type": "string",
              "value": "={{$json[\"Page\"]}}"
            },
            {
              "id": "30fb6df1-ff2d-4911-97cd-92c36558baa8",
              "name": "Clicks",
              "type": "string",
              "value": "={{$json[\"Clicks\"]}}"
            },
            {
              "id": "cb1a7ed9-033a-4a3b-9006-9da3e74270b8",
              "name": "Impressions",
              "type": "string",
              "value": "={{$json[\"Impressions\"]}}"
            },
            {
              "id": "d8689677-7b0c-4aea-a664-217b7ca6031f",
              "name": "CTR",
              "type": "string",
              "value": "={{$json[\"CTR\"]}}"
            },
            {
              "id": "73195bc2-e3b7-4f1e-8747-4edcfe4cc324",
              "name": "Average Position",
              "type": "string",
              "value": "={{$json[\"Average Position\"]}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "182f6345-97e7-48ab-933b-8af692ffa762",
      "name": "Sticky Note - Page Ranking",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5760,
        -2944
      ],
      "parameters": {
        "color": 5,
        "width": 1300,
        "height": 384,
        "content": "## Page Ranking\n**Setup Instructions:**\n1. Set date range for report in node \"Export Keywords from GSC 3\";\n2. Reload authorization in node \"Export Keywords from GSC 3\";\n3. Update report document link in node \"Import to Report 3\"."
      },
      "typeVersion": 1
    },
    {
      "id": "bef4bd4e-74bf-49ba-b865-53d64a55528c",
      "name": "Import from Google Analytics 1",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -6560,
        -3616
      ],
      "parameters": {
        "endDate": "2025-11-25T00:00:00",
        "dateRange": "custom",
        "returnAll": true,
        "startDate": "2025-11-01T00:00:00",
        "metricsGA4": {
          "metricValues": [
            {
              "name": "organicGoogleSearchClicks",
              "listName": "other"
            },
            {
              "name": "organicGoogleSearchImpressions",
              "listName": "other"
            },
            {
              "name": "organicGoogleSearchClickThroughRate",
              "listName": "other"
            },
            {
              "name": "organicGoogleSearchAveragePosition",
              "listName": "other"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GA4_PROPERTY_ID"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {}
          ]
        },
        "additionalFields": {}
      },
      "typeVersion": 2
    },
    {
      "id": "ff6161f0-109a-44bd-99e1-59d65b0f0190",
      "name": "Format Data 7",
      "type": "n8n-nodes-base.code",
      "position": [
        -6368,
        -3616
      ],
      "parameters": {
        "jsCode": "// Sort dates from oldest to newest\nconst sortedItems = items.sort((a, b) => parseInt(a.json.date) - parseInt(b.json.date));\n\n// Initialize variables for summing\nlet totalClicks = 0;\nlet totalImpressions = 0;\nlet totalCtrSum = 0;\nlet totalPositionSum = 0;\nlet count = sortedItems.length;\n\n// Map to transform data and sum values\nconst formattedData = sortedItems.map(item => {\n    // Convert date from YYYYMMDD to DD-MM-YYYY\n    const rawDate = item.json.date;\n    const formattedDate = `${rawDate.slice(6, 8)}-${rawDate.slice(4, 6)}-${rawDate.slice(0, 4)}`;\n\n    // Get values and convert to numbers\n    const clicks = parseInt(item.json.organicGoogleSearchClicks, 10);\n    const impressions = parseInt(item.json.organicGoogleSearchImpressions, 10);\n    const ctr = parseFloat(item.json.organicGoogleSearchClickThroughRate) * 100;\n    const position = parseFloat(item.json.organicGoogleSearchAveragePosition);\n\n    // Sum values\n    totalClicks += clicks;\n    totalImpressions += impressions;\n    totalCtrSum += ctr;\n    totalPositionSum += position;\n\n    return {\n        json: {\n            \"Date\": formattedDate, \n            \"Clicks\": clicks, \n            \"Impressions\": impressions, \n            \"CTR (%)\": ctr.toFixed(2).replace(\".\", \",\"), \n            \"Average Position\": position.toFixed(2).replace(\".\", \",\")\n        }\n    };\n});\n\n// Calculate average CTR and position\nconst averageCtr = (totalCtrSum / count).toFixed(2).replace(\".\", \",\");\nconst averagePosition = (totalPositionSum / count).toFixed(2).replace(\".\", \",\");\n\n// Add \"Total\" row\nformattedData.push({\n    json: {\n        \"Date\": \"Total\", \n        \"Clicks\": totalClicks, \n        \"Impressions\": totalImpressions, \n        \"CTR (%)\": averageCtr, \n        \"Average Position\": averagePosition\n    }\n});\n\n// Return data to n8n\nreturn formattedData;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "70b908b4-b2a9-48f4-b3ca-ffda654e9be5",
      "name": "Import to Report 1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -6144,
        -3616
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Clicks",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Clicks",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Impressions",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Impressions",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CTR (%)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "CTR (%)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Average Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Average Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "ea579d0d-06d5-4c6f-a881-725dbc6a28d6",
      "name": "Export Links from Document 1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -6896,
        -4080
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DATA_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "3b9ef20e-a66e-4d37-9541-82fb3883a965",
      "name": "Import Data to Report 2",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -5360,
        -4080
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Article URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Article URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Publication Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Publication Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Main Keyword",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Main Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reach",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reach",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal Links",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal Links",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Link Labels",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Link Labels",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Link Attributes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Link Attributes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "0ee0d8b5-299d-41fe-8cca-cc94b4e9e71c",
      "name": "Analyze Links 1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -6624,
        -4080
      ],
      "parameters": {
        "url": "={{$json[\"URL\"]}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "d51af504-9210-4ad1-aef6-8cc26153405f",
      "name": "Format Data to Table 1",
      "type": "n8n-nodes-base.code",
      "position": [
        -6288,
        -4080
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// 1\ufe0f\u20e3 **Check if data is available**\nif (!$json[\"data\"] || typeof $json[\"data\"] !== \"string\") {\n    throw new Error(\"No HTML data - make sure HTTP Request returns page content.\");\n}\n\n// 2\ufe0f\u20e3 **Get input HTML**\nlet inputData = $json[\"data\"];\n\nconsole.log(\"=== STARTING ANALYSIS ===\");\nconsole.log(\"HTML length:\", inputData.length);\n\n// 3\ufe0f\u20e3 **Finding 'entry-content' section with multiple strategies**\n\n// Strategy 1: Find exact position of <div class=\"entry-content\">\nlet entryContentStart = inputData.indexOf('<div class=\"entry-content\">');\nlet entryContent = \"\";\nlet foundMethod = \"\";\n\nif (entryContentStart !== -1) {\n    console.log(\"\u2705 Found <div class=\\\"entry-content\\\"> at position:\", entryContentStart);\n    \n    // Find end of this div - look for closing </div>\n    // But we need to be careful with nested divs\n    let currentPos = entryContentStart + '<div class=\"entry-content\">'.length;\n    let divCount = 1; // Start with 1 because we're already in a div\n    let endPos = -1;\n    \n    while (currentPos < inputData.length && divCount > 0) {\n        let nextDiv = inputData.indexOf('<div', currentPos);\n        let nextCloseDiv = inputData.indexOf('</div>', currentPos);\n        \n        if (nextCloseDiv === -1) break; // No more closing divs\n        \n        if (nextDiv !== -1 && nextDiv < nextCloseDiv) {\n            // Found opening div before closing\n            divCount++;\n            currentPos = nextDiv + 4;\n        } else {\n            // Found closing div\n            divCount--;\n            if (divCount === 0) {\n                endPos = nextCloseDiv;\n                break;\n            }\n            currentPos = nextCloseDiv + 6;\n        }\n    }\n    \n    if (endPos !== -1) {\n        entryContent = inputData.substring(entryContentStart + '<div class=\"entry-content\">'.length, endPos);\n        foundMethod = \"Strategy 1: Precise div parsing\";\n        console.log(\"\u2705 Extracted entry-content, length:\", entryContent.length);\n    }\n}\n\n// Fallback if we didn't find it or something went wrong\nif (!entryContent || entryContent.length < 100) {\n    console.log(\"\u274c Strategy 1 didn't work, trying fallback\");\n    let match = inputData.match(/<div class=\"entry-content\">([\\s\\S]*?)(?=<\\/div>)/);\n    if (match) {\n        entryContent = match[1];\n        foundMethod = \"Strategy 2: Regex fallback\";\n    } else {\n        // Last attempt - take everything between entry-content and footer\n        let start = inputData.indexOf('entry-content');\n        let end = inputData.indexOf('<footer', start);\n        if (start !== -1 && end !== -1) {\n            entryContent = inputData.substring(start, end);\n            foundMethod = \"Strategy 3: From entry-content to footer\";\n        } else {\n            throw new Error(\"Cannot find entry-content section in any way\");\n        }\n    }\n}\n\nconsole.log(\"Used strategy:\", foundMethod);\nconsole.log(\"entry-content length:\", entryContent.length);\nconsole.log(\"entry-content start:\", entryContent.slice(0, 300));\n\n// 4\ufe0f\u20e3 **Finding links in entry-content**\nconsole.log(\"\\n=== LINK ANALYSIS ===\");\n\nconst allLinks = [];\n\n// Very simple and effective regex for links\nconst linkRegex = /<a\\s[^>]*href=[\"']([^\"']+)[\"'][^>]*>((?:(?!<\\/a>)[\\s\\S])*)<\\/a>/gi;\n\nlet match;\nlet linkCount = 0;\n\nwhile ((match = linkRegex.exec(entryContent)) !== null && linkCount < 50) {\n    linkCount++;\n    \n    const href = match[1].trim();\n    const fullContent = match[2];\n    \n    console.log(`\\n--- Link ${linkCount} ---`);\n    console.log(\"URL:\", href);\n    console.log(\"Content:\", fullContent.slice(0, 100));\n    \n    // Extract clean anchor text\n    let anchorText = fullContent.replace(/<[^>]*>/g, \"\").trim();\n    anchorText = anchorText.replace(/\\s+/g, \" \").trim();\n    \n    // Check rel attribute in entire <a> tag\n    const fullTag = match[0];\n    const relMatch = fullTag.match(/rel=[\"']([^\"']+)[\"']/i);\n    const rel = relMatch ? relMatch[1] : \"follow\";\n    \n    console.log(\"Anchor text:\", anchorText);\n    console.log(\"Rel:\", rel);\n    \n    // \ud83d\udccc **VERY BROAD internal link filters**\n    // Replace 'example.com' with your actual domain\n    const domain = \"example.com\";\n    const isInternalLink = (\n        // All links containing your domain\n        href.includes(domain) ||\n        // Relative links\n        (href.startsWith(\"/\") && !href.startsWith(\"//\")) ||\n        // Hash links\n        href.startsWith(\"#\") ||\n        // Links without protocol starting with domain\n        (href.indexOf(\"://\") === -1 && href.indexOf(\"www.\") === -1 && !href.startsWith(\"mailto:\") && !href.startsWith(\"tel:\"))\n    );\n    \n    console.log(\"Is internal:\", isInternalLink);\n    \n    if (isInternalLink && anchorText && anchorText.length > 0) {\n        // Check duplicates\n        const isDuplicate = allLinks.some(existingLink => \n            existingLink.href === href && existingLink.anchorText === anchorText\n        );\n        \n        if (!isDuplicate) {\n            allLinks.push({\n                href,\n                anchorText,\n                rel\n            });\n            console.log(\"\u2705 ADDED TO LIST\");\n        } else {\n            console.log(\"\u274c DUPLICATE - skipped\");\n        }\n    } else {\n        console.log(\"\u274c REJECTED - doesn't meet criteria\");\n    }\n}\n\n// \u2705 **Link summary**\nconsole.log(`\\n=== LINK SUMMARY ===`);\nconsole.log(\"Total number of internal links found:\", allLinks.length);\n\nallLinks.forEach((link, index) => {\n    console.log(`${index + 1}. ${link.href} -> \"${link.anchorText}\" [${link.rel}]`);\n});\n\n// 5\ufe0f\u20e3 **Format results**\nconst internalLinks = allLinks.map(link => link.href).join(\", \");\nconst anchorTexts = allLinks.map(link => link.anchorText).join(\", \");\nconst linkAttributes = allLinks.map(link => link.rel).join(\", \");\n\n// 6\ufe0f\u20e3 **Get metadata**\nconsole.log(\"\\n=== GETTING METADATA ===\");\n\n// Title - first check h1 in entry-content\nlet title = \"No title\";\nconst titleMatch = entryContent.match(/<h1[^>]*>(.*?)<\\/h1>/i);\nif (titleMatch) {\n    title = titleMatch[1].replace(/<[^>]*>/g, \"\").trim();\n    console.log(\"Title from entry-content:\", title);\n} else {\n    // Fallback - title tag\n    const titleTagMatch = inputData.match(/<title[^>]*>(.*?)<\\/title>/i);\n    if (titleTagMatch) {\n        title = titleTagMatch[1].replace(/<[^>]*>/g, \"\").trim();\n        console.log(\"Title from title tag:\", title);\n    }\n}\n\n// Article URL\nconst canonicalMatch = inputData.match(/<link[^>]+rel=[\"']canonical[\"'][^>]+href=[\"']([^\"']+)[\"']/i);\nconst articleUrl = canonicalMatch ? canonicalMatch[1] : \"No link\";\nconsole.log(\"Article URL:\", articleUrl);\n\n// Publication date\nconst publishedDateMatch = inputData.match(/<meta[^>]+property=[\"']article:published_time[\"'][^>]+content=[\"']([^\"']+)[\"']/i);\nlet publishedDate = publishedDateMatch ? publishedDateMatch[1] : \"No date\";\nconsole.log(\"Publication date (raw):\", publishedDate);\n\nfunction formatDate(dateString) {\n    if (!dateString || dateString === \"No date\") return \"No date\";\n    \n    console.log(\"Formatting date - input:\", dateString);\n    \n    try {\n        // If date contains T or + (ISO format), try directly first\n        let date = new Date(dateString);\n        \n        // If it doesn't work, try to extract only date part\n        if (isNaN(date.getTime())) {\n            console.log(\"First attempt failed, trying alternative\");\n            \n            // Extract only YYYY-MM-DD part\n            const dateMatch = dateString.match(/(\\d{4})-(\\d{2})-(\\d{2})/);\n            if (dateMatch) {\n                const [, year, month, day] = dateMatch;\n                date = new Date(year, month - 1, day); // month-1 because JavaScript counts months from 0\n                console.log(\"Created date from parts:\", year, month, day);\n            }\n        }\n        \n        if (isNaN(date.getTime())) {\n            console.log(\"All date parsing attempts failed\");\n            return \"Invalid date\";\n        }\n        \n        const day = String(date.getDate()).padStart(2, '0');\n        const month = String(date.getMonth() + 1).padStart(2, '0');\n        const year = String(date.getFullYear()).slice(2);\n        \n        console.log(`Formatted date: ${day}-${month}-${year}`);\n        return `${day}-${month}-${year}`;\n        \n    } catch (error) {\n        console.log(\"Date formatting error:\", error);\n        return \"Date formatting error\";\n    }\n}\n\npublishedDate = formatDate(publishedDate);\nconsole.log(\"Publication date (formatted):\", publishedDate);\n\n// Keywords\nconst keywordsMatch = inputData.match(/<meta[^>]+name=[\"']keywords[\"'][^>]+content=[\"']([^\"']+)[\"']/i);\nconst keywords = keywordsMatch ? keywordsMatch[1] : \"No keywords\";\nconsole.log(\"Keywords:\", keywords);\n\n// 7\ufe0f\u20e3 **Return results**\nconsole.log(\"\\n=== FINAL RESULTS ===\");\nconsole.log(\"Title:\", title);\nconsole.log(\"Article URL:\", articleUrl);\nconsole.log(\"Published Date:\", publishedDate);\nconsole.log(\"Keywords:\", keywords);\nconsole.log(\"Internal Links:\", internalLinks);\nconsole.log(\"Anchor Texts:\", anchorTexts);\nconsole.log(\"Link Attributes:\", linkAttributes);\n\nreturn {\n    title,\n    articleUrl,\n    publishedDate,\n    keywords,\n    volume: \"\",\n    internalLinks,\n    anchorTexts,\n    linkAttributes\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "75f79f2d-4811-4ece-945b-9c6a0ce98183",
      "name": "Format Data for Table Paste 1",
      "type": "n8n-nodes-base.code",
      "position": [
        -5856,
        -4080
      ],
      "parameters": {
        "jsCode": "// Get input data from Google Sheets\nconst data = $input.all().map(item => item.json);\n\n// Map new column headers\nconst newHeaders = {\n    title: \"Title\",\n    articleUrl: \"Article URL\",\n    publishedDate: \"Publication Date\",\n    keywords: \"Main Keyword\",\n    volume: \"Reach\",\n    internalLinks: \"Internal Links\",\n    anchorTexts: \"Link Labels\",\n    linkAttributes: \"Link Attributes\"\n};\n\n// Function to convert date from ISO to DD-MM-YY format - FIXED\nfunction formatDate(dateString) {\n    if (!dateString || dateString === \"No date\" || dateString === \"\") {\n        console.log(\"No date to format\");\n        return \"\"; // If no date, return empty string\n    }\n    \n    console.log(\"Formatting date in processing - input:\", dateString, \"type:\", typeof dateString);\n    \n    // Check if date is already in DD-MM-YY format\n    if (typeof dateString === \"string\" && /^\\d{2}-\\d{2}-\\d{2}$/.test(dateString)) {\n        console.log(\"Date already in DD-MM-YY format, returning unchanged:\", dateString);\n        return dateString;\n    }\n    \n    try {\n        // If date contains T or + (ISO format), try directly first\n        let date = new Date(dateString);\n        \n        // If it doesn't work, try to extract only date part\n        if (isNaN(date.getTime())) {\n            console.log(\"First attempt failed, trying alternative\");\n            \n            // Extract only YYYY-MM-DD part\n            const dateMatch = dateString.match(/(\\d{4})-(\\d{2})-(\\d{2})/);\n            if (dateMatch) {\n                const [, year, month, day] = dateMatch;\n                date = new Date(year, month - 1, day); // month-1 because JavaScript counts months from 0\n                console.log(\"Created date from parts:\", year, month, day);\n            }\n        }\n        \n        // If still no valid date\n        if (isNaN(date.getTime())) {\n            console.log(\"All date parsing attempts failed for:\", dateString);\n            return dateString; // Return original value instead of empty string\n        }\n        \n        const day = String(date.getDate()).padStart(2, '0');\n        const month = String(date.getMonth() + 1).padStart(2, '0');\n        const year = String(date.getFullYear()).slice(2);\n        \n        const formattedResult = `${day}-${month}-${year}`;\n        console.log(`Formatted date: ${formattedResult}`);\n        return formattedResult;\n        \n    } catch (error) {\n        console.log(\"Date formatting error:\", error, \"- returning original value\");\n        return dateString; // Return original value instead of empty string\n    }\n}\n\n// Process data and format rows\nconst processedData = [];\n\ndata.forEach((row, rowIndex) => {\n    console.log(`\\nProcessing row ${rowIndex + 1}:`, row);\n    \n    const internalLinks = row.internalLinks ? row.internalLinks.split(\", \") : [];\n    const anchorTexts = row.anchorTexts ? row.anchorTexts.split(\", \") : [];\n    const linkAttributes = row.linkAttributes ? row.linkAttributes.split(\", \") : [];\n    \n    console.log(\"Found internal links:\", internalLinks.length);\n    console.log(\"Publication date (raw):\", row.publishedDate);\n    \n    // Convert date to DD-MM-YY\n    const formattedDate = formatDate(row.publishedDate);\n    console.log(\"Publication date (formatted):\", formattedDate);\n    \n    // First row contains all main data\n    if (internalLinks.length > 0) {\n        internalLinks.forEach((link, index) => {\n            const processedRow = {\n                [newHeaders.title]: index === 0 ? row.title : \"\",  // Only first row has title\n                [newHeaders.articleUrl]: index === 0 ? row.articleUrl : \"\",  // Article URL\n                [newHeaders.publishedDate]: index === 0 ? formattedDate : \"\",  // Formatted date\n                [newHeaders.keywords]: index === 0 ? row.keywords : \"\",  // Only first row has keyword\n                [newHeaders.volume]: index === 0 ? row.volume || \"\" : \"\",  // Only first row has reach\n                [newHeaders.internalLinks]: link.trim(),  // Each link gets separate row\n                [newHeaders.anchorTexts]: anchorTexts[index] ? anchorTexts[index].trim() : \"\",  // Related link label\n                [newHeaders.linkAttributes]: linkAttributes[index] || \"follow\"  // Default follow\n            };\n            \n            processedData.push(processedRow);\n            console.log(`Added row ${index + 1} for link:`, link.slice(0, 50));\n        });\n    } else {\n        // If no internal links, add row with only basic data\n        const processedRow = {\n            [newHeaders.title]: row.title,\n            [newHeaders.articleUrl]: row.articleUrl,  // Added article URL\n            [newHeaders.publishedDate]: formattedDate,\n            [newHeaders.keywords]: row.keywords,\n            [newHeaders.volume]: row.volume || \"\",\n            [newHeaders.internalLinks]: \"\",\n            [newHeaders.anchorTexts]: \"\",\n            [newHeaders.linkAttributes]: \"\"\n        };\n        \n        processedData.push(processedRow);\n        console.log(\"Added row without internal links\");\n    }\n});\n\nconsole.log(`\\n=== PROCESSING SUMMARY ===`);\nconsole.log(\"Total number of processed rows:\", processedData.length);\nconsole.log(\"First 3 processed rows:\", processedData.slice(0, 3));\n\n// Return processed data in appropriate format\nreturn processedData;"
      },
      "typeVersion": 2
    },
    {
      "id": "9fc35392-f86d-489a-85fe-50ae03a7ee79",
      "name": "Analyze External Links 1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -7184,
        -4592
      ],
      "parameters": {
        "url": "={{$json[\"URL\"]}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "5160562a-ef03-43f2-916d-758dddcecb9b",
      "name": "Format Data for Table 1",
      "type": "n8n-nodes-base.code",
      "position": [
        -6976,
        -4592
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const html = $json[\"data\"] || \"\"; // Get HTML from input data\n\n// Debugging: See what's in html\nconsole.log(\"HTML contains:\", html);\n\n// If HTML is empty, return error\nif (!html || html.trim() === \"\") {\n    return {\n        error: \"No data retrieved from HTTP Request. Check configuration.\",\n        url: $json[\"url\"] || \"No URL\"\n    };\n}\n\n// Extract article title\nconst titleMatch = html.match(/<title>(.*?)<\\/title>/i);\nconst title = titleMatch ? titleMatch[1].trim() : \"Title not found\";\n\n// Extract canonical link\nconst canonicalMatch = html.match(/<link[^>]+rel=[\"']canonical[\"'][^>]+href=[\"']([^\"']+)[\"']/i);\nconst canonical = canonicalMatch ? canonicalMatch[1] : \"Canonical not found\";\n\n// Extract article publication date\nconst dateMatch = html.match(/<meta[^>]+property=[\"']article:published_time[\"'][^>]+content=[\"']([^\"']+)[\"']/i);\nlet publishDate = dateMatch ? dateMatch[1] : \"\";\n\n// \ud83d\udd04 **Convert date format to DD-MM-YY**\nfunction formatDate(dateString) {\n    if (!dateString) return \"\"; // If no date, return empty string\n    const date = new Date(dateString);\n    const day = String(date.getDate()).padStart(2, '0');\n    const month = String(date.getMonth() + 1).padStart(2, '0'); // Months start from 0\n    const year = String(date.getFullYear()).slice(2); // Get only last two digits of year\n    return `${day}-${month}-${year}`;\n}\n\n// Convert date to new format\npublishDate = formatDate(publishDate);\n\n// Extract links containing your domain\n// Replace 'example.com' with your actual domain\nconst domain = \"example.com\";\nconst links = [];\nconst regex = /<a[^>]+href=\"([^\"]+)\"[^>]*>(.*?)<\\/a>/g;\nlet match;\n\nwhile ((match = regex.exec(html)) !== null) {\n    const href = match[1];\n    const text = match[2].trim();\n\n    // Check rel=\"nofollow\" attribute\n    const relMatch = match[0].match(/rel=[\"']nofollow[\"']/i);\n    const relType = relMatch ? \"nofollow\" : \"follow\";\n\n    if (href.includes(domain) || text.includes(domain)) {\n        links.push({ href, text, rel: relType });\n    }\n}\n\n// Return data as single object\nreturn {\n    title,\n    canonical,\n    publishDate, // New field with date in DD-MM-YY format\n    links: links.length ? links : [{ message: `No links containing '${domain}' found in provided HTML.` }],\n    url: $json[\"url\"] || \"No URL\"\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "ecde8070-6d59-4544-858f-86fc639768e7",
      "name": "Proper Data Formatting",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -6800,
        -4592
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "GPT-4O"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Input data:\nArticle title: {{$json[\"title\"]}}\nCanonical link: {{$json[\"canonical\"]}}\nPublication date: {{$json[\"publishDate\"]}}\n\nLinks in article:\n{{ $json.links.map(link => `- ${link.text} (${link.href}) [${link.rel}]`).join('\\n') }}\n\nTransform this data into tabular format ready to save in Google Sheets:\n\n1. First row: Article title, Canonical link, Publication date.\n2. Next rows: Link, Link description, Follow/nofollow attribute.\n3. Each value separated by comma.\n4. Return data in CSV format, where each line represents one table row."
            }
          ]
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "1ed5cda6-9aa0-40be-acbc-2c933b5141de",
      "name": "Format Data Under Table 1",
      "type": "n8n-nodes-base.code",
      "position": [
        -6464,
        -4592
      ],
      "parameters": {
        "jsCode": "// Initialize output array\nconst output = [];\n\n// Iterate over each input data element\nitems.forEach(item => {\n    // Get CSV data from message.content field\n    const content = item.json.message.content;\n\n    // Remove Markdown markers and split data into lines\n    const lines = content.replace(/```csv/g, '').trim().split('\\n');\n\n    // Check if there are enough lines\n    if (lines.length < 4) {\n        throw new Error(\"Invalid input data format. Check data source.\");\n    }\n\n    // Separate first part (headers and article title) from links\n    const articleInfo = lines.slice(1, 2)[0]?.split(',');\n    const links = lines.slice(3);\n\n    // Check if headers are correct\n    if (!articleInfo || articleInfo.length < 3) {\n        throw new Error(\"Missing article title, canonical link or publication date.\");\n    }\n\n    // Get article title, canonical link and publication date\n    const title = articleInfo[0]?.trim() || \"No title\";\n    const canonical = articleInfo[1]?.trim() || \"No canonical link\";\n    const publishDate = articleInfo[2]?.trim() || \"\"; // New field\n\n    // Process each link and link description\n    links.forEach((linkRow, index) => {\n        if (linkRow.trim() === \"\") return; // Ignore empty rows\n\n        const [link, description, relType] = linkRow.split(',');\n\n        // Add to results with appropriate data\n        output.push({\n            \"Article Title\": index === 0 ? title : \"\", \n            \"Canonical Link\": index === 0 ? canonical : \"\", \n            \"Publication Date\": index === 0 ? publishDate : \"\", // New field\n            \"Link\": link?.trim() || \"No link\",\n            \"Link Description\": description?.trim() || \"No description\",\n            \"Attribute\": relType?.trim() || \"follow\" // New field\n        });\n    });\n});\n\n// Return result data\nreturn output.map(row => ({ json: row }));"
      },
      "typeVersion": 2
    },
    {
      "id": "45992637-7145-4374-9dfa-08d6196a5bf0",
      "name": "Import Data to Report 3",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -6272,
        -4592
      ],
      "parameters": {
        "columns": {
          "value": {
            "Article Title": "={{$json[\"Article Title\"]}}",
            "Publication Date": "={{$json[\"Publication Date\"]}}"
          },
          "schema": [
            {
              "id": "Article Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Article Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Canonical Link",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Canonical Link",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Publication Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Publication Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Link",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Link",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Link Description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Link Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Attribute",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Attribute",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "ddd41e66-02c9-4692-9659-647fbdd1d9ac",
      "name": "Import to Report 3",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -4752,
        -2768
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Clicks",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Clicks",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Impressions",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Impressions",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CTR",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "CTR",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Average Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Average Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "ac4ae239-4cd2-44b3-ac05-ac0529f3164c",
      "name": "Format Data 12",
      "type": "n8n-nodes-base.code",
      "position": [
        -4832,
        -2192
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n    const convertDotsToCommas = (value) => {\n        // Check if the value is a number or can be converted to a number\n        const num = Number(value);\n        if (!isNaN(num) && typeof value === 'string') {\n            return value.replace('.', ',');\n        }\n        // If it's already a number, convert to string and replace\n        if (typeof value === 'number') {\n            return value.toString().replace('.', ',');\n        }\n        return value;\n    };\n\n    return {\n        json: {\n            \"Keyword\": item.json[\"Keyword\"],\n            \"URL\": item.json[\"URL\"],\n            \"Avg. Monthly Searches\": item.json[\"Avg. Monthly Searches\"],\n            \"Position\": item.json[\"Position\"],\n            \"Previous Position\": item.json[\"Previous Position\"],\n            \"Position Change\": item.json[\"Position Change\"],\n            \"Estimated Traffic\": convertDotsToCommas(item.json[\"Estimated Traffic\"]),\n            \"Previous Estimated Traffic\": convertDotsToCommas(item.json[\"Previous Estimated Traffic\"]),\n            \"Estimated Traffic Change\": convertDotsToCommas(item.json[\"Estimated Traffic Change\"]),\n        }\n    };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "2da61380-ac2c-4b10-b368-a8bdb4620e9e",
      "name": "Format Data 13",
      "type": "n8n-nodes-base.code",
      "position": [
        -4848,
        -1712
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n    const convertDotsToCommas = (value) => {\n        // Check if the value is a number or can be converted to a number\n        const num = Number(value);\n        if (!isNaN(num) && typeof value === 'string') {\n            return value.replace('.', ',');\n        }\n        // If it's already a number, convert to string and replace\n        if (typeof value === 'number') {\n            return value.toString().replace('.', ',');\n        }\n        return value;\n    };\n\n    return {\n        json: {\n            \"Keyword\": item.json[\"Keyword\"],\n            \"URL\": item.json[\"URL\"],\n            \"Avg. Monthly Searches\": item.json[\"Avg. Monthly Searches\"],\n            \"Position\": item.json[\"Position\"],\n            \"Previous Position\": item.json[\"Previous Position\"],\n            \"Position Change\": item.json[\"Position Change\"],\n            \"Estimated Traffic\": convertDotsToCommas(item.json[\"Estimated Traffic\"]),\n            \"Previous Estimated Traffic\": convertDotsToCommas(item.json[\"Previous Estimated Traffic\"]),\n            \"Estimated Traffic Change\": convertDotsToCommas(item.json[\"Estimated Traffic Change\"]),\n        }\n    };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "77867ec9-6e2f-45a0-83b7-ce6891c4c17b",
      "name": "Export Data from Document 1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -5056,
        -2192
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DATA_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "9346647f-74f3-4c5e-b242-2fb8f2b7d3a0",
      "name": "Export Data from Document 2",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -5056,
        -1712
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DATA_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "be44d212-6e2b-45e7-ab1b-0fe143515d7e",
      "name": "Import Data to Sheet 10",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -4608,
        -2192
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Avg. Monthly Searches",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Avg. Monthly Searches",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Previous Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position Change",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Position Change",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Estimated Traffic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Estimated Traffic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Estimated Traffic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Previous Estimated Traffic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Estimated Traffic Change",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Estimated Traffic Change",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "20f781ef-e427-430f-9a71-a3c0c427ad8a",
      "name": "Import Data to Sheet 11",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -4624,
        -1712
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Avg. Monthly Searches",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Avg. Monthly Searches",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Previous Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position Change",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Position Change",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Estimated Traffic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Estimated Traffic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Estimated Traffic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Previous Estimated Traffic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Estimated Traffic Change",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Estimated Traffic Change",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "04c75c75-76e3-4f12-9c5e-9e6ca6625c49",
      "name": "Sticky Note - Position Increases",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5104,
        -2384
      ],
      "parameters": {
        "color": 5,
        "width": 760,
        "height": 400,
        "content": "## Largest Position Increases\n**Setup Instructions:**\n1. Add growth data in \"Data Sheet\";\n2. Update report document link in node \"Import Data to Sheet 10\"."
      },
      "typeVersion": 1
    },
    {
      "id": "07bd288c-33b4-4427-988c-11d905b69b38",
      "name": "Sticky Note - Position Decreases",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5104,
        -1920
      ],
      "parameters": {
        "color": 5,
        "width": 756,
        "height": 432,
        "content": "## Largest Position Decreases\n**Setup Instructions:**\n1. Add decrease data in \"Data Sheet\";\n2. Update report document link in node \"Import Data to Sheet 11\"."
      },
      "typeVersion": 1
    },
    {
      "id": "ed95ed8e-b6e4-49ee-bfb1-7d3e0337381d",
      "name": "Export Links from Google Document 1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -7392,
        -4592
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_SHEET_NAME"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DATA_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "914be6a9-1ca0-4e89-9579-eb5266275083",
  "connections": {
    "Format Data 7": {
      "main": [
        [
          {
            "node": "Import to Report 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Table 2": {
      "main": [
        [
          {
            "node": "Convert Dots to Commas 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Table 3": {
      "main": [
        [
          {
            "node": "Convert Dots to Commas 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data 12": {
      "main": [
        [
          {
            "node": "Import Data to Sheet 10",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data 13": {
      "main": [
        [
          {
            "node": "Import Data to Sheet 11",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Links 1": {
      "main": [
        [
          {
            "node": "Format Data to Table 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Organize Table 2": {
      "main": [
        [
          {
            "node": "Import to Report 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Organize Table 3": {
      "main": [
        [
          {
            "node": "Import to Report 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data to Table 1": {
      "main": [
        [
          {
            "node": "Format Data for Table Paste 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Proper Data Formatting": {
      "main": [
        [
          {
            "node": "Format Data Under Table 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data for Table 1": {
      "main": [
        [
          {
            "node": "Proper Data Formatting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze External Links 1": {
      "main": [
        [
          {
            "node": "Format Data for Table 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Dots to Commas 2": {
      "main": [
        [
          {
            "node": "Organize Table 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Dots to Commas 3": {
      "main": [
        [
          {
            "node": "Organize Table 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data Under Table 1": {
      "main": [
        [
          {
            "node": "Import Data to Report 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Keywords from GSC 2": {
      "main": [
        [
          {
            "node": "Create Table 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Keywords from GSC 3": {
      "main": [
        [
          {
            "node": "Create Table 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Data from Document 1": {
      "main": [
        [
          {
            "node": "Format Data 12",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Data from Document 2": {
      "main": [
        [
          {
            "node": "Format Data 13",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Links from Document 1": {
      "main": [
        [
          {
            "node": "Analyze Links 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data for Table Paste 1": {
      "main": [
        [
          {
            "node": "Import Data to Report 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Test workflow'": {
      "main": [
        [
          {
            "node": "Export Data from Document 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Export Data from Document 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Import from Google Analytics 1": {
      "main": [
        [
          {
            "node": "Format Data 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Links from Google Document 1": {
      "main": [
        [
          {
            "node": "Analyze External Links 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}