{
  "name": "Google Maps local business leads to Google Sheets, weekly, new places only (Apify Google Maps Places)",
  "nodes": [
    {
      "parameters": {
        "content": "## Google Maps local business leads to Google Sheets\n\nEvery Monday this workflow searches Google Maps for a business type in an area and appends the places that are not in your sheet yet: name, category, address, phone, website, rating, review count, Google Maps link and the date the place was first seen.\n\n### How it works\n1. **Search Google Maps** runs the Apify actor `arcadium/google-maps-places` (Google Maps Places Scraper by Arcadium Data) for your search terms and area. The area is searched map cell by map cell, so a city search is not limited to one result list.\n2. **Read places in sheet** reads the `placeId` and `cid` columns already in the sheet.\n3. **New places only** keeps place rows whose `placeId` (or CID) is not in the sheet and adds today's date as `firstSeen`.\n4. **Append new places** writes one row per new place. No new places, no rows.\n\n### Set up\n1. Install or enable the Apify node (`@apify/n8n-nodes-apify`) and add an **Apify API** credential.\n2. Edit `searchTerms` and `location` in **Search Google Maps**.\n3. Create a sheet tab **Places** with this header row: `placeId | cid | name | category | address | phone | website | rating | reviewCount | googleMapsUrl | searchTerm | firstSeen`. Add your Google Sheets credential and sheet URL to both Sheets nodes.\n4. Execute once (the first run fills the sheet), then activate.\n\n### Cost\n$0.002 per place on the Free Apify plan at the time of writing. Every place found is charged each week, also the ones already in the sheet: the dedupe keeps the sheet clean, it does not lower the run cost. The node caps a run at $0.50.",
        "height": 900,
        "width": 480
      },
      "id": "8c2f4d10-6a3b-0000-4000-8000-000000000010",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-520, -360]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [1],
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "8c2f4d10-6a3b-0000-4000-8000-000000000001",
      "name": "Every Monday",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [0, 160]
    },
    {
      "parameters": {
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "actorId": "arcadium~google-maps-places",
        "actorInputMode": "json",
        "customBody": "{\n  \"searchTerms\": [\n    \"dentist\",\n    \"orthodontist\"\n  ],\n  \"location\": \"Austin, TX\",\n  \"maxPlacesPerSearch\": 100,\n  \"language\": \"en\",\n  \"country\": \"us\",\n  \"includePhones\": true,\n  \"website\": \"any\",\n  \"skipClosed\": true,\n  \"includeDetails\": false,\n  \"includeCoverageRows\": true,\n  \"includeErrorRows\": true\n}",
        "timeout": 3600,
        "memory": 1024,
        "maxTotalChargeUsd": 0.5,
        "build": "",
        "authentication": "apifyApi"
      },
      "id": "8c2f4d10-6a3b-0000-4000-8000-000000000002",
      "name": "Search Google Maps",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 2,
      "position": [240, 160],
      "credentials": {
        "apifyApi": {
          "id": "REPLACE_WITH_YOUR_APIFY_CREDENTIAL_ID",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "resource": "sheet",
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_YOUR_SPREADSHEET_ID/edit",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "Places",
          "mode": "name"
        },
        "options": {}
      },
      "id": "8c2f4d10-6a3b-0000-4000-8000-000000000003",
      "name": "Read places in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [480, 160],
      "executeOnce": true,
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL_ID",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Keep only places that are not in the sheet yet. Dedupe key: placeId (stable between runs),\n// CID as a fallback. The sheet is the memory: delete a row there and the place comes back next week.\nconst seen = new Set();\nfor (const item of $('Read places in sheet').all()) {\n  const s = item.json;\n  if (s.placeId) seen.add(String(s.placeId));\n  if (s.cid) seen.add(`cid:${s.cid}`);\n}\nconst today = $now.toFormat('yyyy-MM-dd');\nconst out = [];\nfor (const item of $('Search Google Maps').all()) {\n  const r = item.json;\n  // Coverage and error rows are free summary rows; only place rows become leads.\n  if (r.rowType !== 'place') continue;\n  const keys = [r.placeId ? String(r.placeId) : '', r.cid ? `cid:${r.cid}` : ''].filter(Boolean);\n  if (keys.length === 0 || keys.some((k) => seen.has(k))) continue;\n  for (const k of keys) seen.add(k);\n  out.push({\n    json: {\n      placeId: r.placeId ?? '',\n      cid: r.cid ? String(r.cid) : '',\n      name: r.name ?? '',\n      category: r.category ?? '',\n      address: r.address ?? '',\n      phone: r.phone ?? '',\n      website: r.website ?? '',\n      rating: r.rating ?? '',\n      reviewCount: r.reviewCount ?? '',\n      googleMapsUrl: r.placeUrl ?? '',\n      searchTerm: r.searchTerm ?? '',\n      firstSeen: today,\n    },\n  });\n}\nreturn out;"
      },
      "id": "8c2f4d10-6a3b-0000-4000-8000-000000000004",
      "name": "New places only",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [720, 160]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "resource": "sheet",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_YOUR_SPREADSHEET_ID/edit",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "Places",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {
          "cellFormat": "RAW"
        }
      },
      "id": "8c2f4d10-6a3b-0000-4000-8000-000000000005",
      "name": "Append new places",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [960, 160],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL_ID",
          "name": "Google Sheets account"
        }
      }
    }
  ],
  "connections": {
    "Every Monday": {
      "main": [
        [
          {
            "node": "Search Google Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google Maps": {
      "main": [
        [
          {
            "node": "Read places in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read places in sheet": {
      "main": [
        [
          {
            "node": "New places only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New places only": {
      "main": [
        [
          {
            "node": "Append new places",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
