{
  "id": "ja1ZFW7wTlDady1i",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Track keyword position dynamics by URL in Google Sheets with DataForSEO",
  "tags": [],
  "nodes": [
    {
      "id": "f7140313-2cd1-4c50-8145-54ff993174d4",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        -16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9,
              "weeksInterval": 2
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "35b93b54-9482-46b7-b5d4-61dd27789ffb",
      "name": "Create sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1088,
        -16
      ],
      "parameters": {
        "title": "={{ $json.url }}",
        "options": {},
        "operation": "create",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98/edit?usp=drivesdk",
          "cachedResultName": "Positions tracking - Output"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "25f68bcd-afa7-4fb1-b887-599cd7b3fa13",
      "name": "Get live google organic SERP regular",
      "type": "n8n-nodes-dataforseo.dataForSeoSerpApi",
      "position": [
        2224,
        -16
      ],
      "parameters": {
        "depth": 20,
        "target": "={{ $('Normalize URL').item.json.url }}",
        "keyword": "={{ $('Loop over items (each keyword)').item.json.Keyword }}",
        "language_name": "english",
        "location_name": "united states"
      },
      "credentials": {
        "dataForSeoApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "4ea70828-9a12-49b2-b656-6b8d93e21396",
      "name": "Append or update row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3008,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "Keyword"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Normalize URL').item.json.url }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98/edit?usp=drivesdk",
          "cachedResultName": "Positions tracking - Output"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5334a925-5b20-402a-a181-1f80e007e1f9",
      "name": "Append row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1760,
        -160
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "Keyword"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Normalize URL').item.json.url }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98/edit?usp=drivesdk",
          "cachedResultName": "Positions tracking - Output"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "f2d5e1e0-e533-4072-a331-88d9bf345e0f",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1984,
        -16
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "889e65ab-c1bb-4ebe-b2e0-483d5d694824",
      "name": "Normalize URL",
      "type": "n8n-nodes-base.code",
      "position": [
        864,
        -16
      ],
      "parameters": {
        "jsCode": "let url = $input.first().json.URL;\nlet clean = url.split('#')[0];\nclean = clean.split('?')[0];\nif (clean.length > 1 && clean.endsWith('/')) {\n  clean = clean.slice(0, -1);\n}\n\nreturn {\n  url: clean.trim()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "719451fb-56f3-481f-89df-102ad637e5cd",
      "name": "Calculate delta & status",
      "type": "n8n-nodes-base.code",
      "position": [
        2656,
        -16
      ],
      "parameters": {
        "jsCode": "let delta = null;\nconst prevPosition = $input.first().json['position_' + $now.plus({days: -14}).toFormat('yyyy-MM-dd')] || null;\nconst currentPosition = $('Get live google organic SERP regular').first()?.json?.tasks?.[0]?.result?.[0]?.items?.[0]?.rank_absolute || null;\nif (\n  prevPosition \n  && prevPosition !== 'Not Found'\n  && prevPosition !== 'Error'\n  && currentPosition\n) {\n  delta = prevPosition - currentPosition;\n}\n\nlet status = 'error';\n\nif ($('Get live google organic SERP regular').first().json.tasks?.[0]?.result?.[0]?.items == null) {\n  status = 'not_found';\n} else {\n  status = 'found';\n}\n\nreturn {\n  delta: delta,\n  status: status\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "671dfbea-8dcf-4225-a3c1-d35127a1e798",
      "name": "Prepare data for GS",
      "type": "n8n-nodes-base.set",
      "position": [
        2832,
        -16
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={\n\"Keyword\": {{ $('Loop over items (each keyword)').item.json.Keyword.trim() }}\n \"position_{{ $now.format('yyyy-MM-dd') }}\": {{ $('Get live google organic SERP regular').item.json.tasks[0].result[0].items[0].rank_absolute }},\n\"delta_{{ $now.format('yyyy-MM-dd') }}\": {{ $json.delta }},\n\"matched_url_{{ $now.format('yyyy-MM-dd') }}\": {{ $('Get live google organic SERP regular').item.json.tasks[0].result[0].items[0].url }},\n\"status_{{ $now.format('yyyy-MM-dd') }}\": {{ $json.status }}\n}\n "
      },
      "typeVersion": 3.4
    },
    {
      "id": "c4d897ba-1801-4f1e-80d2-65f5a87d75c2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 1104,
        "height": 416,
        "content": "## Create a new sheet if it doesn't exist\nCreate or select a Google Sheets connection. Select output spreadsheet."
      },
      "typeVersion": 1
    },
    {
      "id": "9019d097-8f6b-4f3b-9243-a711675dfe96",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 976,
        "height": 416,
        "content": "## Get SERP data with DataForSEO and save it to Google Sheets\nCreate or select a Google Sheets connection, and select the output spreadsheet.\nCreate or select a DataForSEO connection."
      },
      "typeVersion": 1
    },
    {
      "id": "3609c7a5-906a-4945-8b5d-a67bbd50e1df",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 432,
        "height": 416,
        "content": "## Get active keywords from the input in Google Sheets\nCreate or select a Google Sheets connection. Select the input table with keywords. The sheet must have the same columns as in [this Example](https://docs.google.com/spreadsheets/d/1WPeLL-5futt5PzcUvbQRX3k2RXCEpliddJWuNlfdLR4/edit?usp=sharing)."
      },
      "typeVersion": 1
    },
    {
      "id": "a1111093-728a-4a25-b71a-6f000ec69ac5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -384
      ],
      "parameters": {
        "width": 480,
        "height": 624,
        "content": "This workflow automatically retrieves URLs and their target keywords from Google Sheets, checks their current rankings on Google search via DataForSEO, and writes the results into a historical Google Sheets report where each tab represents a separate URL and each run adds new date-based columns with rankings and changes.\n\n## How it works\n1. Triggers automatically every two weeks.\n2. Fetches keywords and URLs from your input in Google Sheets (if set, fetches only active records).\n3. Checks the top-20 Google search results for your keywords and URLs using the DataForSEO SERP API.\n4. Creates a dedicated sheet for each URL, saves the current position in Google Sheets, calculates the ranking delta, and logs status. \n5. Adds new columns for each date of the run, creating a historical data record in Google Sheets.\n\n## Setup steps\n\n1. Create or select your DataForSEO connection (use your [API login and password](https://app.dataforseo.com/api-access)).\n2. Create or select a Google Sheets connection for the input and output spreadsheets.\n3. Prepare the input sheet with keywords and required columns (URL, Keyword, Active).\n4. Set the preferred workflow schedule."
      },
      "typeVersion": 1
    },
    {
      "id": "e303f2c2-a6f7-4ff3-a990-d349dea3cb41",
      "name": "Get keywords and URLs",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        224,
        -16
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1JLgnLCFD1qhdbr1-rbeTgi1Sv3sIynMvmRRssJBQJqw/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1JLgnLCFD1qhdbr1-rbeTgi1Sv3sIynMvmRRssJBQJqw",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1JLgnLCFD1qhdbr1-rbeTgi1Sv3sIynMvmRRssJBQJqw/edit?usp=drivesdk",
          "cachedResultName": "Positions tracking  - Input"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "dc168507-20eb-4712-a778-66bd841e168d",
      "name": "Filter (only active)",
      "type": "n8n-nodes-base.filter",
      "position": [
        432,
        -16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "31a61fed-0f56-42f5-94bd-0216e3a8b592",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Active"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "2d710aa6-088a-435c-b83f-94c96306f331",
      "name": "Loop over items (each keyword)",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        656,
        -16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "c9db71a9-96d3-481e-b907-cc3380eb9d77",
      "name": "Is a new sheet created?",
      "type": "n8n-nodes-base.if",
      "position": [
        1296,
        -16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "fd006d00-db1b-4407-a2ef-6185a3626aa9",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.spreadsheetId }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "05e7082f-a801-429e-97fe-2a8db2b8619e",
      "name": "Prepare column data for GS",
      "type": "n8n-nodes-base.set",
      "position": [
        1536,
        -160
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={\n  \"Keyword\": \"\"\n}\n "
      },
      "typeVersion": 3.4
    },
    {
      "id": "ab41c942-ec14-4a3d-a093-d35743b31663",
      "name": "Find the row with the keyword",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2448,
        -16
      ],
      "parameters": {
        "options": {
          "returnFirstMatch": true
        },
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Loop over items (each keyword)').item.json.Keyword }}",
              "lookupColumn": "=Keyword"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Normalize URL').item.json.url }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Ms5Mtrf7RH13rhSqSWPWJyUTU0477Vj4qbF7qRloQ98/edit?usp=drivesdk",
          "cachedResultName": "Positions tracking - Output"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "af44906b-98a4-4b62-b175-7dcd75d987b1",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Get live google organic SERP regular",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create sheet": {
      "main": [
        [
          {
            "node": "Is a new sheet created?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize URL": {
      "main": [
        [
          {
            "node": "Create sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get keywords and URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append row in sheet": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare data for GS": {
      "main": [
        [
          {
            "node": "Append or update row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter (only active)": {
      "main": [
        [
          {
            "node": "Loop over items (each keyword)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get keywords and URLs": {
      "main": [
        [
          {
            "node": "Filter (only active)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is a new sheet created?": {
      "main": [
        [
          {
            "node": "Prepare column data for GS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calculate delta & status": {
      "main": [
        [
          {
            "node": "Prepare data for GS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare column data for GS": {
      "main": [
        [
          {
            "node": "Append row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append or update row in sheet": {
      "main": [
        [
          {
            "node": "Loop over items (each keyword)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find the row with the keyword": {
      "main": [
        [
          {
            "node": "Calculate delta & status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop over items (each keyword)": {
      "main": [
        [],
        [
          {
            "node": "Normalize URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get live google organic SERP regular": {
      "main": [
        [
          {
            "node": "Find the row with the keyword",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}