GithubHelp home page GithubHelp logo

jamiewilson / form-to-google-sheets Goto Github PK

View Code? Open in Web Editor NEW
4.3K 38.0 753.0 25 KB

Store HTML form submissions in Google Sheets.

License: Apache License 2.0

JavaScript 91.70% HTML 8.30%
html-form google-sheets fetch-api promise-polyfills javascript

form-to-google-sheets's Introduction

Submit a Form to Google Sheets | Demo

How to create an HTML form that stores the submitted form data in Google Sheets using plain 'ol JavaScript (ES6), Google Apps Script, Fetch and FormData.

1. Create a new Google Sheet

  • First, go to Google Sheets and Start a new spreadsheet with the Blank template.
  • Rename it Email Subscribers. Or whatever, it doesn't matter.
  • Put the following headers into the first row:
A B C ...
1 timestamp email

To learn how to add additional input fields, checkout section 7 below.

2. Create a Google Apps Script

  • Click on Tools > Script Editor… which should open a new tab.
  • Rename it Submit Form to Google Sheets. Make sure to wait for it to actually save and update the title before editing the script.
  • Now, delete the function myFunction() {} block within the Code.gs tab.
  • Paste the following script in it's 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, checkout the form-script-commented.js file in the repo for a detailed explanation.

3. Run the setup function

  • Next, go to Run > Run Function > initialSetup to run this function.
  • In the Authorization Required dialog, click on Review Permissions.
  • Sign in or pick the Google account associated with this projects.
  • You should see a dialog that says Hi {Your Name}, Submit Form to Google Sheets wants to...
  • Click Allow

4. 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 spreadsheet and On form submit
  • Then click Save

5. Publish the project as a web app

  • Click on Publish > Deploy as web app….
  • Set Project Version to New and put initial version in the input field below.
  • Leave Execute the app as: set to Me([email protected]).
  • For Who has access to the app: select Anyone, even anonymous.
  • Click Deploy.
  • In the popup, copy the Current web app URL from 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 like https://script.google.com/a/yourdomain.com/macros/s/XXXX….

6. Input your web app URL

Open the file named index.html. On line 12 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 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 paramters.

Because Fetch and FormData aren't fully supported, you'll likely want to include their respective polyfills. See section #8.

Fun fact! The <html>, <head>, and body tags are actually among a handful of optional tags, but since the rules around how the browser parses a page are kinda complicated, you'd probably not want to omit them on real websites.

7. 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 create new headers with the exact, case-sensitive name values:

A B C D ...
1 timestamp email firstName lastName

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

Have feedback/requests/issues?

Please create a new issue. PRs are definitely welcome, but please run your ideas by me before putting in a lot of work. Thanks!

Related/Inspirational Articles

Documentation

form-to-google-sheets's People

Contributors

jamiewilson avatar omprakash95 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

form-to-google-sheets's Issues

Multiple forms in one page ?

Hi,

First of all, thanks for this, it works very well as long as i don't put multiple forms on a single page. Do you know how i can manage that ?

Thanks.

specify sheet in form data

Hi

it seems (though im new to google scripting) that you should be able to specify the sheetname in the form (via a hidden input field) and the write the data to that sheet (script would lookfor 'SheetName' or similar and overwrite the default 'Sheet1'.

This would make the system a bit more generic and not mean creating a web app and new url for each googlesheet you wanted to use as a populate.

Ideally be nice to have 1 scripts/web app with a url and supply the name of the destination workbook and sheet in the form but i am not sure scripts work like that - they seem to be attached to a single workbook (and security may be tricky)

Cheers

Rob

Not working when sheetName other than 'Sheet1'

This code working when I use 'Sheet1' as sheetName, but not working on any other sheetName, even as simple as 'Sheet2' is not working.

// working
var sheetName = 'Sheet1'

// not working
var sheetName = 'Sheet2'

Using checkboxes - only the first selected option appears

Hi everyone! I'm very new to programming so please forgive me if this obvious, but how do I get all checked boxes to show up on the sheet if I use a group of checkboxes? They all use the same name, and I was hoping to get all checked boxes to appear in one cell, perhaps separated by a comma the way google forms do. But when I try, only the first option selected appears in that cell.

Help will be very much appreciated - thank you so much!

Modifying sheet name in script and of spreadsheet causes failure

I have this script working and receiving multiple different fields from a form and populating the table correctly, but only if the Sheet is named "Sheet1" and the sheetName variable is set to "Sheet1". When I started out with a custom named sheet it wasn't working, and only started to work once both were set to "Sheet1".

PLEASE HELP ME

Google Analytics Tracking

Would anyone know how to incorporate Google Analytics tracking, and trigger a goal or event when the form is successfully submitted?

Thanks,
Ivan

Adding date and time to the sheet when the form is submitted

Any ideas how can i add a function in which when someone submits the form, the date and time of the submission is sent to spreadsheet?

EDIT- I have used some google scripts available online which add a timestamp in a cell if a particular cell is modified, but they doens't seem to work. I think the data which gets added in the spreadsheet on submitting the form is not added by a user which overrides the active cell function in the script, so it doesn't work. I may be wrong here but anyways please help me with this

Clear form data after submission

Hello everyone, I'm new to programming and I'm using this method to send a simple subscription to my static website. I would like to ask for help to adapt this code to clear the fields of the form shortly after sending the data, I tried some methods with jquery and have erased the data before sending.

<script>
  const scriptURL = 'macro-here'
  const form = document.forms['google']

  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>

Failed to load resource: the server responded with a status of of 401 ()

Everything seems to be setup properly, but I am getting the following error:

Access to fetch at 'https://script.google.com/a/dailysteals.com/macros/s/AKfycbwQWPb4X680QBTg8dy1qfSZqSyoXGZz138eeR5kL9w6akoZjZzf/exec' from origin 'http://vsiblelive.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

multiple elements with same name are not

Your solution seems to not work when a form has multiple elements with same name. Can you add a step that concatenates values from the same 'name' elements somehow?

Support for images

Hi, amazing guide here !
However, I'm trying to find a way to upload images to the sheets. I tried converting them to base64 but then the text isn't accepted in the cells. Is there a way to do that ? (maybe by uploading it to google drive first and then using the url of the file)

Form vulnerable to malicious fomulae

Hi Jamie, it's best to sanitise the data submitted to Google Sheets by removing any "=" characters. Right now anyone using your code is at risk of all their Sheet's data being exfiltrated from the sheet by a malicious =IMPORTDATA formula.
Kind regards,
Ollie

Work on Mobile?

Thanks for the code. Works great on desktop. However, I'm having issues through mobile. Do I have to do anything else to make this work on mobile devices? Thanks!

email column not populated

How's it going? Thanks for building this.
I'm following your set up but when I submit my form, I see a new row and the timestamp column gets filled by the value from my payload for email is not. Any idea why that might be?

Screen Shot 2020-11-20 at 1 01 09 PM

Screen Shot 2020-11-20 at 1 01 27 PM

how to trouble shoot response?

I set this up and get a 200 response when submitting the form-- but no data is saved in the sheet. how do we troubleshoot this without any ability to see whats happening on the back end?

Data not being processed to form

Scripts is not loading submitted form data into Sheets. Rather, Chrome browser is displaying submission text in URL bar of browser.

Data doesn't go on spreadsheets and doGet doesn't run

Hi, so i made a spreadsheet for my account system yet when i tested it, no data was added to the sheet and when i looked at the run history manage thingy it show that "doGet" failed to run (many things are paraphrased since my system language is not english)

post data after submit (not from <form>) results in undefined

I found this very interesting and wanted to adapt it to my needs. I am doing a single page application in vue and I have the form in a child component, and the submit button in the parent one. I also control the button with 'onclick'. Because of this I don't have an explicitly defined

and I don't have a submit button in it. Nevertheless, I have data retrieved from an input which I dispatch to 'state' and then I want to pass it to google sheet.

The code below post to google successfully, but the data is displayed in Google-sheet as 'undefined' no matter how I assign the value to the variable that I send.
I tried different approaches with the same result: 'undefined':

saveToGoogleSheet () {
  const scriptURL = 'https://script.google.com/macros/s/.../exec'
  const form = ''
  var sendingData = new FormData(form)
  endingData.append('starRating', this.feedbackData.starRating)
  fetch(scriptURL, {method: 'POST', body: new FormData(sendingData)})
    .then(response => console.log('Success!', response))
    .catch(error => console.error('Error!', error.message))
}

or

saveToGoogleSheet () {
  const scriptURL = 'https://script.google.com/macros/s/.../exec'
  fetch(scriptURL, {method: 'POST', body: JSON.stringify(this.feedbackData)})
    .then(response => console.log('Success!', response))
    .catch(error => console.error('Error!', error.message))
}

or

saveToGoogleSheet () {
  const scriptURL = 'https://script.google.com/macros/s/.../exec'
  fetch(scriptURL, {
    method: 'POST',
    body: {
      userName: this.feedbackData.nowReading,
      userNumber: this.feedbackData.starRating
    }
  })
    .then(response => console.log('Success!', response))
    .catch(error => console.error('Error!', error.message))
}

or even:

saveToGoogleSheet () {
  const scriptURL = 'https://script.google.com/macros/s/.../exec'
  fetch(scriptURL, {
    method: 'POST',
    body: {
      'nowReading': 'this',
      'starRating': '5'
    }
  })
    .then(response => console.log('Success!', response))
    .catch(error => console.error('Error!', error.message))
}

saveToGoogleSheet() is the method that I call after dispatching the data to 'state'.
Also, I have not placed the eventListener('submit' e => {e.preventDefault();... because I am not sure how to do it in my method. But I would not think this is the cause as I get a success response:

Success!
Response {type: "cors", url: "https://script.googleusercontent.com/macros/echo?u…xxx", redirected: true, status: 200, ok: true, …}
body: ReadableStream
bodyUsed: false
headers: Headers
proto: Headers
ok: true
redirected: true
status: 200
statusText: ""
type: "cors"
url: "https://script.googleusercontent.com/macros/echo?user_content_key=..."
proto: Response

What am I missing?

Data not showing when form is filed from any other location

Another issue i'm facing is that whenever I fill the form from my home network, the data gets saved in the sheet. But whenever my friend fills the same form from his home, the form doesn't get saved in the sheet. I have asked my friend to fill that form from his house(1km from my house) and my brother from his house(different country). Data didn't get saved :(
Any help is highly appreciated

intialSetup not called

When running I found that intialSetup() was not being called, maybe due to a missed step by me? For this reason I removed it and simply assigned the spreadsheet id during the POST call:

function doPost (event) {      
  const lock = LockService.getScriptLock()
  lock.tryLock(10000)

  try {
     // line below changed:
    const doc = SpreadsheetApp.openById(SpreadsheetApp.getActiveSpreadsheet().getId());
    const sheet = doc.getSheetByName(sheetName);

    const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    const nextRow = sheet.getLastRow() + 1;

    if (event) {
      const newRow = headers.map(function(header) {
        return header === 'timestamp' ? new Date() : event.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 (error) {
    return ContentService
      .createTextOutput(JSON.stringify({ 'result': 'error', 'error': error }))
      .setMimeType(ContentService.MimeType.JSON)
  }
  finally {
    lock.releaseLock()
  }
}

"Undefined" values in Google Sheets

Hi,

i followed the guide step by step, the data is sent to Google Sheet but every value is printed in the sheet as "undefined".
Also, if the headers don't start in the first column, the values are printed in the empty header's cells too, which makes me wonder if the script check for the header's names.

Here's a screenshot of the sheet.
screen

Modifying sheet name in script and of spreadsheet causes failure

I have this script working and receiving multiple different fields from a form and populating the table correctly, but only if the Sheet is named "Sheet1" and the sheetName variable is set to "Sheet1". When I started out with a custom named sheet it wasn't working, and only started to work once both were set to "Sheet1".

Not a huge issue (although it would be nice to have multiple forms write back to different worksheets instead of multiple workbooks). Just curious if anyone else ran into the same issue and was able to find a solution.

Handling Forms with File Upload

Hello there,

I was wondering if you can provide any direction on how one might handle file upload fields in your solution? Is there some additional google script that can be incorporated to connect a google drive account as well? Thanks.

Alex

Prevent inserting formulas in the spreadsheet

this little snippet is a true piece of art!
I have found a small issue though: it's possible to insert formulas into the spreadsheet. This could end up being a little annoying and given the fact that google Sheets is so powerful, it could lead to some vulnerabilities.
Anyways, in order to prevent formulas, I have added an apostrophe (') in front of every cell except for the timestamp using the following function:
if(header !== 'timestamp') {
return "'"+e.parameter[header]
}
inserted on line 26 (or 68 in the commented version).

Demo is not working

Submit a Form to Google Sheets | Demo

After clicking the Demo hyperlink. I arrived to this page: https://form-to-google-sheets.surge.sh/

I filled out the simple form and got an error.

Does anyone have a real time working solution for how to Submit HTML form DIRECTLY to Google Sheets.

Not working on Safari

There's an error on Safari desktop and mobile browsers--a submission will return a false positive. It will "submit," but the connected Google sheet will not actually get filled with a new entry. Help?

Google sheets

Can able to convert excel into web app with EASA software , Spreadsheetconvert and Appizy ..But cannot able convert google sheets into web app .......
Pleases How to help me for Google sheets convert to web app .....

Script not working

Hi,

I tried to use your script but it didn't work straight away.

The issue was coming from this line

sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])

I think it's because the nextRow didn't exist yet. On each call an empty error object was returned. By removing this line the code was working (without inserting the data obviously).

So I changed the script a bit, instead of using setValues I'm using appendRow.

Here is the final version of the doPost function I'm using:

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 newRow = headers.map(function(header) {
      return header === 'timestamp' ? new Date() : e.parameter[header]
    })
    
    sheet.appendRow(newRow)

    return ContentService
    .createTextOutput(JSON.stringify({ 'result': 'success', 'row': newRow}))
      .setMimeType(ContentService.MimeType.JSON) 
  }

  catch (e) {
    return ContentService
      .createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
      .setMimeType(ContentService.MimeType.JSON)
  }

  finally {
    lock.releaseLock()
  }
}

Debugging Google Scripts is a real pane so thanks for the amazing work you've done on the script 👍

Multiple entries on spreedsheet

When people send the form duplicates or triplicates entries how to send only one entry for every time they clic the button.

Wrong response from script

Even though the post gets through and appears in the spreadsheets, the response isn't as expected. Se below. I expect it to reply { 'result': 'success', 'row': nextRow }

Suggestions why this is not working?

The console.log output is:

body:ReadableStream
bodyUsed:false
headers:Headers {}
ok:true
redirected:true
status:200
statusText:""
type:"cors"
url: "https://script.googleusercontent.com/macros/echo?user_content_key=EWSseDIyMx8ENILuImnj3wcvKUaFRuGOKbqOM-Gc_PDGrB1XzULbrcXY4HHNDvIj8Vix3fZmQh4rveqvKl6WKqPbp6Rvp2CFm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnPqJW_LD0jUKaPZLOziaiM07QXJUwLQ7n8Ab_gtb0cXkPAqkB6ZDUVPzoI336pbgIp0mgLDwm9cO&lib=M4zG9cxq1ENE8zGROop-QDlmGtjrdEMxxxx"
__proto__:Response

Visiting the url, a page with the Google App Script logo appears along with a centered text saying:
Script function not found: doGet

Form Submission Landing "Thank You" Page

I am having trouble with getting a landing page working. I have everything working with a custom form and App Scripts, but I need my form submission to redirect users to a specific URL. I saw there link at the bottom of the article and a similar issue listed here, but there is not much description for how to implement this. I know some JS, but I am no expert and could really use a hand figuring this out.

Respectfully,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.