I have received a few emails asking how I made the poll on my home page. The poll is embedded directly in the page (it doesn’t redirect you to Google Forms), and it updates in real time. It can also be styled any way you want.
1. Make a Google Form
- Go to Google Forms
- Make a blank form, add a multiple-choice question (i.e. “How do you manage your blog?”)
- Optionally, set it as Required.
- Click Publish and set the form is set so Anyone with the link can respond
- Copy the responder link. The form’s
FORM_ID
will look like1FAIpQLSfExampleFormIDHere12345
. It is the long string between/e/
and/viewform
2. Get Pre-Filled Form
- In your form, click ⋮ (More) → Pre-fill form.
- Select any option and click Get link → COPY LINK.
- The pre-filled URL will look something like:
https://docs.google.com/forms/d/e/1FAIpQLSfExampleFormIDHere12345/viewform?usp=pp_url&entry.0123456789=This+is+an+option
- The pre-filled URL will look something like:
- Copy the part that looks like
entry.0123456789
. This is your entry ID
2. Link Responses to Google Sheets
- Back to your form (not pre-filled), click Responses → Link to Sheets. Link it to a new sheet.
- Copy the Sheet ID from the URL:
https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit
TheSPREADSHEET_ID
is the long string between/d/
and/edit
- Click Share and set the sheet is set so Anyone with the link can view
3. Basic HTML Form
<form id="pollForm">
<label><input type="radio" name="entry.0123456789" value="Option A">Option A Label</label><br>
<label><input type="radio" name="entry.0123456789" value="Option B">Option B Label</label><br>
<label><input type="radio" name="entry.0123456789" value="__other_option__" id="otherRadio">Other:</label>
<input type="text" id="otherInput" name="entry.0123456789.other_option_response"><br><br>
<button id="voteBtn" type="submit" disabled>Vote</button>
<button id="resultsBtn" type="button" disabled>View Results</button>
</form>
<section id="results"></section>
Replace entry.0123456789
with the actual entry ID you copied earlier. Make sure the Value matches exactly what you typed in your form (but the label text can be different if you wish).
- If you include an “Other” option, use the format above with a text input for custom responses.
- If not, you can just use regular radio buttons.
4. Javascript
document.addEventListener('DOMContentLoaded', function() {
// === CONFIGURATION ===
const FORM_ID = "YOUR_FORM_ID"; // Google Form ID (from the form's URL)
const SHEET_ID = "YOUR_SHEET_ID"; // Google Sheet ID (linked to form responses)
const STORAGE_KEY = `pollVoted_${FORM_ID}`; // LocalStorage key to track if this browser already voted
// === DOM ELEMENTS ===
const form = document.getElementById("pollForm");
const voteBtn = document.getElementById("voteBtn");
const resultsBtn = document.getElementById("resultsBtn");
const resultsSection = document.getElementById("results");
const otherRadio = document.getElementById("otherRadio");
const otherInput = document.getElementById("otherInput");
// === INITIAL STATE ===
// If this browser already voted, disable the vote button but allow viewing results
if (localStorage.getItem(STORAGE_KEY) === "true") {
voteBtn.disabled = true;
resultsBtn.disabled = false;
}
// === ENABLE VOTING WHEN USER SELECTS AN OPTION ===
form.querySelectorAll("input[name='entry.0123456789']").forEach(radio => {
radio.addEventListener("change", () => {
// Enable "Other" text input only if "Other" is selected
if (otherRadio.checked) {
otherInput.disabled = false;
} else {
otherInput.disabled = true;
otherInput.value = "";
}
// Enable vote button if user hasn’t already voted
if (!localStorage.getItem(STORAGE_KEY)) {
voteBtn.disabled = false;
}
});
});
// === HANDLE FORM SUBMISSION ===
form.addEventListener("submit", function(e) {
e.preventDefault(); // Prevent page reload
// Block duplicate votes
if (localStorage.getItem(STORAGE_KEY) === "true") {
alert("You’ve already voted from this browser.");
return;
}
// Require a selected option
const selected = form.querySelector("input[name='entry.0123456789']:checked");
if (!selected) {
alert("Please select an answer.");
return;
}
// Require text if "Other" is chosen
if (otherRadio.checked && !otherInput.value.trim()) {
alert("Please enter your custom answer.");
return;
}
// Prepare data to send
const formData = new FormData(form);
// Submit vote directly to the Google Form
fetch(`https://docs.google.com/forms/d/e/${FORM_ID}/formResponse`, {
method: "POST",
mode: "no-cors", // Required: avoids CORS issues but prevents reading response
body: formData
}).then(() => {
// Mark as voted in localStorage
localStorage.setItem(STORAGE_KEY, "true");
// Update UI
voteBtn.disabled = true;
resultsBtn.disabled = false;
resultsSection.innerHTML = "<p>Thanks for voting!</p>";
}).catch(() => {
alert("Error submitting vote. Please try again.");
});
});
// === FETCH AND DISPLAY RESULTS ===
resultsBtn.addEventListener("click", function() {
resultsSection.textContent = "Loading results...";
// Google Sheets "gviz" endpoint returns JSON wrapped in a function call
fetch(`https://docs.google.com/spreadsheets/d/${SHEET_ID}/gviz/tq?`)
.then(res => res.text())
.then(text => {
// Extract JSON from wrapper
const match = text.match(/google\.visualization\.Query\.setResponse\((.*)\);/s);
if (!match) throw new Error("Invalid response format");
const data = JSON.parse(match[1]);
// Each row is one form response
const rows = data.table.rows;
const counts = {};
// Count responses by answer
rows.forEach(row => {
const answer = row.c[0]?.v; // Column 0 contains the answer
if (answer) counts[answer] = (counts[answer] || 0) + 1;
});
// Render results
let html = "<h3>Results</h3><ul>";
for (const [answer, count] of Object.entries(counts)) {
html += `<li>${answer}: ${count}</li>`;
}
html += "</ul>";
resultsSection.innerHTML = html;
})
.catch(err => {
resultsSection.textContent = "Error loading results: " + err.message;
});
});
});
Replace all instances of entry.0123456789
with the actual entry ID you copied earlier, as well as the FORM_ID
and SHEET_ID
.
How It Works
- Votes are submitted directly to Google Forms through its hidden form endpoint
- Results are retrieved from the linked Google Sheet via the
gviz
API - Duplicate votes are blocked using
localStorage
(per browser/device)- Each new poll automatically uses a new storage key, since it’s based on the
FORM_ID
.
- Each new poll automatically uses a new storage key, since it’s based on the
That’s it! This is the basic skeleton of the code I use for my polls. It doesn’t include any styling, so you’ll need to handle that part yourself. I hope this helps!