
Track Google Search Rankings with Bright Data, Google Sheets & Gmail
Description
Categories
📢 Marketing
Nodes Used
n8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.gmailn8n-nodes-base.stickyNoten8n-nodes-base.httpRequestn8n-nodes-base.googleSheetsn8n-nodes-base.googleSheetsn8n-nodes-base.manualTriggern8n-nodes-base.splitInBatches
PriceFree
Views0
Last Updated11/28/2025
workflow.json
{
"meta": {
"instanceId": "b3b467644dfa8e0c6797928cc6d8bf68d9415ef612f9ba2a0d45772e9aff0f75",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "65c5da22-3579-48d7-a37d-899c956a4b3a",
"name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1600,
575
],
"parameters": {},
"typeVersion": 1
},
{
"id": "8279787b-d056-4a46-aca3-2888416f46cf",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1600,
240
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 24
}
]
}
},
"typeVersion": 1.2
},
{
"id": "5d17fa71-520a-45bf-82b4-ebb5025085c5",
"name": "Making Email Template",
"type": "n8n-nodes-base.code",
"position": [
-720,
40
],
"parameters": {
"jsCode": "const simplifiedResults = items.map(item => {\n return {\n json: {\n Keyword: item.json.Keyword,\n rank: item.json.rank\n }\n };\n});\n\n// Start the HTML table\nlet emailContent = \"<table border='1' cellpadding='5' cellspacing='0' style='border-collapse: collapse;'>\";\n\n// Add table header\nemailContent += \"<tr><th>Keyword</th><th>Rank</th></tr>\";\n\n// Add table rows for each result\nsimplifiedResults.forEach(result => {\n emailContent += `<tr><td>${result.json.Keyword}</td><td>${result.json.rank}</td></tr>`;\n});\n\n// Close the table tag\nemailContent += \"</table>\";\n\n// Return the email content with the table\nreturn [\n {\n json: {\n emailBody: emailContent\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "f689b1b7-553f-4a88-ae8f-94de4bd6701a",
"name": "Reading Keywords",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1380,
240
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1441108147,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1fvWyTep-kTTyStxqKjgh3qlnH3uAC4c75uLMJQp8s6A/edit#gid=1441108147",
"cachedResultName": "Keyword"
},
"documentId": "={{ $credentials.googleSheetDocId }}"
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YBSRE682i2VDBuXC",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "3fb77ecb-4f3c-4e87-9ffb-70d5e38bb3ff",
"name": "Transforming Keywords",
"type": "n8n-nodes-base.code",
"position": [
-1160,
240
],
"parameters": {
"jsCode": "// Get all rows from input\nconst items = $input.all();\n\n// Transform each row's 'Keyword' field\nreturn items.map(item => {\n const keyword = item.json.Keyword || '';\n const transformedKeyword = keyword.replaceAll(' ', '+');\n\n return {\n json: {\n ...item.json, // keep original data\n transformedKeyword\n }\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "fe63d679-a349-4ce2-8d0b-9122da534aa8",
"name": "Loop over Keywords",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-940,
240
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "2ad0d400-d52b-4401-8f65-c79d7b329c32",
"name": "Getting Ranks",
"type": "n8n-nodes-base.httpRequest",
"position": [
-720,
240
],
"parameters": {
"url": "https://api.brightdata.com/request",
"method": "POST",
"options": {},
"jsonBody": "={\"zone\": \"serp_n8n\",\"url\": \"https://www.google.com/search?q={{ $json.transformedKeyword }}&gl=US\", \n \"format\": \"raw\"} ",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{$credentials.brightDataApiKey}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "99acbc30-b1d8-4c16-819a-20b4a186a56d",
"name": "Rank Finder",
"type": "n8n-nodes-base.code",
"position": [
-500,
240
],
"parameters": {
"jsCode": "const html = $input.first().json.data;\nconst targetDomain = $('Reading Keywords').first().json.Domain; \n\n\nconst regex = /<a[^>]+href=\"(http[^\"]+)\"[^>]*>/g;\nlet match;\nlet links = [];\nwhile ((match = regex.exec(html)) !== null) {\n const url = match[1];\n if (\n url.startsWith(\"http\") &&\n !url.includes(\"google.com\") &&\n !url.includes(\"/search?\") &&\n !url.includes(\"webcache\")\n ) {\n links.push(url);\n }\n}\n\n// Deduplicate and trim links\nlinks = [...new Set(links.map(link => link.trim()))];\n\n// Try to find the rank (position) of your target domain\nlet rank = null;\nlet foundUrl = null;\nfor (let i = 0; i < links.length; i++) {\n if (links[i].includes(targetDomain)) {\n rank = i + 1; // 1-based position\n foundUrl = links[i];\n break;\n }\n}\n\n// Get current date and time\nconst now = new Date();\nconst dateTime = now.toLocaleString(); // e.g., \"5/19/2025, 2:30:00 PM\"\n\n// Output result\nreturn [\n {\n json: {\n row: $('Loop over Keywords').first().json.row_number,\n rank: rank || \"Not Ranked\",\n url: foundUrl || \"N/A\",\n totalResultsChecked: links.length,\n found: !!foundUrl,\n checkedAt: dateTime\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "eec8fa1e-c0c7-48a5-b7e4-9e5d9be3a52f",
"name": "Post Rank Results",
"type": "n8n-nodes-base.googleSheets",
"position": [
-280,
315
],
"parameters": {
"columns": {
"value": {
"url": "={{ $json.url }}",
"rank": "={{ $json.rank }}",
"found": "={{ $json.found }}",
"Domain": "={{ $('Reading Keywords').item.json.Domain }}",
"Keyword": "={{ $('Reading Keywords').item.json.Keyword }}",
"checkedAt": "={{ $json.checkedAt }}",
"totalResultsChecked": "={{ $json.totalResultsChecked }}"
},
"schema": [
{
"id": "Keyword",
"type": "string",
"display": true,
"required": false,
"displayName": "Keyword",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Domain",
"type": "string",
"display": true,
"required": false,
"displayName": "Domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rank",
"type": "string",
"display": true,
"required": false,
"displayName": "rank",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "url",
"type": "string",
"display": true,
"required": false,
"displayName": "url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "totalResultsChecked",
"type": "string",
"display": true,
"required": false,
"displayName": "totalResultsChecked",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "found",
"type": "string",
"display": true,
"required": false,
"displayName": "found",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "checkedAt",
"type": "string",
"display": true,
"required": false,
"displayName": "checkedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Results"
},
"documentId": "={{ $credentials.googleSheetDocId }}"
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YBSRE682i2VDBuXC",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "86b5345d-6a6c-4c5b-9ca2-312ab627f69e",
"name": "Sending Email Message",
"type": "n8n-nodes-base.gmail",
"position": [
-500,
40
],
"webhookId": "71c2feef-d8c3-414b-8cab-d70082ab2df4",
"parameters": {
"sendTo": "={{ $credentials.recipientEmail }}",
"message": "={{ $json.emailBody }}",
"options": {},
"subject": "Ranked"
},
"credentials": {
"gmailOAuth2": {
"id": "9C4Tnt40p6JpYjIr",
"name": "Gmail account"
}
},
"executeOnce": true,
"typeVersion": 2.1
},
{
"id": "2aa430ee-b967-461e-9b20-97d34a1e5775",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2480,
-360
],
"parameters": {
"width": 780,
"height": 1740,
"content": "# n8n Workflow Explanation & Setup Guide\n\n## Workflow Overview\n\nThis n8n workflow automates keyword rank tracking on Google Search and emails the results. It works as follows:\n\n1. **Trigger** \n - Manually by clicking ‘Test workflow’ or automatically every 24 hours.\n\n2. **Read Keywords from Google Sheets** \n - Fetch keywords and target domains from a specified Google Sheets document.\n\n3. **Transform Keywords** \n - Format keywords for URL encoding (replace spaces with `+`).\n\n4. **Batch Processing** \n - Process each keyword individually.\n\n5. **Get Google Search Results** \n - Use BrightData API to fetch raw Google search HTML for each keyword.\n\n6. **Parse and Find Rank** \n - Extract URLs from search results and identify the rank of the target domain.\n\n7. **Save Results to Google Sheets** \n - Append rank results to a \"Results\" sheet.\n\n8. **Prepare Email Template** \n - Format results into an HTML table.\n\n9. **Send Email** \n - Email the results table to a specified recipient.\n\n---\n\n## Setup Instructions\n\n### 1. Google Sheets Setup\n\n- Create a Google Sheet with:\n - A sheet containing `Keyword` and `Domain` columns.\n - A sheet named \"Results\" for output.\n- Update the **document ID** and **sheet names** in:\n - **\"Reading Keywords\"** node\n - **\"Post Rank Results\"** node\n\n### 2. BrightData API Setup\n\n- Sign up for BrightData or a similar data provider.\n- Obtain your API token.\n- Replace the **Authorization Bearer token** in the **\"Getting Ranks\"** HTTP Request node.\n\n### 3. Gmail Email Setup\n\n- Set up Gmail OAuth2 credentials in n8n.\n- Ensure Gmail API access is enabled.\n- Change the recipient email address in the **\"Sending Email Message\"** node's `sendTo` field.\n\n### 4. Change Google Search Location\n\n- In the **\"Getting Ranks\"** node, the location is set via the URL parameter `gl=US`.\n- To change location, replace `US` with the desired country code:\n\n| Country | Code |\n| ------------- | -----|\n| United States | US |\n| United Kingdom | GB |\n| Canada | CA |\n| Australia | AU |\n\nExample (for UK):\n\n```json\n\"url\": \"https://www.google.com/search?q={{ $json.transformedKeyword }}&gl=GB\"\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Rank Finder": {
"main": [
[
{
"node": "Post Rank Results",
"type": "main",
"index": 0
}
]
]
},
"Getting Ranks": {
"main": [
[
{
"node": "Rank Finder",
"type": "main",
"index": 0
}
]
]
},
"Reading Keywords": {
"main": [
[
{
"node": "Transforming Keywords",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Reading Keywords",
"type": "main",
"index": 0
}
]
]
},
"Post Rank Results": {
"main": [
[
{
"node": "Loop over Keywords",
"type": "main",
"index": 0
}
]
]
},
"Loop over Keywords": {
"main": [
[
{
"node": "Making Email Template",
"type": "main",
"index": 0
}
],
[
{
"node": "Getting Ranks",
"type": "main",
"index": 0
}
]
]
},
"Making Email Template": {
"main": [
[
{
"node": "Sending Email Message",
"type": "main",
"index": 0
}
]
]
},
"Sending Email Message": {
"main": [
[]
]
},
"Transforming Keywords": {
"main": [
[
{
"node": "Loop over Keywords",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Test workflow’": {
"main": [
[
{
"node": "Reading Keywords",
"type": "main",
"index": 0
}
]
]
}
}
}