AutomationFlowsMarketing & Ads › Lead Generation Flow

Lead Generation Flow

Lead Generation Flow. Uses googleSheets, @hasdata/n8n-nodes-hasdata. Event-driven trigger; 18 nodes.

Event trigger★★★★☆ complexity18 nodesGoogle Sheets@Hasdata/N8N Nodes Hasdata
Marketing & Ads Trigger: Event Nodes: 18 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "name": "Lead Generation Flow",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -2080,
        256
      ],
      "id": "f72f7271-3c3e-41ae-9fae-0593b87fc408",
      "name": "Manual Trigger",
      "notes": "Click to start the workflow manually"
    },
    {
      "parameters": {
        "operation": "create",
        "documentId": {
          "__rl": true,
          "value": "1nfFNvbdD1dyUNWxRzuWiTzAODSxH8j5B06z_YKvOFg4",
          "mode": "list",
          "cachedResultName": "Leads",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nfFNvbdD1dyUNWxRzuWiTzAODSxH8j5B06z_YKvOFg4/edit?usp=drivesdk"
        },
        "title": "={{ $now.toFormat(\"yyyy-MM-dd HH:mm\") }}",
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        -1872,
        256
      ],
      "id": "70ab6843-92be-4846-8b6b-0569fe090f4f",
      "name": "Create Results Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "Creates a new sheet with timestamp to store results"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "config",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        -1664,
        256
      ],
      "id": "21f6bec3-a62f-4216-9484-661b5f1ab6e2",
      "name": "Read Config Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "Reads search queries and parameters from config sheet"
    },
    {
      "parameters": {
        "jsCode": "// 1. Initialize an array to store all tasks\nlet allTasks = [];\n\n// 2. Constants\nconst RESULTS_PER_PAGE = 20;\n\n// 3. Loop through Google Sheets rows\nfor (const item of $input.all()) {\n  const config = item.json;\n  \n  // Validation: basic check for the query\n  if (!config.leads_query) continue;\n\n  const leads_query = config.leads_query;\n  const location = config.location || \"\"; \n  const pageCount = parseInt(config.page_count) || 1; \n  \n  // Get the starting page number from the table (default to 1 if empty)\n  const startPage = parseInt(config.offset) || 1;\n\n  // Generate a task for each requested page\n  for (let p = 0; p < pageCount; p++) {\n    const currentPageNumber = startPage + p;\n    \n    // Convert Page Number to API Position (Offset)\n    // Page 1 -> (1-1) * 20 = 0\n    // Page 2 -> (2-1) * 20 = 20\n    const apiPosition = (currentPageNumber - 1) * RESULTS_PER_PAGE;\n    \n    allTasks.push({\n      json: {\n        leads_query: leads_query,\n        ll: \"\",\n        location: location,\n        pageOffset: apiPosition, // This goes to the API\n        pageNumber: currentPageNumber, // Optional: for your own tracking\n        original_row: config.row_number || null \n      }\n    });\n  }\n}\n\n// 4. Return the tasks\nreturn allTasks;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1456,
        256
      ],
      "id": "5254f5c9-0c16-4ebf-b199-3717dc823084",
      "name": "Build Search Tasks",
      "notes": "Converts config into multiple search tasks with pagination (20 results per page)"
    },
    {
      "parameters": {
        "resource": "google_maps",
        "q": "={{ $json.leads_query }} {{ $json.location }}",
        "additionalFields": {
          "start": "={{ $json.pageOffset }}",
          "ll": "=@40.7455096,-74.0083012,14z"
        }
      },
      "type": "@hasdata/n8n-nodes-hasdata.hasData",
      "typeVersion": 1,
      "position": [
        -1248,
        256
      ],
      "id": "59a2cb00-5e6a-4e18-b133-40b5e499820c",
      "name": "Search Google Maps",
      "retryOnFail": true,
      "credentials": {
        "hasDataApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput",
      "notes": "Fetches business listings from Google Maps based on query and location"
    },
    {
      "parameters": {
        "fieldToSplitOut": "localResults",
        "include": "=",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        -1040,
        256
      ],
      "id": "502d0020-9115-4e94-94ed-0005af31664f",
      "name": "Split Results",
      "notes": "Separates each business into individual items for processing"
    },
    {
      "parameters": {
        "compare": "selectedFields",
        "fieldsToCompare": "localResults.title, localResults.kgmid",
        "options": {}
      },
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        -832,
        256
      ],
      "id": "93b5fee9-d26f-49c3-a7c0-8edce026ef3e",
      "name": "Remove Duplicates",
      "notes": "Removes duplicate businesses based on name and address"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "d2c86a6c-2c9d-472e-a328-71d92bdc74f2",
              "leftValue": "={{ $json.localResults.website }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -624,
        256
      ],
      "id": "6baaac69-71d4-440f-9aa3-2dec01bd4538",
      "name": "Has Website?",
      "notes": "Checks if business has a website - TRUE path scrapes it, FALSE path searches Google"
    },
    {
      "parameters": {
        "resource": "web_scraping",
        "url": "={{ $json.localResults.website }}",
        "additionalFields": {
          "extractEmails": true
        }
      },
      "type": "@hasdata/n8n-nodes-hasdata.hasData",
      "typeVersion": 1,
      "position": [
        -416,
        160
      ],
      "id": "394e8937-6a21-4036-a142-25fc3b79a81c",
      "name": "Scrape Website",
      "retryOnFail": true,
      "credentials": {
        "hasDataApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput",
      "notes": "Scrapes the business website to extract email addresses"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "5590ef2a-7d53-4637-afa0-10acdeb7dc41",
              "leftValue": "={{ $json.emails }}",
              "rightValue": "",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -208,
        96
      ],
      "id": "6885fbf9-0e3d-44ed-8fb4-584dfd17c201",
      "name": "Emails Found?",
      "notes": "Checks if emails were found on website - TRUE adds them, FALSE searches Google"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a574f281-57df-4ee8-9d56-945f141b5345",
              "name": "title",
              "value": "={{ $('Has Website?').item.json.localResults.title }}",
              "type": "string"
            },
            {
              "id": "f92a90cd-b7ea-4740-8d0e-739d18912fa4",
              "name": "phone",
              "value": "={{ $('Has Website?').item.json.localResults.phone }}",
              "type": "string"
            },
            {
              "id": "c6a92d26-418d-41f0-86d1-4fe67755bf94",
              "name": "address",
              "value": "={{ $('Has Website?').item.json.localResults.address }}",
              "type": "string"
            },
            {
              "id": "5a1e6e7c-559f-4419-9719-73de143ba76a",
              "name": "website",
              "value": "={{ $('Has Website?').item.json.localResults.website }}",
              "type": "string"
            },
            {
              "id": "3ebc4a23-0bb6-43a0-b8f1-804acc29793b",
              "name": "emails",
              "value": "={{ $json.emails.join(', ') }}",
              "type": "string"
            },
            {
              "id": "03dd5a32-cc01-4cab-872b-8d18a40797cc",
              "name": "rating",
              "value": "={{ $('Has Website?').item.json.localResults.rating }}",
              "type": "number"
            },
            {
              "id": "361d3f7a-bfd4-40a7-901c-13e8e895d1fa",
              "name": "reviews",
              "value": "={{ $('Has Website?').item.json.localResults.reviews }}",
              "type": "number"
            },
            {
              "id": "b1a783ca-5559-4e2d-8d28-5b98140cc89d",
              "name": "type",
              "value": "={{ $('Has Website?').item.json.localResults.types.join(', ') }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        0
      ],
      "id": "5a80f8c2-8169-4c06-9a14-b6cca319fc96",
      "name": "Format Lead with Emails",
      "notes": "Prepares lead data including scraped emails for output"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ad7fae25-b7e0-46ab-ad95-568ab9c6813a",
              "name": "localResults.title",
              "value": "={{ $('Has Website?').item.json.localResults.title }}",
              "type": "string"
            },
            {
              "id": "183dca66-d14a-4a63-9c14-30fffdaf4745",
              "name": "localResults.phone",
              "value": "={{ $('Has Website?').item.json.localResults.phone }}",
              "type": "string"
            },
            {
              "id": "d2791f5a-9e15-4d29-b1a7-488137c0deb8",
              "name": "localResults.address",
              "value": "={{ $('Has Website?').item.json.localResults.address }}",
              "type": "string"
            },
            {
              "id": "0d935b92-cb1e-4779-bd7e-3af1d2dd38ba",
              "name": "localResults.website",
              "value": "={{ $('Has Website?').item.json.localResults.website }}",
              "type": "string"
            },
            {
              "id": "faff970a-cb9d-4511-abb3-c97908cfdf59",
              "name": "rating",
              "value": "={{ $('Has Website?').item.json.localResults.rating }}",
              "type": "number"
            },
            {
              "id": "f4252269-c3b3-4ee2-a45c-458d3021123d",
              "name": "reviews",
              "value": "={{ $('Has Website?').item.json.localResults.reviews }}",
              "type": "number"
            },
            {
              "id": "4cc0b0f7-e726-4c69-8682-c87a75306efb",
              "name": "type",
              "value": "={{ $('Has Website?').item.json.localResults.types.join(', ') }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        160
      ],
      "id": "578e651f-d067-44be-860a-80473fc5510e",
      "name": "Format Lead without Emails",
      "notes": "Prepares lead data without emails (will search Google next)"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        -368,
        400
      ],
      "id": "1033c1b5-9a71-4049-99ef-302cacc5a748",
      "name": "Merge for Google Search",
      "notes": "Combines leads without website or without scraped emails for Google search"
    },
    {
      "parameters": {
        "q": "={{ $json.localResults.title }}, {{ $json.localResults.address.split(', ').slice(1).join(', ') }} email \"@\"",
        "additionalFields": {}
      },
      "type": "@hasdata/n8n-nodes-hasdata.hasData",
      "typeVersion": 1,
      "position": [
        -192,
        400
      ],
      "id": "493d6ab0-d7b0-4553-ac19-b0cb6a9a28b4",
      "name": "Search Google for Emails",
      "credentials": {
        "hasDataApi": {
          "name": "<your credential>"
        }
      },
      "notes": "Searches Google to find email addresses for businesses"
    },
    {
      "parameters": {
        "jsCode": "/**\n * Optimized lead processor for n8n.\n * Features: Ancestor matching for Merge1 data and advanced email cleanup.\n */\n\nlet finalResults = [];\n\n// Iterate through items coming from the previous node\nfor (let i = 0; i < $input.all().length; i++) {\n  const item = $input.all()[i];\n  const data = item.json;\n  const results = data.organicResults || [];\n\n  /**\n   * Safe data retrieval from Merge for Google Search.\n   * Using itemMatching(i) ensures we sync correctly with previous workflow steps.\n   */\n  let sourceData = {};\n  let fullData = {};\n  try {\n    const ancestor = $(\"Merge for Google Search\").itemMatching(i).json;\n    sourceData = ancestor.localResults || ancestor;\n    fullData = ancestor;\n  } catch (e) {\n    const fallback = $(\"Merge for Google Search\").all()[i]?.json;\n    sourceData = fallback?.localResults || fallback || {};\n    fullData = fallback;\n  }\n\n  let foundData = {\n    website: null,\n    emails: null,\n    title: sourceData.title || null,\n    phone: sourceData.phone || null,\n    address: sourceData.address || null,\n    rating: fullData.rating || sourceData.rating || null,\n    reviews: fullData.reviews || sourceData.reviews || null,\n    type: fullData.type || sourceData.type || null\n  };\n\n  /**\n   * REFINED EMAIL REGEX\n   * [a-z]{2,6} - Look for lowercase extensions only.\n   * (?![A-Z])  - Negative lookahead: stop if a Capital letter follows (like .Read).\n   */\n  const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-z]{2,6}(?![A-Z])/;\n\n  for (const res of results) {\n    const textToScan = (res.snippet || \"\") + \" \" + (res.title || \"\");\n    const match = textToScan.match(emailRegex);\n\n    if (match) {\n      let cleanedEmail = match[0];\n      \n      /**\n       * MANUAL ARTIFACT REMOVAL\n       * Cleans up cases where snippet text is glued to the email.\n       */\n      const artifacts = [\".Read\", \".View\", \".More\", \".Check\", \".Full\", \".Website\"];\n      for (const art of artifacts) {\n        if (cleanedEmail.endsWith(art)) {\n          cleanedEmail = cleanedEmail.slice(0, -art.length);\n        }\n      }\n\n      // Final trim for any trailing dots\n      cleanedEmail = cleanedEmail.replace(/\\.+$/, \"\"); \n\n      foundData.emails = cleanedEmail;\n      foundData.website = res.link;\n      \n      // Stop at the first valid email found in the results list\n      break; \n    }\n  }\n\n  finalResults.push({ json: foundData });\n}\n\nreturn finalResults;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        16,
        400
      ],
      "id": "5ea07ca7-9c06-4f56-bc32-45856a647af9",
      "name": "Extract Emails from Results",
      "notes": "Extracts email addresses from Google search results using regex"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        400,
        208
      ],
      "id": "da35c842-1741-4c17-be24-56567d96d3b7",
      "name": "Merge All Leads",
      "notes": "Combines all leads (with website emails and Google-found emails)"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Create Results Sheet').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Create Results Sheet').first().json.title }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [
            {
              "id": "title",
              "displayName": "title",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "address",
              "displayName": "address",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "website",
              "displayName": "website",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "emails",
              "displayName": "emails",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "rating",
              "displayName": "rating",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "reviews",
              "displayName": "reviews",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "type",
              "displayName": "type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        576,
        208
      ],
      "id": "d53997d8-d504-4475-a412-0ab59e57bf24",
      "name": "Save to Results Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "Writes all found leads to the timestamped results sheet"
    },
    {
      "parameters": {
        "content": "## Requirements\n\n- Google Sheets file **\"Leads\"**\n- Sheet **\"config\"** with fields:\n  - leads_query\n  - location\n  - page_count\n  - offset\n- [HasData API](https://hasdata.com/) credentials ",
        "height": 224,
        "width": 400
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2096,
        -64
      ],
      "typeVersion": 1,
      "id": "b95f73c0-42a7-483d-b662-315ebabf8ded",
      "name": "Sticky Note"
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Create Results Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Results Sheet": {
      "main": [
        [
          {
            "node": "Read Config Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Config Sheet": {
      "main": [
        [
          {
            "node": "Build Search Tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Search Tasks": {
      "main": [
        [
          {
            "node": "Search Google Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google Maps": {
      "main": [
        [
          {
            "node": "Split Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Results": {
      "main": [
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicates": {
      "main": [
        [
          {
            "node": "Has Website?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Website?": {
      "main": [
        [
          {
            "node": "Scrape Website",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge for Google Search",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Scrape Website": {
      "main": [
        [
          {
            "node": "Emails Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Emails Found?": {
      "main": [
        [
          {
            "node": "Format Lead with Emails",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Lead without Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Lead with Emails": {
      "main": [
        [
          {
            "node": "Merge All Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Lead without Emails": {
      "main": [
        [
          {
            "node": "Merge for Google Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge for Google Search": {
      "main": [
        [
          {
            "node": "Search Google for Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google for Emails": {
      "main": [
        [
          {
            "node": "Extract Emails from Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Emails from Results": {
      "main": [
        [
          {
            "node": "Merge All Leads",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge All Leads": {
      "main": [
        [
          {
            "node": "Save to Results Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "5e34e393-a5f4-4c7a-9235-04891b8f2a1d",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "QfqmMXhPJidm7tdH",
  "tags": []
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Lead Generation Flow. Uses googleSheets, @hasdata/n8n-nodes-hasdata. Event-driven trigger; 18 nodes.

Source: https://gist.github.com/valka465/c64f49e5933400a7f7a42ce2933ddb93 — original creator credit. Request a take-down →

More Marketing & Ads workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Marketing & Ads

This n8n template shows how to build a complete B2B lead generation pipeline that scrapes Google Maps for businesses and enriches them with emails from their websites or Google search.

Google Sheets, @Hasdata/N8N Nodes Hasdata
Marketing & Ads

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

Google Sheets, N8N Nodes Mrscraper, HTTP Request
Marketing & Ads

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

Google Sheets, HTTP Request
Marketing & Ads

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

HTTP Request, Google Sheets
Marketing & Ads

This workflow leverages n8n to perform automated Google Maps API queries and manage data efficiently in Google Sheets. It's designed to extract specific location data based on a given list of ZIP code

Execute Workflow Trigger, Stop And Error, HTTP Request +1