Zero-dependency JavaScript library for capturing leads directly to Google Sheets
MIT License Zero Dependencies Vanilla JS No Backend
LeadGen.js is a lightweight, zero-dependency vanilla JavaScript library designed for frontend developers who need to capture form submissions and store lead data directly in Google Sheets without setting up any backend infrastructure.
Traditional lead capture solutions require a backend server, database, and API endpoints. LeadGen.js eliminates this complexity by using Google Sheets as a no-code backend. Simply include the script, configure your Google Sheets web app URL, and your forms will automatically submit data to your spreadsheet.
This guide walks you through setting up LeadGen.js from scratch. You will need a Google account and a basic HTML page.
Create an HTML file with a form. The form should have input fields with name attributes - these names will become column headers in your Google Sheet.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Lead Form</title>
</head>
<body>
<form id="my-lead-form">
<input type="text" name="firstName" placeholder="First Name" required>
<input type="text" name="lastName" placeholder="Last Name" required>
<input type="email" name="email" placeholder="Email Address" required>
<input type="tel" name="phone" placeholder="Phone Number">
<select name="interest">
<option value="product">Product Inquiry</option>
<option value="support">Support</option>
<option value="other">Other</option>
</select>
<textarea name="message" placeholder="Your message"></textarea>
<button type="submit">Submit</button>
</form>
<script src="https://cdn.jsdelivr.net/gh/NileGazer00/leadgen.js/leadgen.js"></script>
<script>
LeadGen.init({
formId: "my-lead-form",
sheetUrl: "YOUR_GOOGLE_APPS_SCRIPT_WEB_APP_URL",
theme: "light"
});
</script>
</body>
</html>
Follow the Google Sheets Setup Guide to create your Google Apps Script web app URL.
Replace YOUR_GOOGLE_APPS_SCRIPT_WEB_APP_URL with the URL you received from publishing your Google Apps Script. When a user submits the form, the data will be sent to your Google Sheet automatically.
Open your HTML page in a browser, fill out the form, and submit it. Check your Google Sheet - you should see a new row with the submitted data. Open the browser console (F12) to see debug logs from LeadGen.js.
LeadGen.js can be loaded in multiple ways depending on your project setup.
<script src="https://cdn.jsdelivr.net/gh/NileGazer00/leadgen.js/leadgen.js"></script>
<script src="/path/to/leadgen.js"></script>
<script type="module">
import { LeadGen } from "https://cdn.jsdelivr.net/gh/NileGazer00/leadgen.js/leadgen.module.js";
LeadGen.init({ formId: "my-form", sheetUrl: "YOUR_WEB_APP_URL" });
</script>
npm install leadgen-js
// CommonJS
const { LeadGen } = require("leadgen-js");
// ES Modules
import { LeadGen } from "leadgen-js";
This step-by-step guide explains how to set up Google Sheets to receive data from LeadGen.js using a Google Apps Script web app.
| firstName | lastName | email | phone | interest | message | timestamp |function doPost(e) {
try {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = JSON.parse(e.postData.contents);
data.timestamp = new Date().toISOString();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
if (headers.indexOf("timestamp") === -1) {
sheet.appendRow(["timestamp"]);
headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
}
var rowData = headers.map(function(header) {
return data[header] || "";
});
sheet.appendRow(rowData);
return ContentService
.createTextOutput(JSON.stringify({ status: "success", message: "Lead captured successfully" }))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService
.createTextOutput(JSON.stringify({ status: "error", message: error.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e) {
return ContentService
.createTextOutput(JSON.stringify({ status: "active", message: "LeadGen.js web app is running" }))
.setMimeType(ContentService.MimeType.JSON);
}
Open the web app URL in your browser. You should see:
{"status":"active","message":"LeadGen.js web app is running"}
If you see this, your web app is working correctly.
Initializes LeadGen.js on a form element. This is the main entry point for the library. Call this method after the DOM is loaded and after including the LeadGen.js script.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| config | Object | Yes | - | Configuration object |
| config.formId | String | Yes | null | The ID of the HTML form element to attach LeadGen to |
| config.sheetUrl | String | Yes | null | The Google Apps Script web app URL |
| config.theme | String | No | "light" | Theme for form styling: "light", "dark", or "none" |
| config.validate | Boolean | No | true | Enable built-in form validation |
| config.debug | Boolean | No | false | Enable console debug logging |
| config.onSuccess | Function | No | null | Callback function fired after successful submission |
| config.onError | Function | No | null | Callback function fired when submission fails |
| config.customHeaders | Object | No | {} | Additional HTTP headers sent with each request |
void
LeadGen.init({
formId: "contact-form",
sheetUrl: "https://script.google.com/macros/s/YOUR_ID/exec",
theme: "dark",
validate: true,
debug: true,
onSuccess: function(response) {
console.log("Lead captured:", response);
alert("Thank you! We will be in touch.");
},
onError: function(error) {
console.error("Submission failed:", error);
alert("Something went wrong. Please try again.");
}
});
Calculates the number of leads needed to reach a revenue target based on an average customer value. This is useful for marketing planning and setting lead generation goals.
| Parameter | Type | Required | Description |
|---|---|---|---|
| target | Number | Yes | The revenue target in dollars (e.g., 21000) |
| avg | Number | Yes | The average revenue per customer in dollars (e.g., 499) |
Number - The number of leads needed (rounded up to the nearest integer)
var leads = LeadGen.calculateNeeded(21000, 499);
console.log(leads); // Output: 43
var subs = LeadGen.calculateNeeded(5000, 99);
console.log(subs); // Output: 51
Changes the form theme at runtime without re-initializing. Useful for implementing dark mode toggles.
| Parameter | Type | Required | Description |
|---|---|---|---|
| theme | String | Yes | "light", "dark", or "none" (removes all LeadGen styling) |
void
document.getElementById("dark-toggle").addEventListener("click", function() {
LeadGen.setTheme("dark");
});
document.getElementById("light-toggle").addEventListener("click", function() {
LeadGen.setTheme("light");
});
Returns analytics data about form submissions stored in the current session. Data is stored in sessionStorage and resets when the browser session ends.
Object - An analytics object with the following properties:
| Property | Type | Description |
|---|---|---|
| totalAttempts | Number | Total number of form submission attempts |
| successful | Number | Number of successful submissions |
| failed | Number | Number of failed submissions |
| validationErrors | Number | Number of submissions blocked by validation |
| conversionRate | String | Percentage of successful submissions (e.g., "85.7%") |
var stats = LeadGen.getAnalytics();
console.log("Conversion rate:", stats.conversionRate);
console.log("Successful:", stats.successful);
console.log("Failed:", stats.failed);
Removes all LeadGen event listeners and resets the instance. Useful for single-page applications where forms are dynamically added and removed from the DOM.
void
LeadGen.destroy();
document.getElementById("my-form").remove();
LeadGen.init({ formId: "my-form", sheetUrl: url });
Manually triggers form validation without submitting. Returns validation results so you can display custom error messages.
Object - { valid: Boolean, errors: String[] }
var result = LeadGen.validateForm("my-form");
if (!result.valid) {
console.log("Invalid fields:", result.errors);
result.errors.forEach(function(err) {
console.warn(err);
});
}
<form id="basic-form">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send</button>
</form>
<script src="https://cdn.jsdelivr.net/gh/NileGazer00/leadgen.js/leadgen.js"></script>
<script>
LeadGen.init({
formId: "basic-form",
sheetUrl: "https://script.google.com/macros/s/YOUR_ID/exec"
});
</script>
LeadGen.init({
formId: "signup-form",
sheetUrl: "https://script.google.com/macros/s/YOUR_ID/exec",
validate: true,
debug: true,
onSuccess: function(response) {
document.getElementById("signup-form").style.display = "none";
document.getElementById("success-msg").style.display = "block";
},
onError: function(error) {
document.getElementById("error-msg").textContent = "Error: " + error.message;
document.getElementById("error-msg").style.display = "block";
}
});
import React, { useEffect, useRef } from "react";
function LeadForm() {
const formRef = useRef(null);
useEffect(function() {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/gh/NileGazer00/leadgen.js/leadgen.js";
script.onload = function() {
LeadGen.init({
formId: "react-lead-form",
sheetUrl: "https://script.google.com/macros/s/YOUR_ID/exec",
validate: true,
onSuccess: function() { alert("Lead captured!"); }
});
};
document.body.appendChild(script);
return function() {
LeadGen.destroy();
document.body.removeChild(script);
};
}, []);
return (
<form id="react-lead-form" ref={formRef}>
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<button type="submit">Submit</button>
</form>
);
}
<template>
<form id="vue-lead-form">
<input type="text" name="name" v-model="form.name" placeholder="Name">
<input type="email" name="email" v-model="form.email" placeholder="Email">
<button type="submit">Submit</button>
</form>
</template>
<script>
export default {
data() { return { form: { name: "", email: "" } }; },
mounted() {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/gh/NileGazer00/leadgen.js/leadgen.js";
script.onload = () => {
LeadGen.init({
formId: "vue-lead-form",
sheetUrl: "https://script.google.com/macros/s/YOUR_ID/exec"
});
};
document.head.appendChild(script);
},
beforeUnmount() { LeadGen.destroy(); }
};
</script>
<div id="step-1" class="form-step">
<input type="text" name="firstName" placeholder="First Name" required>
<input type="text" name="lastName" placeholder="Last Name" required>
<button type="button" onclick="nextStep()">Next</button>
</div>
<div id="step-2" class="form-step" style="display:none;">
<input type="email" name="email" placeholder="Email" required>
<input type="tel" name="phone" placeholder="Phone">
<button type="button" onclick="prevStep()">Back</button>
<button type="button" onclick="submitForm()">Submit</button>
</div>
<script>
function nextStep() {
document.getElementById("step-1").style.display = "none";
document.getElementById("step-2").style.display = "block";
}
function prevStep() {
document.getElementById("step-2").style.display = "none";
document.getElementById("step-1").style.display = "block";
}
function submitForm() {
var data = {
firstName: document.querySelector('[name="firstName"]').value,
lastName: document.querySelector('[name="lastName"]').value,
email: document.querySelector('[name="email"]').value,
phone: document.querySelector('[name="phone"]').value
};
LeadGen.sendData(data);
}
</script>
<button id="toggle-theme">Toggle Dark Mode</button>
<script>
document.getElementById("toggle-theme").addEventListener("click", function() {
var current = document.body.getAttribute("data-theme");
if (current === "dark") {
document.body.setAttribute("data-theme", "light");
LeadGen.setTheme("light");
} else {
document.body.setAttribute("data-theme", "dark");
LeadGen.setTheme("dark");
}
});
</script>
Full reference of all configuration options available in LeadGen.init().
| Option | Type | Default | Description |
|---|---|---|---|
| formId | String | null | ID of the form element to attach to |
| sheetUrl | String | null | Google Apps Script web app URL |
| theme | String | "light" | "light", "dark", or "none" |
| validate | Boolean | true | Enable HTML5 form validation |
| debug | Boolean | false | Log all operations to console |
| onSuccess | Function | null | Success callback: function(response) |
| onError | Function | null | Error callback: function(error) |
| customHeaders | Object | {} | Extra HTTP headers for requests |
| timeout | Number | 10000 | Request timeout in milliseconds |
| redirectUrl | String | null | URL to redirect to after successful submission |
| resetForm | Boolean | true | Reset form fields after successful submission |
| disableOnSubmit | Boolean | true | Disable submit button while request is in progress |
Cause: The Google Apps Script web app URL is incorrect or the script has an error.
Fix:
Cause: Google Apps Script blocks cross-origin requests when not properly deployed.
Fix:
Cause: Column headers in the sheet do not match form field names.
Fix:
Cause: LeadGen.js is not intercepting the form submit event.
Fix:
document.addEventListener("DOMContentLoaded", function() {
LeadGen.init({ formId: "my-form", sheetUrl: "YOUR_URL" });
});
Cause: The web app is not set to allow anonymous access.
Fix: Re-deploy with "Who has access" set to "Anyone" - see Step 4 in the Google Sheets Setup Guide.
Cause: HTML5 validation attributes (required, pattern, type) are too strict.
Fix: Either adjust your HTML attributes or disable built-in validation:
LeadGen.init({ formId: "my-form", sheetUrl: "YOUR_URL", validate: false });
Yes. LeadGen.js is released under the MIT License, which means it is free for personal and commercial use. You can use it in any project without paying fees or royalties.
Yes. LeadGen.js is framework-agnostic because it operates on standard HTML form elements. See the Examples section for React and Vue integration code.
Your Google Sheet remains private. The Google Apps Script runs under your Google account permissions. External users can only send data to your sheet through the web app endpoint - they cannot read or modify existing data.
Yes. LeadGen.js is designed specifically for static hosting platforms like GitHub Pages, Netlify, Vercel, and Cloudflare Pages. No server-side code is needed.
Google Apps Script has a 99.9% uptime SLA. If it does go down, form submissions will fail and the onError callback will fire. You can implement a fallback such as showing a message asking users to email you directly.
Google Apps Script has a quota of 20,000 URL fetch requests per day for free accounts. For most lead generation forms, this is more than sufficient. Google Workspace accounts have higher limits.
Yes. Initialize multiple instances with different form IDs and sheet URLs:
LeadGen.init({
formId: "contact-form",
sheetUrl: "https://script.google.com/macros/s/ID_CONTACT/exec"
});
LeadGen.init({
formId: "newsletter-form",
sheetUrl: "https://script.google.com/macros/s/ID_NEWSLETTER/exec"
});
Yes. Set theme: "none" to disable all LeadGen styling and apply your own CSS. The library only modifies the form element you specify and does not affect the rest of your page.
No. LeadGen.js is designed for text-based form data. File uploads require a backend server for processing and storage.
<input type="text" name="website" style="display:none;" tabindex="-1" autocomplete="off">
<script>
LeadGen.init({
formId: "my-form",
sheetUrl: "YOUR_URL",
onSuccess: function(response) {
var honeypot = document.querySelector('[name="website"]').value;
if (honeypot) {
console.warn("Spam detected - honeypot field was filled");
return;
}
}
});
</script>
Contributions to LeadGen.js are welcome. This is an open-source project and we appreciate all help from the JavaScript community.
If you find a bug, please open a GitHub issue with:
By contributing to LeadGen.js, you agree that your contributions will be licensed under the MIT License.