DEV Community

Cover image for Submit a Form to a Google Spreadsheet
Omer Lahav
Omer Lahav

Posted on • Updated on

Submit a Form to a Google Spreadsheet

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

  1. Go to Google Sheets and Start a new spreadsheet with the Blank template. Disclaimer: It doesn't really matter how you name the file.
  2. On the first row, write timestamp on the first column and email on the second column.

Create a Google Apps Script

  1. Click on Tools > Script Editor… which should open a new tab.
  2. Name the script Submit Form to Google Sheets or however you want for you to remember what the script does.
  3. Delete the function myFunction() {} block withing the Code.gs tab.
  4. 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()
  }
}
Enter fullscreen mode Exit fullscreen mode

If you want to better understand what this script is doing, check out the form-script-commented.js file in the repo for a detailed explanation.

Run the setup function

  1. Now, go to Run > Run Function > initialSetup to run this function.
  2. In the Authorization Required dialog, click on Review Permissions.
  3. Sign in or pick the Google account associated with this project.
  4. You should see a dialog that says Hi {Your Name}, Submit Form to Google Sheets wants to...
  5. Then click Allow

Add a new project trigger

  1. Click on Edit > Current project’s triggers.
  2. In the dialog click No triggers set up. Click here to add one now.
  3. In the dropdowns select doPost
  4. Set the events fields to From spreadsheet and On form submit
  5. Then click Save

Publish the project as a web app

  1. Click on Publish > Deploy as web app….
  2. Set Project Version to New and put initial version in the input field below.
  3. Leave Execute the app as: set to Me(your@address.com).
  4. For Who has access to the app: select Anyone, even anonymous.
  5. Click Deploy.
  6. In the popup, copy the Current web app URL from the dialog.
  7. 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 like https://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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

  1. Promise Polyfill
  2. Fetch Polyfill
  3. FormData Polyfill

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>
Enter fullscreen mode Exit fullscreen mode

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 (43)

Collapse
 
jonthuem profile image
JonthueM

What if I just want to push certain inputs not all?

Collapse
 
nameless08 profile image
nameless08

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?

Collapse
 
nameless08 profile image
nameless08 • Edited

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?

Collapse
 
omerlahav profile image
Omer Lahav

You commented the same thing on Dec 17 '21 and on March 26.
How is this helpful? :)

Collapse
 
bobymarley profile image
BobyMarley • Edited

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...

Collapse
 
omerlahav profile image
Omer Lahav

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 :) ]

Collapse
 
drafty profile image
Drafty

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.

Collapse
 
sagar12992510 profile image
Sagar

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?

Collapse
 
shristi1 profile image
Shristi Sharma • Edited

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:

// Send Data to Google Sheets
  var formData = {
    'name': 'Bob Smith',
    'email': 'bob@example.com'
  };
  var options = {
    'method' : 'post',
    'payload' : formData
  };
  fetch(scriptURL, options)
      .then(response => console.log('Success!', response))
      .catch(error => console.error('Error!', error.message))
Enter fullscreen mode Exit fullscreen mode

Could you please help me out?

Collapse
 
shristi1 profile image
Shristi Sharma

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:

saveToGoogleSheet () {
  const scriptURL = 'https://script.google.com/macros/s/.../exec'
  var sendingData = new FormData() // adjusted here
  sendingData.append('some_key', some_var_with_value)
  fetch(scriptURL, {method: 'POST', body: sendingData}) // adjusted here
    .then(response => console.log('Success!', response))
    .catch(error => console.error('Error!', error.message))
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sjha92865 profile image
Shubham Jha

I was suprised how no-one got this doGet() not found error.
I know on many Youtubers have provided the same code, they run script and it worked for them, don't know how and data was put in spreadsheet as well.
Can't i upload more than one image??

Collapse
 
mauroian profile image
mauroian

Hi Omer. Your script works very well and I am grateful to you!

I now have an issue: on submitting data from my HTML form to the sheet, how can I get a Redirect, so the user is sent to a Success or Error page?
Many thanks!

Collapse
 
sjha92865 profile image
Shubham Jha

can you please tell me where have you added index.html file, on appscript or your on vs code???

Collapse
 
omerlahav profile image
Omer Lahav

Hi there mauroian, glad it helped!
About the redirecting part: How do you decide whether the user is being sent to the success/error page? Is it based on whether the form was sent successfully to the spreadsheet, by one of the form's fields or by another criterion?

Collapse
 
mauroian profile image
mauroian • Edited

All I need is that the form was sent successfully to the spreadsheet. In which case, I'd like the user to be redirected to a success page :)

Thread Thread
 
omerlahav profile image
Omer Lahav • Edited

Try to find this line on the current code:

.then(response => console.log('Success!', response))
Enter fullscreen mode Exit fullscreen mode

And try to replace it with (You can pick either one of the options with comments, no need for both of them):

.then((response) => {
    console.log('Success!', response);
    // similar behavior as an HTTP redirect
    window.location.replace("https://dev.to/");

    // similar behavior as clicking on a link
    window.location.href = "https://dev.to/";
});
Enter fullscreen mode Exit fullscreen mode

Same goes for the error part, it should do the trick.

Thread Thread
 
mauroian profile image
mauroian

Done that. Now nothing gets submitted to the spreadsheet (and no redirect or anything else happens).

Thread Thread
 
mauroian profile image
mauroian

By the way, I think this might be important: in the original code, which was working, when I successfully submitted data to the spreadsheet, my HTML page did not change at all. It continued to show the input fields and submit button.

Basically, the line:
.then(response => console.log('Success!', response))

did nothing, as no 'Success!' message was shown in my page.

So, the issue could be in how/what the google script sends as response, or in the original code in index.html itself.

I am stuck at the moment... would be so happy if this worked!

Thread Thread
 
omerlahav profile image
Omer Lahav

It wasn't meant to do anything else besides sending to the spreadsheet.
In the following line:

form.addEventListener('submit', e => {
.............
}
Enter fullscreen mode Exit fullscreen mode

Everything between the { .... } will happen after the form is being submitted and you can modify it as much as you want, anything that happens there is up to your limits and not really relevant to this article or this mechanism :)
This whole article was only about the "sending-to-spreadsheet" part and nothing else, this is why nothing is happening other than that.
It already feels more of a tech support session than an easy js fix so just feel free to ping me wherever you'd like through one of the contact channels in my profile. The redirecting part is pure JS and can be found online but I'll try to help you out.

Collapse
 
yurrey profile image
abdelwahed

I have this error saying
Google hasn't verified this app
The app is requesting access to sensitive info in your Google Account

Collapse
 
levinunnink profile image
Levi Nunnink

Hey Omer, this is a cool way of doing this. I actually just built a free service that does this without any need for Javascript code.

I'd love to hear what you think: sheetmonkey.io/

Collapse
 
joboswd profile image
joboswd

Bro can you run 2 scripts at the same time? 2nd script will be used for Sheet2

Collapse
 
omerlahav profile image
Omer Lahav

I haven't tried it, but it might work. Have you tried to copy the "doPost" function and change the sheet name? Just remember to do everything on the same spreadsheet.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.