This workflow corresponds to n8n.io template #16997 — we link there as the canonical source.
This workflow follows the Form Trigger → Google Sheets recipe pattern — see all workflows that pair these two integrations.
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 →
{
"meta": {
"templateCredsSetupCompleted": false
},
"name": "Find nearby businesses by proximity using OpenStreetMap and save to Google Sheets",
"tags": [],
"nodes": [
{
"id": "92684d46-54bb-4c5d-825a-36ca433269f7",
"name": "Search request",
"type": "n8n-nodes-base.formTrigger",
"position": [
128,
368
],
"parameters": {
"options": {
"appendAttribution": false
},
"formTitle": "Local business discovery by proximity",
"formFields": {
"values": [
{
"fieldLabel": "Location",
"placeholder": "City or address, e.g. Kingston, Ontario",
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "Business category",
"fieldOptions": {
"values": [
{
"option": "Restaurants"
},
{
"option": "Cafes & coffee shops"
},
{
"option": "Bars & pubs"
},
{
"option": "Hair & beauty salons"
},
{
"option": "Gyms & fitness"
},
{
"option": "Auto repair shops"
},
{
"option": "Dentists"
},
{
"option": "Florists"
},
{
"option": "Real estate agents"
},
{
"option": "Retail shops (any)"
}
]
},
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "Search radius",
"defaultValue": "2000",
"fieldOptions": {
"values": [
{
"option": "500"
},
{
"option": "1000"
},
{
"option": "2000"
},
{
"option": "5000"
}
]
},
"requiredField": true
},
{
"fieldType": "number",
"fieldLabel": "Max results",
"defaultValue": "50",
"requiredField": true
}
]
},
"formDescription": "Find businesses near a location and record which ones list a website."
},
"typeVersion": 2.6
},
{
"id": "e0691771-8dcf-4c58-b417-48fdce42605b",
"name": "Settings",
"type": "n8n-nodes-base.set",
"position": [
304,
368
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a1",
"name": "nominatimUrl",
"type": "string",
"value": "https://nominatim.openstreetmap.org"
},
{
"id": "a2",
"name": "overpassUrl",
"type": "string",
"value": "https://overpass-api.de/api/interpreter"
},
{
"id": "a3",
"name": "userAgent",
"type": "string",
"value": "n8n local business discovery (contact: user@example.com)"
},
{
"id": "a4",
"name": "searchLocation",
"type": "string",
"value": "={{ $json.Location }}"
},
{
"id": "a5",
"name": "category",
"type": "string",
"value": "={{ $json['Business category'] }}"
},
{
"id": "a6",
"name": "radiusMeters",
"type": "string",
"value": "={{ $json['Search radius'] }}"
},
{
"id": "a7",
"name": "maxResults",
"type": "string",
"value": "={{ $json['Max results'] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5ea35547-f898-4909-9373-b0a87da52ce7",
"name": "Geocode location (Nominatim)",
"type": "n8n-nodes-base.httpRequest",
"position": [
496,
368
],
"parameters": {
"url": "={{ $json.nominatimUrl }}/search",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "={{ $json.searchLocation }}"
},
{
"name": "format",
"value": "json"
},
{
"name": "limit",
"value": "1"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "={{ $json.userAgent }}"
}
]
}
},
"typeVersion": 4.4,
"alwaysOutputData": true
},
{
"id": "16718154-fe0f-4738-9137-a3b1fda36c4a",
"name": "Location found?",
"type": "n8n-nodes-base.if",
"position": [
656,
368
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $json.lat }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "dcc593c0-4a7a-465a-8ddd-500d433c3bb9",
"name": "Build Overpass query",
"type": "n8n-nodes-base.code",
"position": [
848,
352
],
"parameters": {
"jsCode": "const settings = $('Settings').first().json;\nconst lat = $json.lat;\nconst lon = $json.lon;\nconst radius = Number(settings.radiusMeters) || 2000;\nconst category = settings.category;\n\nconst tagMap = {\n 'Restaurants': '[\"amenity\"=\"restaurant\"]',\n 'Cafes & coffee shops': '[\"amenity\"=\"cafe\"]',\n 'Bars & pubs': '[\"amenity\"~\"bar|pub\"]',\n 'Hair & beauty salons': '[\"shop\"~\"hairdresser|beauty\"]',\n 'Gyms & fitness': '[\"leisure\"=\"fitness_centre\"]',\n 'Auto repair shops': '[\"shop\"=\"car_repair\"]',\n 'Dentists': '[\"amenity\"=\"dentist\"]',\n 'Florists': '[\"shop\"=\"florist\"]',\n 'Real estate agents': '[\"office\"=\"estate_agent\"]',\n 'Retail shops (any)': '[\"shop\"]'\n};\nconst tag = tagMap[category] || '[\"shop\"]';\n\nconst query = '[out:json][timeout:25];\\n(\\n node' + tag + '(around:' + radius + ',' + lat + ',' + lon + ');\\n way' + tag + '(around:' + radius + ',' + lat + ',' + lon + ');\\n);\\nout center tags;';\n\nreturn [{ json: { overpassQuery: query, overpassUrl: settings.overpassUrl, userAgent: settings.userAgent } }];"
},
"typeVersion": 2
},
{
"id": "372cc414-99d5-4578-a12d-31c129824914",
"name": "Find businesses (Overpass)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1008,
352
],
"parameters": {
"url": "={{ $json.overpassUrl }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "data",
"value": "={{ $json.overpassQuery }}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "={{ $json.userAgent }}"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "366c09a2-1377-49db-a927-ee4d7a5bfa3c",
"name": "Normalize businesses",
"type": "n8n-nodes-base.code",
"position": [
1200,
368
],
"parameters": {
"jsCode": "const settings = $('Settings').first().json;\nconst elements = $json.elements || [];\nconst max = Number(settings.maxResults) || 50;\n\nconst results = [];\nfor (const el of elements) {\n const t = el.tags || {};\n const website = t.website || t['contact:website'] || t.url || '';\n const lat = el.lat != null ? el.lat : (el.center && el.center.lat);\n const lon = el.lon != null ? el.lon : (el.center && el.center.lon);\n const addressParts = [t['addr:housenumber'], t['addr:street'], t['addr:city'], t['addr:postcode']].filter(Boolean);\n results.push({ json: {\n name: t.name || '(unnamed)',\n category: settings.category,\n address: addressParts.join(', '),\n phone: t.phone || t['contact:phone'] || '',\n website: website,\n website_on_file: website ? 'yes' : 'no',\n opening_hours: t.opening_hours || '',\n latitude: lat != null ? lat : '',\n longitude: lon != null ? lon : '',\n osm_link: 'https://www.openstreetmap.org/' + el.type + '/' + el.id,\n search_location: settings.searchLocation,\n search_radius_m: settings.radiusMeters,\n retrieved_at: new Date().toISOString()\n } });\n}\nreturn results.slice(0, max);"
},
"typeVersion": 2
},
{
"id": "22a86f1f-ca3c-42f1-ad99-6ff233698477",
"name": "Append to results sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1392,
368
],
"parameters": {
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.7
},
{
"id": "c613e46b-e687-4020-bf98-a3ea320bbc42",
"name": "No matching location",
"type": "n8n-nodes-base.noOp",
"position": [
976,
736
],
"parameters": {},
"typeVersion": 1
},
{
"id": "8f36ca5d-9d18-4173-9867-1d9e6c75003d",
"name": "Sticky Note b4c0bf39",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
48
],
"parameters": {
"width": 460,
"height": 796,
"content": "## Find nearby businesses by proximity using OpenStreetMap and save to Google Sheets\n\nLook up businesses around any location and record which ones list a website. Useful for local market research, building area directories, and territory mapping.\n\n### How it works\n1. Submit the form with a location, business category, search radius, and result limit.\n2. Nominatim geocodes the location to coordinates (1 request).\n3. A single Overpass API call returns every matching business in the radius, with their OpenStreetMap tags (website, phone, hours).\n4. Each business is normalized and appended to your Google Sheet, with a `website_on_file` yes/no flag.\n\n### Setup steps\n- [ ] Connect a Google Sheets credential and pick a destination sheet on \"Append to results sheet\".\n- [ ] Open the Settings node and put a real contact address in the User-Agent value (OpenStreetMap policy requires it).\n\n### Customization\n- Edit the category to OpenStreetMap tag map in **Build Overpass query**.\n- Adjust the radius options or add columns in **Normalize businesses**.\n- Filter your sheet to `website_on_file = no` to focus your research."
},
"typeVersion": 1
},
{
"id": "5c532087-d014-4b0f-b266-b55102ad4f87",
"name": "Sticky Note fd72b4dc",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
192
],
"parameters": {
"color": 7,
"width": 372,
"height": 348,
"content": "## 1. Search input\nThe form collects location, category, radius and limit. Settings holds editable endpoints and your contact User-Agent."
},
"typeVersion": 1
},
{
"id": "857a7e1f-28ad-4754-9da8-6b52d8c5bb4a",
"name": "Sticky Note bd8080c9",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
192
],
"parameters": {
"color": 7,
"width": 668,
"height": 360,
"content": "## 2. Geocode + find businesses\nNominatim turns the location into coordinates; one Overpass call returns all matching businesses nearby."
},
"typeVersion": 1
},
{
"id": "0174eca2-a1d6-47fc-a403-6df1010df424",
"name": "Sticky Note 164f5c83",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
192
],
"parameters": {
"color": 7,
"width": 440,
"height": 360,
"content": "## 3. Save to Google Sheets\nEach business becomes a row, flagged by whether it lists a website."
},
"typeVersion": 1
},
{
"id": "4bb650a8-f65e-4f2d-8c56-65f43ea8025d",
"name": "Sticky Note 35a4b80b",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
560
],
"parameters": {
"color": 3,
"width": 396,
"height": 196,
"content": "## Respect the OpenStreetMap usage policy\nSet a real contact in the Settings User-Agent and keep volume modest (about 1 request per second). A missing website tag means \"worth checking\", not \"no site exists\"."
},
"typeVersion": 1
},
{
"id": "b7f4a210-3c9d-4e61-9a52-1d0e7c884411",
"name": "Sticky Note nomatch",
"type": "n8n-nodes-base.stickyNote",
"position": [
864,
560
],
"parameters": {
"color": 7,
"width": 300,
"height": 318,
"content": "## Handle no match\nIf the location cannot be geocoded, the IF false branch stops here, so no bad coordinates are queried."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Settings": {
"main": [
[
{
"node": "Geocode location (Nominatim)",
"type": "main",
"index": 0
}
]
]
},
"Search request": {
"main": [
[
{
"node": "Settings",
"type": "main",
"index": 0
}
]
]
},
"Location found?": {
"main": [
[
{
"node": "Build Overpass query",
"type": "main",
"index": 0
}
],
[
{
"node": "No matching location",
"type": "main",
"index": 0
}
]
]
},
"Build Overpass query": {
"main": [
[
{
"node": "Find businesses (Overpass)",
"type": "main",
"index": 0
}
]
]
},
"Normalize businesses": {
"main": [
[
{
"node": "Append to results sheet",
"type": "main",
"index": 0
}
]
]
},
"Find businesses (Overpass)": {
"main": [
[
{
"node": "Normalize businesses",
"type": "main",
"index": 0
}
]
]
},
"Geocode location (Nominatim)": {
"main": [
[
{
"node": "Location found?",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects a location and business search criteria via an n8n form, geocodes the location with OpenStreetMap Nominatim, finds nearby matching businesses using the Overpass API, and appends normalized results (including website availability) to a Google Sheets…
Source: https://n8n.io/workflows/16997/ — 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 audits a list of URLs from a CSV upload or an XML sitemap using the Google PageSpeed Insights API, writes Lighthouse and Core Web Vitals metrics into a new Google Sheets report, and emai
Submit any YouTube, Vimeo, or Zoom webinar URL using a simple form and the workflow handles everything from there. It runs a two-phase pipeline: first identifying the top viral moments in your video w
Stop chasing blurry receipts and manually typing expense data. This workflow creates an intelligent, "snap-and-submit" reimbursement pipeline that hosts photos via UploadToURL, extracts deep data via
Atlas Opco Import. Uses github, stopAndError, formTrigger, googleSheets. Event-driven trigger; 22 nodes.
Shopify and E-Commerce store owners often struggle to create engaging 3D videos from static product images. This workflow automates that entire process—from image upload to video delivery—so store own