{
  "name": "Track competitor Google review counts and star ratings in Google Sheets",
  "nodes": [
    {
      "id": "9a1f4c70-0001-4b01-a001-000000000001",
      "name": "Every Monday morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        460
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9a1f4c70-0002-4b02-a002-000000000002",
      "name": "Set your business and search",
      "type": "n8n-nodes-base.set",
      "position": [
        260,
        460
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9a1f4c70-0011-4b11-a011-+1234567890",
              "name": "yourBusinessName",
              "type": "string",
              "value": "Greg's Auto Repair"
            },
            {
              "id": "9a1f4c70-0012-4b12-a012-+1234567890",
              "name": "searchTerm",
              "type": "string",
              "value": "auto repair"
            },
            {
              "id": "9a1f4c70-0013-4b13-a013-+1234567890",
              "name": "location",
              "type": "string",
              "value": "Nashville, Tennessee, United States"
            },
            {
              "id": "9a1f4c70-0014-4b14-a014-+1234567890",
              "name": "googleDomain",
              "type": "string",
              "value": "google.com"
            },
            {
              "id": "9a1f4c70-0015-4b15-a015-+1234567890",
              "name": "country",
              "type": "string",
              "value": "us"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9a1f4c70-0003-4b03-a003-000000000003",
      "name": "Search the Google local pack",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        520,
        460
      ],
      "parameters": {
        "memory": 4096,
        "actorId": "johnvc~google-local-api",
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "customBody": "={{ JSON.stringify({ q: $json.searchTerm, location: $json.location, google_domain: $json.googleDomain, gl: $json.country, hl: 'en', max_pages: 1 }) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0004-4b04-a004-000000000004",
      "name": "Keep searches that returned results",
      "type": "n8n-nodes-base.filter",
      "position": [
        780,
        460
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "9a1f4c70-0041-4b41-a041-+1234567890",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.local_results }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9a1f4c70-0005-4b05-a005-000000000005",
      "name": "One row per business",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1040,
        460
      ],
      "parameters": {
        "include": "selectedOtherFields",
        "options": {},
        "fieldToSplitOut": "local_results",
        "fieldsToInclude": "search_parameters"
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0006-4b06-a006-000000000006",
      "name": "Score every rival against you",
      "type": "n8n-nodes-base.code",
      "position": [
        1300,
        460
      ],
      "parameters": {
        "jsCode": "// Build this week's review scoreboard.\n// Every business Google showed in the local pack becomes one row, and each\n// rival is scored against your own listing on review count and star rating.\nconst config = $('Set your business and search').first().json;\nconst yourName = String(config.yourBusinessName || '').trim().toLowerCase();\nconst weekOf = $now.toFormat('yyyy-MM-dd');\n\nconst rows = $input.all().map((item) => {\n  const biz = item.json.local_results || {};\n  const params = item.json.search_parameters || {};\n  const title = String(biz.title || '');\n  return {\n    search: params.q || '',\n    location: params.location || '',\n    rank: biz.position ?? '',\n    business: title,\n    isYours: yourName !== '' && title.toLowerCase().includes(yourName),\n    rating: typeof biz.rating === 'number' ? biz.rating : '',\n    reviews: typeof biz.reviews === 'number' ? biz.reviews : '',\n    category: biz.type || '',\n    phone: biz.phone || '',\n    placeId: biz.place_id || '',\n  };\n});\n\n// Your own listing, if it ranked for this search. When it did not, the two\n// comparison columns stay blank rather than guessing a baseline.\nconst you = rows.find((r) => r.isYours);\nconst canCompare = (r, field) =>\n  you && !r.isYours && r[field] !== '' && you[field] !== '';\n\nreturn rows.map((r) => ({\n  json: {\n    'Week of': weekOf,\n    Search: r.search,\n    Location: r.location,\n    'Rank in pack': r.rank,\n    Business: r.business,\n    'Is your business': r.isYours ? 'YES' : '',\n    Rating: r.rating,\n    Reviews: r.reviews,\n    'Reviews vs you': canCompare(r, 'reviews') ? r.reviews - you.reviews : '',\n    'Rating vs you': canCompare(r, 'rating')\n      ? Number((r.rating - you.rating).toFixed(1))\n      : '',\n    Category: r.category,\n    Phone: r.phone,\n    'Place ID': r.placeId,\n  },\n}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9a1f4c70-0007-4b07-a007-000000000007",
      "name": "Append this week's scoreboard",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1560,
        460
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "9a1f4c70-0090-4b90-a090-000000000090",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -620,
        40
      ],
      "parameters": {
        "width": 520,
        "height": 1140,
        "content": "## Track competitor Google review counts and star ratings in Google Sheets\n\nNo Google Business Profile connection, no SerpApi or Bright Data account, and no AI model. Every week this workflow reads the Google local pack for your search term and appends one row per business to your sheet: star rating, review count, rank, and how far ahead or behind each rival sits, with your own listing flagged YES.\n\nBecause it reads Google's public local results instead of a listing you own, it picks up your competitors' numbers as easily as your own. That is the part a Google Business Profile integration cannot do.\n\n**Who's it for**\nOwners watching their reputation against the shop down the street, local SEO consultants reporting to clients, and agencies pitching review generation work.\n\n**How it works**\n1. A schedule trigger fires once a week\n2. One Set node holds your business name, search term, and city\n3. The Apify node pulls the whole local pack for that search\n4. A Code node scores every rival against you, and the rows land in your sheet\n\n**Setup**\n1. Create a free Apify account and add your API token to the Apify credential\n2. Connect your Google account in the Sheets node and pick a spreadsheet\n3. Replace the example business name, search term, and location, then run once to test\n\n**Good to know**\nAbout 2 cents per run, so roughly 9 cents a month on a weekly schedule.\nOne run reads one page of Google's local results, which is about 20 businesses on desktop.\nThis records star rating and review count, not the text of individual reviews.\nActor: https://apify.com/johnvc/google-local-api?fpr=9n7kx3&fp_sid=n8n"
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0091-4b91-a091-000000000091",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -40,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 1. Schedule\nRuns every Monday morning. Change the cadence, or swap in a manual trigger while you test."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0092-4b92-a092-000000000092",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        220,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 2. Your input\nYour business name, search term, and city. This is the only node you edit."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0093-4b93-a093-000000000093",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 3. Search\nRuns the Google Local API actor on your search term. Add your Apify credential here."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0094-4b94-a094-000000000094",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 4. Clean\nSkips the run when Google returned no local results, so blank rows never reach the sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0095-4b95-a095-000000000095",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1000,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 5. Unpack\nSplits the local pack into one item per business."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0096-4b96-a096-000000000096",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1260,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 6. Score\nFlags your listing and works out the review and rating gap to every rival."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0097-4b97-a097-000000000097",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 7. Deliver\nPick your spreadsheet and tab. Columns map automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0080-4b80-a080-000000000080",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        220,
        760
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 220,
        "content": "### Your business not in the pack?\nThe two comparison columns stay blank when your listing does not rank for that search. That is a finding worth logging: try a closer search term, or a city you actually rank in."
      },
      "typeVersion": 1
    },
    {
      "id": "9a1f4c70-0081-4b81-a081-000000000081",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        760
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 220,
        "content": "### Self-hosting n8n?\nThere is also a dedicated community node for this actor: `n8n-nodes-google-local-api` on npm."
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Every Monday morning": {
      "main": [
        [
          {
            "node": "Set your business and search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "One row per business": {
      "main": [
        [
          {
            "node": "Score every rival against you",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search the Google local pack": {
      "main": [
        [
          {
            "node": "Keep searches that returned results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set your business and search": {
      "main": [
        [
          {
            "node": "Search the Google local pack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score every rival against you": {
      "main": [
        [
          {
            "node": "Append this week's scoreboard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep searches that returned results": {
      "main": [
        [
          {
            "node": "One row per business",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}