Lately, I created an HTML form where the results are sent to a Google spreadsheet. I used it for analytics purposes, but it's useful even just for keeping track of the form's results. After trying all kinds of solutions and even searching beyond the 2nd page on Google, I found something that worked for me and I thought worth sharing.
Prerequisites: HTML and basic JavaScript knowledge.
The credit for this solution goes to Jamie Wilson who created and uploaded it to Github.
First, Create the Spreadsheet
- Go to Google Sheets and
Start a new spreadsheetwith theBlanktemplate. Disclaimer: It doesn't really matter how you name the file. - On the first row, write timestamp on the first column and email on the second column.
Create a Google Apps Script
- Click on
Tools > Script Editor…which should open a new tab. - Name the script
Submit Form to Google Sheetsor however you want for you to remember what the script does. - Delete the
function myFunction() {}block withing theCode.gstab. - Paste the following script in its place and
File > Save:
var sheetName = 'Sheet1'
var scriptProp = PropertiesService.getScriptProperties()
function intialSetup () {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
scriptProp.setProperty('key', activeSpreadsheet.getId())
}
function doPost (e) {
var lock = LockService.getScriptLock()
lock.tryLock(10000)
try {
var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
var sheet = doc.getSheetByName(sheetName)
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
var nextRow = sheet.getLastRow() + 1
var newRow = headers.map(function(header) {
return header === 'timestamp' ? new Date() : e.parameter[header]
})
sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'success', 'row': nextRow }))
.setMimeType(ContentService.MimeType.JSON)
}
catch (e) {
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
.setMimeType(ContentService.MimeType.JSON)
}
finally {
lock.releaseLock()
}
}
If you want to better understand what this script is doing, check out the
form-script-commented.jsfile in the repo for a detailed explanation.
Run the setup function
- Now, go to
Run > Run Function > initialSetupto run this function. - In the
Authorization Requireddialog, click onReview Permissions. - Sign in or pick the Google account associated with this project.
- You should see a dialog that says
Hi {Your Name}, Submit Form to Google Sheets wants to... - Then click
Allow
Add a new project trigger
- Click on
Edit > Current project’s triggers. - In the dialog click
No triggers set up. Click here to add one now. - In the dropdowns select
doPost - Set the events fields to
From spreadsheetandOn form submit - Then click
Save
Publish the project as a web app
- Click on
Publish > Deploy as web app…. - Set
Project VersiontoNewand putinitial versionin the input field below. - Leave
Execute the app as:set toMe(your@address.com). - For
Who has access to the app:selectAnyone, even anonymous. - Click
Deploy. - In the popup, copy the
Current web app URLfrom the dialog. - And click
OK.
IMPORTANT! If you have a custom domain with Gmail, you might need to click OK, refresh the page, and then go to
Publish > Deploy as web app…again to get the proper web app URL. It should look something likehttps://script.google.com/a/yourdomain.com/macros/s/XXXX….
Input your web app URL
Open the file named index.html. On line 7 replace <SCRIPT URL> with your script url:
<form name="submit-to-google-sheet">
<input name="email" type="email" placeholder="Email" required>
<button type="submit">Send</button>
</form>
<script>
const scriptURL = '<SCRIPT URL>'
const form = document.forms['submit-to-google-sheet']
form.addEventListener('submit', e => {
e.preventDefault()
fetch(scriptURL, { method: 'POST', body: new FormData(form)})
.then(response => console.log('Success!', response))
.catch(error => console.error('Error!', error.message))
})
</script>
As you can see, this script uses the Fetch API, a fairly new promise-based mechanism for making web requests. It makes a "POST" request to your script URL and uses FormData to pass in our data as URL parameters.
Adding additional form data
To capture additional data, you'll just need to create new columns with titles matching exactly the name values from your form inputs. For example, if you want to add first and last name inputs, you'd give them name values like so:
<form name="submit-to-google-sheet">
<input name="email" type="email" placeholder="Email" required>
<input name="firstName" type="text" placeholder="First Name">
<input name="lastName" type="text" placeholder="Last Name">
<button type="submit">Send</button>
</form>
Then, you can create new headers with the exact, case-sensitive name values. Just go to the spreadsheet and add the names to the first row, each name in a different column.
Related Polyfills
Some of this stuff is not yet fully supported by browsers or doesn't work on older ones. Here are some polyfill options to use for better support.
Since the FormData polyfill is published as a Node package and needs to be compiled for browsers to work with, a good option for including these is using Browserify's CDN called wzrd.in. This service compiles, minifies and serves the latest version of these scripts for us.
You'll want to make sure these load before the main script handling the form submission. e.g.:
<script src="https://wzrd.in/standalone/formdata-polyfill"></script>
<script src="https://wzrd.in/standalone/promise-polyfill@latest"></script>
<script src="https://wzrd.in/standalone/whatwg-fetch@latest"></script>
<script>
const scriptURL = '<SCRIPT URL>'
const form = document.forms['submit-to-google-sheet']
...
</script>
And... Yep, this is it.
After trying several different methods to do this "simple" action, that's the only one that worked for me - so I hope it helps. Feel free to share it/save it for future use and feel free to comment in case you have ideas to improve it!
Latest comments (45)
What if I just want to push certain inputs not all?
Hi Omer.
Great job.
I have a task to stack data on different sheets. Could you please tell me how to modify the script to pass the sheetName value from the site?
Hi Omer
Nice work
I have the task of stacking data on different sheets. Could you please tell me how to modify the script to pass the "sheetName" value from the site?
You commented the same thing on Dec 17 '21 and on March 26.
How is this helpful? :)
Hi Omer. I get error TypeError: Cannot read property 'getId' of null
Please tell me what is causing this error. Thnks!
drive.google.com/file/d/1bIhcizg8C...
I solved it with ChatGPT. Replace the beginning code to:
var sheetId = 'YOUR_SPREADSHEET_ID'; // Replace 'YOUR_SPREADSHEET_ID' with the actual ID of your Google Sheet
var sheetName = 'Sheet1'; // Replace 'Sheet1' with the name of your sheet if it's different
function doPost(e) {
var lock = LockService.getScriptLock();
lock.tryLock(10000);
try {
var doc = SpreadsheetApp.openById(sheetId); // Open the spreadsheet by ID
var sheet = doc.getSheetByName(sheetName);
THE REST OF THE CODE IS THE SAME
So the difference here is that you're putting the exact Google Sheet's ID
Same! Did you solve it?
It's true, FabForm looks like a great solution. (someone mentioned it as well last December: dev.to/fabform/comment/1kha4 )
The thing is I wrote this article two years ago, on January 2020. I looked for this kind of a solution for 7 (!!!) pages on google :)
Couldn't find any other solution than this one that was suggested by Jamie Wilson . If other, easier solutions came to the market since then - I'm truly happy because finding and implement this solution was a bit of a mess. [You can see it by the amount of tech support I had to give here in the comments section :) ]
Hi Omer, I follow the steps I getting email message on this line --> form.addEventListener('submit', e => { saying that TypeError: Cannot read property 'addEventListener' of undefined I just copy what you told me.
What about security? anybody can be a fishing attack on spreadhseet. I can send tons of request by fetch method to overflow the spreadhseet. Is there any solution for this security threate?
Hi Omer! Thank you so much for this post. As a high schooler, your tutorial is much easier to follow than others.
I am sending data from a js file (without an HTML form) to my google sheet and I do get a "Success! {}" message in the console and the timestamp updates, but the rest of the columns on my sheet never update with data. I have checked to make sure my column headers and code match up and are case sensitive.
My code on Google Apps Script is the exact same as yours, and this is how I am sending data from my js file:
Could you please help me out?
Solved it! This link explains the solution: github.com/jamiewilson/form-to-goo...
But, one small change from the link above is not putting quotes in FormData() because it signifies a string, and the workaround should be to create a null HTML Form element.
So use:
var sendingData = new FormData()Instead of:
var sendingData = new FormData('')Solution code for js file:
Some comments may only be visible to logged-in visitors. Sign in to view all comments.