AutomationFlowsData & Sheets › Track Google Rankings Daily with Decodo and Google Sheets

Track Google Rankings Daily with Decodo and Google Sheets

ByKhairul Muhtadin @khmuhtadin on n8n.io

Automatically monitor your keyword positions on Google every day. This workflow uses Decodo to pull live search results and logs them straight into Google Sheets, no manual checking needed.

Cron / scheduled trigger★★★★☆ complexity17 nodes@Decodo/N8N Nodes DecodoGoogle Sheets
Data & Sheets Trigger: Cron / scheduled Nodes: 17 Complexity: ★★★★☆ Added:
Track Google Rankings Daily with Decodo and Google Sheets — n8n workflow card showing @Decodo/N8N Nodes Decodo, Google Sheets integration

This workflow corresponds to n8n.io template #13736 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "EdbRd7sQPXqlrRrY",
  "name": "Track Google Rankings Automatically with Decodo & Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "sched_1",
      "name": "Schedule Run",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        240,
        192
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "set_input_1",
      "name": "Input Defaults",
      "type": "n8n-nodes-base.set",
      "position": [
        448,
        192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "keyword",
              "type": "string",
              "value": "best standing desk"
            },
            {
              "id": "a2",
              "name": "country",
              "type": "string",
              "value": "USA"
            },
            {
              "id": "a3",
              "name": "language",
              "type": "string",
              "value": "en"
            },
            {
              "id": "a4",
              "name": "device",
              "type": "string",
              "value": "desktop"
            },
            {
              "id": "a5",
              "name": "top_n",
              "type": "number",
              "value": 5
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "http_decodo_1",
      "name": "Fetch Google SERP",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        688,
        192
      ],
      "parameters": {
        "geo": "={{ $json.country }}",
        "query": "={{ $json.keyword }}",
        "locale": "={{ $json.language }}",
        "operation": "google_search"
      },
      "credentials": {
        "decodoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "if_has_results_1",
      "name": "Check Results",
      "type": "n8n-nodes-base.if",
      "position": [
        960,
        192
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ Array.isArray($json.results) ? $json.results.length : 0 }}",
              "rightValue": 1
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "split_out_1",
      "name": "Split Payload Results",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1200,
        176
      ],
      "parameters": {
        "include": "allOtherFields",
        "options": {},
        "fieldToSplitOut": "results"
      },
      "typeVersion": 1
    },
    {
      "id": "set_norm_1",
      "name": "Map Result Row",
      "type": "n8n-nodes-base.set",
      "position": [
        1920,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "n1",
              "name": "keyword",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.keyword }}"
            },
            {
              "id": "n2",
              "name": "country",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.country }}"
            },
            {
              "id": "n3",
              "name": "language",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.language }}"
            },
            {
              "id": "n4",
              "name": "device",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.device }}"
            },
            {
              "id": "n5",
              "name": "rank",
              "type": "string",
              "value": "={{ $json.organic.pos_overall || $json.organic.pos || \"\" }}"
            },
            {
              "id": "n6",
              "name": "title",
              "type": "string",
              "value": "={{ $json.organic.title || \"\" }}"
            },
            {
              "id": "n7",
              "name": "url",
              "type": "string",
              "value": "={{ $json.organic.url || \"\" }}"
            },
            {
              "id": "n8",
              "name": "description",
              "type": "string",
              "value": "={{ $json.organic.desc || \"\" }}"
            },
            {
              "id": "n9",
              "name": "checkedAt",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "if_valid_row_1",
      "name": "Check Row Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        2160,
        176
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "u1",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.url || \"\" }}",
              "rightValue": ""
            },
            {
              "id": "u2",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ Number($json.rank || 999) }}",
              "rightValue": "={{ Number($(\"Input Defaults\").item.json.top_n || 5) }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "gs_success_1",
      "name": "Save SERP Results",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2448,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "URL": "={{ $json.url }}",
            "Rank": "={{ $json.rank }}",
            "Title": "={{ $json.title }}",
            "Keyword": "={{ $json.keyword }}",
            "Description": "={{ $json.description }}"
          },
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rank",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Rank",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Au8cuHixZqIgrfYd5QZcViMyhq9Ee-5qhNWcmCRGePI/edit#gid=0",
          "cachedResultName": " SERP_Results"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Au8cuHixZqIgrfYd5QZcViMyhq9Ee-5qhNWcmCRGePI",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Au8cuHixZqIgrfYd5QZcViMyhq9Ee-5qhNWcmCRGePI/edit?usp=drivesdk",
          "cachedResultName": "Keyword monitoring"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "set_err_no_results_1",
      "name": "Build Empty Error",
      "type": "n8n-nodes-base.set",
      "position": [
        2448,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "checkedAt",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            },
            {
              "id": "e2",
              "name": "keyword",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.keyword }}"
            },
            {
              "id": "e3",
              "name": "country",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.country }}"
            },
            {
              "id": "e4",
              "name": "language",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.language }}"
            },
            {
              "id": "e5",
              "name": "device",
              "type": "string",
              "value": "={{ $(\"Input Defaults\").item.json.device }}"
            },
            {
              "id": "e6",
              "name": "errorStage",
              "type": "string",
              "value": "no_results"
            },
            {
              "id": "e7",
              "name": "errorMessage",
              "type": "string",
              "value": "={{ $json.message || ($json.error && $json.error.message) || \"Decodo returned empty results or unexpected payload.\" }}"
            },
            {
              "id": "e8",
              "name": "httpStatus",
              "type": "string",
              "value": "={{ $json.statusCode || $json.code || \"\" }}"
            },
            {
              "id": "e9",
              "name": "rawPreview",
              "type": "string",
              "value": "={{ JSON.stringify($json).slice(0, 300) }}"
            },
            {
              "id": "e10",
              "name": "workflowId",
              "type": "string",
              "value": "EdbRd7sQPXqlrRrY"
            },
            {
              "id": "e11",
              "name": "executionId",
              "type": "string",
              "value": "={{ $execution.id || \"\" }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "set_err_bad_item_1",
      "name": "Build Row Error",
      "type": "n8n-nodes-base.set",
      "position": [
        2672,
        208
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f1",
              "name": "checkedAt",
              "type": "string",
              "value": "={{ $json.checkedAt || $now.toISO() }}"
            },
            {
              "id": "f2",
              "name": "keyword",
              "type": "string",
              "value": "={{ $json.keyword || \"\" }}"
            },
            {
              "id": "f3",
              "name": "country",
              "type": "string",
              "value": "={{ $json.country || \"\" }}"
            },
            {
              "id": "f4",
              "name": "language",
              "type": "string",
              "value": "={{ $json.language || \"\" }}"
            },
            {
              "id": "f5",
              "name": "device",
              "type": "string",
              "value": "={{ $json.device || \"\" }}"
            },
            {
              "id": "f6",
              "name": "errorStage",
              "type": "string",
              "value": "invalid_item"
            },
            {
              "id": "f7",
              "name": "errorMessage",
              "type": "string",
              "value": "={{ \"Result missing URL. title=\" + ($json.title || \"\") }}"
            },
            {
              "id": "f8",
              "name": "httpStatus",
              "type": "string",
              "value": ""
            },
            {
              "id": "f9",
              "name": "rawPreview",
              "type": "string",
              "value": "={{ JSON.stringify($json).slice(0, 300) }}"
            },
            {
              "id": "f10",
              "name": "workflowId",
              "type": "string",
              "value": "EdbRd7sQPXqlrRrY"
            },
            {
              "id": "f11",
              "name": "executionId",
              "type": "string",
              "value": "={{ $execution.id || \"\" }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "gs_error_1",
      "name": "Save SERP Errors",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2848,
        320
      ],
      "parameters": {
        "columns": {
          "value": {
            "keyword": "={{ $json.keyword }}",
            " checkedAt": "={{ $json.checkedAt.toDateTime()}}",
            "errorStage": "={{ $json.errorStage }}",
            "httpStatus": "={{ $json.httpStatus }}",
            "rawPreview": "={{ $json.rawPreview }}",
            "errorMessage": "={{ $json.errorMessage }}"
          },
          "schema": [
            {
              "id": " checkedAt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": " checkedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "keyword",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "errorStage",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "errorStage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "errorMessage",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "errorMessage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "httpStatus",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "httpStatus",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "rawPreview",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "rawPreview",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "SERP_Errors"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Au8cuHixZqIgrfYd5QZcViMyhq9Ee-5qhNWcmCRGePI",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Au8cuHixZqIgrfYd5QZcViMyhq9Ee-5qhNWcmCRGePI/edit?usp=drivesdk",
          "cachedResultName": "Keyword monitoring"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "extract_organic_1",
      "name": "Extract Organic List",
      "type": "n8n-nodes-base.set",
      "position": [
        1440,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "o1",
              "name": "organic",
              "type": "array",
              "value": "={{ ($json.results && $json.results.content && $json.results.content.results && $json.results.content.results.results && $json.results.content.results.results.organic) || ($json.content && $json.content.results && $json.content.results.results && $json.content.results.results.organic) || [] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "split_organic_1",
      "name": "Split Organic Items",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1680,
        176
      ],
      "parameters": {
        "include": "allOtherFields",
        "options": {},
        "fieldToSplitOut": "organic"
      },
      "typeVersion": 1
    },
    {
      "id": "a747d799-124f-4c76-a0cc-53c05ef39d91",
      "name": "Flow Summary",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        -288
      ],
      "parameters": {
        "width": 500,
        "height": 750,
        "content": "## Daily SERP monitor: capture top search results\n\n### How it works\n1. Runs on a daily schedule and loads the configured keyword, country, language, device, and top_n.\n2. Calls Decodo to perform a Google search for the keyword and returns SERP payloads.\n3. Extracts organic results, splits them into rows, and maps title, URL, description, rank, and timestamp.\n4. Validates rows (URL present and rank \u2264 top_n). Valid rows are appended to the SERP_Results Google Sheet; invalid rows or API/no-result errors are logged to SERP_Errors with a timestamp and raw preview.\n\n### Setup\n- [ ] Connect your Google Sheets account used to store results.\n- [ ] Add Decodo API credentials in n8n (Decodo Search node).\n- [ ] Edit the \"Set Search Input\" node to set keyword, country, language, device, and top_n.\n- [ ] Update the Google Sheet ID and sheet names (SERP_Results and SERP_Errors).\n- [ ] Set schedule time or run a manual test run.\n- [ ] Verify appended rows and review SERP_Errors for any failures."
      },
      "typeVersion": 1
    },
    {
      "id": "24fc6079-d7de-404b-bfd6-4cf252d67266",
      "name": "Results Mapping Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2320,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 748,
        "height": 444,
        "content": "## Store Results & Log Errors\nKeeps only valid top-ranked rows and appends them to the results sheet. If there are no results or a row is missing key fields, it writes an error entry to the errors sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "86f89d19-935b-4f42-99d8-1232d668a2af",
      "name": "Error Mapping Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 444,
        "content": "## Schedule & Search Setup\nRuns daily and defines thesearch and scrape with decodo"
      },
      "typeVersion": 1
    },
    {
      "id": "1b4a86e5-b42c-4c26-a498-a1a987093699",
      "name": "Input Example Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        896,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 1404,
        "height": 444,
        "content": "## Parse & Filter Top Results\nBreaks the response into items, extracts organic listings, maps fields, and keeps only valid rows within the top-N ranks."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "timezone": "Asia/Jakarta",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "e4a63262-905a-4245-846a-b4a502554ccc",
  "connections": {
    "Schedule Run": {
      "main": [
        [
          {
            "node": "Input Defaults",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Results": {
      "main": [
        [
          {
            "node": "Split Payload Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Empty Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Defaults": {
      "main": [
        [
          {
            "node": "Fetch Google SERP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map Result Row": {
      "main": [
        [
          {
            "node": "Check Row Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Row Error": {
      "main": [
        [
          {
            "node": "Save SERP Errors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Row Valid": {
      "main": [
        [
          {
            "node": "Save SERP Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Row Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Empty Error": {
      "main": [
        [
          {
            "node": "Save SERP Errors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Google SERP": {
      "main": [
        [
          {
            "node": "Check Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Organic Items": {
      "main": [
        [
          {
            "node": "Map Result Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Organic List": {
      "main": [
        [
          {
            "node": "Split Organic Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Payload Results": {
      "main": [
        [
          {
            "node": "Extract Organic List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Automatically monitor your keyword positions on Google every day. This workflow uses Decodo to pull live search results and logs them straight into Google Sheets, no manual checking needed.

Source: https://n8n.io/workflows/13736/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Automate your SEO competitive analysis with this daily SERP tracker. This workflow uses Decodo to scrape Google Search results for specific keywords and logs rankings directly into Google Sheets, savi

@Decodo/N8N Nodes Decodo, Google Sheets
Data & Sheets

This workflow automates video distribution to 9 social platforms simultaneously using Blotato's API. It includes both a scheduled publisher (checks Google Sheets for videos marked "Ready") and a subwo

Google Sheets, HTTP Request, Form Trigger +2
Data & Sheets

YogiAI. Uses googleSheets, googleSheetsTool, httpRequest, stopAndError. Scheduled trigger; 61 nodes.

Google Sheets, Google Sheets Tool, HTTP Request +1
Data & Sheets

This workflow monitors Google Calendar for events indicating that a customer will visit the company today or the next day, retrieves the required details, and sends reminder notifications to the relev

Google Calendar, Google Sheets, HTTP Request +1
Data & Sheets

Useful if a team is working within a single instance and you want to be notified of what workflows have changed since you last visited them. Another use-case might be monitoring your managed instances

Google Sheets, Execute Workflow Trigger, n8n