This workflow corresponds to n8n.io template #17670 — 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 →
{
"id": "x8qzsYDsYdpwwNgb",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Scrape Google Maps Business Leads to Google Sheets with SearchApi",
"tags": [],
"nodes": [
{
"id": "manual",
"name": "Run Now",
"type": "n8n-nodes-base.manualTrigger",
"notes": "Run on demand when you need a fresh batch of leads. Click Execute Workflow.",
"position": [
0,
112
],
"parameters": {},
"typeVersion": 1
},
{
"id": "schedule",
"name": "Schedule Weekly",
"type": "n8n-nodes-base.scheduleTrigger",
"notes": "Optional: activate the workflow to pull new listings every Monday at 08:00. Change or delete this if you only run manually.",
"position": [
0,
304
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 8
}
]
}
},
"typeVersion": 1.2
},
{
"id": "settings",
"name": "Settings",
"type": "n8n-nodes-base.set",
"notes": "The only node you normally edit. categories = comma-separated business types. locations = semicolon-separated places (names contain commas, so we split on ;).",
"position": [
240,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a1",
"name": "categories",
"type": "string",
"value": "coffee shop, cafe"
},
{
"id": "a2",
"name": "locations",
"type": "string",
"value": "Austin, Texas, United States; Denver, Colorado, United States"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "build",
"name": "Build Search Queries",
"type": "n8n-nodes-base.code",
"notes": "Expands your categories and locations into one Google Maps search per combination per page. Raise PAGES here for more leads per search.",
"position": [
480,
304
],
"parameters": {
"jsCode": "// Build one Google Maps search per category, per location, per page.\n// Google Maps has no separate location field: the reliable way to target a\n// place is to put it in the query itself, e.g. \"coffee shop in Austin, Texas\".\n// Google Maps returns up to ~20 businesses per page; we page for more depth.\nconst cfg = $('Settings').first().json;\nconst PAGES = 2; // pages per category + location. Each page is ~20 leads. Raise for more depth.\n\nconst categories = (cfg.categories || '')\n .split(',').map(c => c.trim()).filter(c => c.length);\n\n// Locations are semicolon-separated because location names contain commas\n// (e.g. \"Austin, Texas; Denver, Colorado\").\nconst locations = (cfg.locations || '')\n .split(';').map(l => l.trim()).filter(l => l.length);\n\nif (!categories.length) {\n throw new Error('No categories found. Add a comma-separated list in the Settings node, e.g. \"dentist, orthodontist\".');\n}\nif (!locations.length) {\n throw new Error('No locations found. Add a semicolon-separated list in the Settings node, e.g. \"Austin, Texas; Denver, Colorado\".');\n}\n\nconst out = [];\nfor (const category of categories) {\n for (const location of locations) {\n for (let page = 1; page <= PAGES; page++) {\n out.push({ json: { category, location, q: category + ' in ' + location, page } });\n }\n }\n}\nreturn out;"
},
"typeVersion": 2
},
{
"id": "searchapi",
"name": "Scrape Google Maps",
"type": "@searchapi/n8n-nodes-searchapi.searchApi",
"notes": "Live Google Maps results via SearchApi.io, one call per category + location + page. Returns business name, address, phone, website, rating and reviews.",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
720,
304
],
"parameters": {
"q": "={{ $json.q }}",
"resource": "google_maps",
"pagination": {
"page": "={{ $json.page }}"
},
"requestOptions": {},
"locationSettings": {},
"searchConfiguration": {}
},
"credentials": {
"searchApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 2000
},
{
"id": "extract",
"name": "Extract Leads",
"type": "n8n-nodes-base.code",
"notes": "Reads local_results from every page, keeps the useful lead fields, and removes duplicate businesses (by place_id). This is the clean, structured lead list.",
"position": [
960,
304
],
"parameters": {
"jsCode": "// Flatten Google Maps local_results from every page into one row per business.\n// Category and location are recovered from Build Search Queries by position\n// (the scrape returns one item per search, in the same order), then we dedup\n// by place_id so the same business is not listed twice.\nconst items = $input.all();\nconst queries = $('Build Search Queries').all();\nconst seen = new Set();\nconst leads = [];\n\nfor (let i = 0; i < items.length; i++) {\n const res = items[i].json || {};\n const src = (queries[i] && queries[i].json) || {};\n const category = src.category || '';\n const location = src.location || '';\n const results = res.local_results || [];\n\n for (const r of results) {\n const placeId = r.place_id || r.data_id || (r.title + '|' + r.address);\n if (seen.has(placeId)) continue;\n seen.add(placeId);\n\n const mapsUrl = r.place_id\n ? 'https://www.google.com/maps/place/?q=place_id:' + r.place_id\n : '';\n\n // Domain drives the Hunter email lookup. Prefer the domain SearchApi already\n // parsed; otherwise derive it from the website URL.\n let domain = r.domain || '';\n if (!domain && r.website) {\n try { domain = new URL(r.website).hostname.replace(/^www\\./, ''); } catch (e) {}\n }\n\n leads.push({ json: {\n business: r.title || '',\n category: category,\n location: location,\n type: r.type || '',\n address: r.address || '',\n phone: r.phone || '',\n website: r.website || '',\n email: '',\n domain: domain,\n rating: (r.rating !== undefined && r.rating !== null) ? r.rating : '',\n reviews: (r.reviews !== undefined && r.reviews !== null) ? r.reviews : '',\n maps_url: mapsUrl,\n place_id: r.place_id || ''\n } });\n }\n}\n\nreturn leads;"
},
"typeVersion": 2
},
{
"id": "sheet",
"name": "Save Leads to Sheet",
"type": "n8n-nodes-base.googleSheets",
"notes": "Appends each lead as a new row. Select your Google Sheet and tab, and give it a header row: business, category, location, type, address, phone, website, rating, reviews, maps_url, place_id.",
"position": [
1632,
304
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "business",
"type": "string",
"display": true,
"required": false,
"displayName": "business",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "location",
"type": "string",
"display": true,
"required": false,
"displayName": "location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "type",
"type": "string",
"display": true,
"required": false,
"displayName": "type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "address",
"type": "string",
"display": true,
"required": false,
"displayName": "address",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "website",
"type": "string",
"display": true,
"required": false,
"displayName": "website",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reviews",
"type": "string",
"display": true,
"required": false,
"displayName": "reviews",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "maps_url",
"type": "string",
"display": true,
"required": false,
"displayName": "maps_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "place_id",
"type": "string",
"display": true,
"required": false,
"displayName": "place_id",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PLHGO9WBiLaWsSb8N00WB7DtB9mq-lmvezwWwnr6y1M/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1PLHGO9WBiLaWsSb8N00WB7DtB9mq-lmvezwWwnr6y1M",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PLHGO9WBiLaWsSb8N00WB7DtB9mq-lmvezwWwnr6y1M/edit?usp=drivesdk",
"cachedResultName": "n8n003"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "sticky_overview",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-736,
-256
],
"parameters": {
"width": 640,
"height": 900,
"content": "## Scrape Google Maps Business Leads to Google Sheets\n\nTurn any list of business categories and locations into a clean lead list \u2014 business name, phone, website, address, rating, review count, and (via Hunter.io) an email \u2014 pulled live from Google Maps through SearchApi.io.\n\n### How it works\n1. **Run Now** or **Schedule Weekly** starts the run.\n2. **Settings** holds the two things you edit: your categories and your locations.\n3. **Build Search Queries** turns them into one Google Maps search per category \u00d7 location \u00d7 page.\n4. **Scrape Google Maps** fetches live results through SearchApi.io.\n5. **Extract Leads** flattens every business into a row and removes duplicates.\n6. **Find Emails (Hunter)** looks up an email for each business domain.\n7. **Save Leads to Sheet** appends each lead to your Google Sheet.\n\n### Setup\n- [ ] Install the community node `@searchapi/n8n-nodes-searchapi`.\n- [ ] Add your SearchApi credential (free key at searchapi.io) on **Scrape Google Maps**.\n- [ ] Add your Hunter.io credential on **Find Emails (Hunter)** \u2014 or delete that node + **Attach Emails** to skip emails.\n- [ ] Connect Google Sheets on **Save Leads to Sheet**; header row: business, category, location, type, address, phone, website, email, rating, reviews, maps_url, place_id.\n- [ ] Edit **Settings**: `categories` (comma-separated) and `locations` (semicolon-separated).\n- [ ] Run once to fill the sheet.\n\n### Customization tips\nRaise `PAGES` in Build Search Queries for more leads. Hunter uses one search credit per lead \u2014 remove the Hunter + Attach Emails nodes if you don't need emails."
},
"typeVersion": 1
},
{
"id": "sticky_configure",
"name": "Section - Configure",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-112
],
"parameters": {
"color": 7,
"width": 448,
"height": 640,
"content": "## 1. Configure\nRun on demand or on a weekly schedule. Edit your categories and locations in **Settings** \u2014 nothing else needs touching."
},
"typeVersion": 1
},
{
"id": "sticky_scrape",
"name": "Section - Scrape & Extract",
"type": "n8n-nodes-base.stickyNote",
"position": [
432,
-112
],
"parameters": {
"color": 7,
"width": 668,
"height": 644,
"content": "## 2. Scrape & extract\nEach category + location becomes a live Google Maps search via SearchApi. Results from every page are flattened into one row per business and deduplicated by place."
},
"typeVersion": 1
},
{
"id": "sticky_save",
"name": "Section - Save",
"type": "n8n-nodes-base.stickyNote",
"position": [
1584,
-112
],
"parameters": {
"color": 7,
"width": 320,
"height": 644,
"content": "## 4. Save leads\nAppends every lead to your Google Sheet: name, type, address, phone, website, email, rating, reviews and a Maps link."
},
"typeVersion": 1
},
{
"id": "hunter",
"name": "Find Emails (Hunter)",
"type": "n8n-nodes-base.hunter",
"notes": "Looks up the emails on file for each business domain via Hunter.io Domain Search. Businesses with no website/domain pass through with no email. Uses Hunter credits: one search per lead.",
"onError": "continueRegularOutput",
"position": [
1184,
304
],
"parameters": {
"limit": 10,
"domain": "={{ $json.domain }}",
"filters": {},
"onlyEmails": false
},
"credentials": {
"hunterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "attach",
"name": "Attach Emails",
"type": "n8n-nodes-base.code",
"notes": "Attaches the best email (highest Hunter confidence) to each lead and passes the full row on to the sheet.",
"position": [
1408,
304
],
"parameters": {
"jsCode": "// Merge Hunter's result back onto each lead (paired by position with Extract\n// Leads). Pick the highest-confidence email Hunter returned for the domain.\nconst items = $input.all();\nconst leads = $('Extract Leads').all();\nconst out = [];\n\nfor (let i = 0; i < items.length; i++) {\n const lead = (leads[i] && leads[i].json) ? Object.assign({}, leads[i].json) : {};\n const data = items[i].json || {};\n const emails = Array.isArray(data.emails) ? data.emails : [];\n\n let best = '';\n if (emails.length) {\n const sorted = emails.slice().sort((a, b) => (b.confidence || 0) - (a.confidence || 0));\n best = sorted[0].value || '';\n }\n lead.email = best;\n out.push({ json: lead });\n}\n\nreturn out;"
},
"typeVersion": 2
},
{
"id": "sticky_enrich",
"name": "Section - Enrich",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
-112
],
"parameters": {
"color": 7,
"width": 420,
"height": 644,
"content": "## 3. Enrich with emails\nHunter.io looks up the emails on file for each business domain; the highest-confidence one is attached to the lead. Delete these two nodes to skip email enrichment."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1",
"saveManualExecutions": true,
"saveExecutionProgress": true,
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
},
"versionId": "0dcd7e60-6b02-4054-9439-9c183591950b",
"nodeGroups": [],
"connections": {
"Run Now": {
"main": [
[
{
"node": "Settings",
"type": "main",
"index": 0
}
]
]
},
"Settings": {
"main": [
[
{
"node": "Build Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Attach Emails": {
"main": [
[
{
"node": "Save Leads to Sheet",
"type": "main",
"index": 0
}
]
]
},
"Extract Leads": {
"main": [
[
{
"node": "Find Emails (Hunter)",
"type": "main",
"index": 0
}
]
]
},
"Schedule Weekly": {
"main": [
[
{
"node": "Settings",
"type": "main",
"index": 0
}
]
]
},
"Scrape Google Maps": {
"main": [
[
{
"node": "Extract Leads",
"type": "main",
"index": 0
}
]
]
},
"Build Search Queries": {
"main": [
[
{
"node": "Scrape Google Maps",
"type": "main",
"index": 0
}
]
]
},
"Find Emails (Hunter)": {
"main": [
[
{
"node": "Attach Emails",
"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.
googleSheetsOAuth2ApihunterApisearchApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs on demand or weekly to scrape Google Maps business listings via SearchApi.io, optionally enriches each business with an email using Hunter.io, and appends the resulting leads (name, contact details, ratings, and Maps link) to a Google Sheets spreadsheet. Runs…
Source: https://n8n.io/workflows/17670/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow reads LinkedIn profile URLs from Google Sheets, enriches each lead using PhantomBuster profile and company scrapers, finds a work email via Hunter.io with a Dropcontact fallback, writes
This workflow collects a business type and location via an n8n form, searches Google Maps results using SearchAPI, parses key business details, and appends the enriched leads to a Google Sheets spread
This n8n workflow automates the process of finding ecommerce seller leads, enriching them with product and business details, discovering company websites, and extracting contact information such as em
This template is for B2B sales teams, SDRs, growth marketers, and founders who maintain a spreadsheet of prospects and need verified contact details -- emails and mobile numbers -- without manual rese
This workflow finds local businesses from Google Maps and automatically enriches them with emails, social profiles, AI summaries, and personalized outreach messages — all saved to Google Sheets. Searc