Embedding Google Forms in a Static Site

When working on hobby projects, there’s something special about saving a few bucks by employing a DYI solution. Even when the trade-off is weeks worth of work to avoid a $5 per month subscription, I choose the DIY path. For one such project recently, I came up with a custom way of embedding Google Forms on my hobby website.

I built a simple static website using vanilla HTML, Javascript, and CSS. The site is only intended for an audience of about 100 users, so I really wanted to avoid paying for a hosting service. Fortunately, I was able to host via AWS S3 completely within the free-tier limits. The whole project wound up costing me $12 for a domain and $0.50 per month for DNS services — happily within budget.

The piece of this project that most challenged my creativity and my budget was gathering user feedback. I wanted to be able to collect responses to a custom form without changing the platform to something like WordPress or hosting my own server.

In an early iteration, I linked users to a Google Form. This approach was fine, but not quite the user experience I wanted. Google Forms offers an option to embed a form, but I wasn’t excited about dealing with iframes and wanted a more custom look.

After considering my options, I decided that I didn’t want to migrate away from my Google Forms. Certainly, I thought, submitting the form must just be making a post request to some backend that then populates the responses tab. Why can’t I just make a post to that same endpoint? So, that’s what I did… kind of. Let me walk you through how I accomplished this.

Disclaimer: this solution is not viable for enterprise-grade applications; anything larger than a personal hobby project should probably consider an off-the-shelf solution.

Building the Form

The first step is to create your form through Google Forms. The only type of question that won’t work well with this approach is file uploads. I found it easiest to make all of the fields optional in the Google Form and enforce requirements directly through the client-side, but different users will have different perspectives on that.

Discovering the Payload

When a Google Form is submitted, it assigns specific parameter names to each input within the form. We will need to discover these names and expected values in order to replicate them from our own client. You could dive into the form preview’s source to find these names, but they can be tricky to pick out.

My preferred approach is to submit a couple of test survey responses and monitor the network developer tools to capture what we need. The request you’re looking for is a request to a “formResponse” endpoint. When you monitor for these parameter names, the important ones to note are of the form entry.<id>. All of the others, including entry.<id>_sentinel, can be ignored. You should come out with one parameter name per group of form inputs. Also, be sure to note the full endpoint. It should be of the form: https://docs.google.com/forms/.../formResponse.

Pure HTML Form

The easiest way to embed a Google Form is to use a pure HTML solution. We’ll start with an HTML form element. Set the form’s action attribute to the full endpoint.

Next, we can add child elements to the form. We’ll replicate the Google Form using HTML elements corresponding as described in the table below. Each child element should be given a name attribute corresponding to the parameter name we discovered from the preview payload.

Note that for things like radio buttons or checkboxes, each group of input elements for a question should have the same name attribute. It’s also important that elements with predefined values, like checkboxes, need to have their value attribute set to the exact same value as displayed in the Google Form.

Google Form question type HTML element type
Multiple choice <input type=”radio” />
Checkboxes <input type=”checkbox” />
Dropdown <select><option></option></select>
Short answer <input type=”text” />
Paragraph <textarea></textarea>
Linear scale <input type=”radio”>
Multiple choice grid <input type=”radio”>
Checkbox grid <input type=”checkbox” />
Time <input type=”time” />
Date <input type=”date” />

Finally, we can add a “submit” button to the form and begin testing it out. Be sure to test each possible option and ensure that values are being recorded correctly in the Google Forms’ responses section. Your resulting HTML should follow the general pattern of the following snippet.

<form id="formId" action="https://docs.google.com/forms/.../formResponse">
  <input type="radio" name="entry.000000" value="Choice 1" />
  <input type="radio" name="entry.000000" value="Choice 2" />
  <input type="radio" name="entry.000000" value="Choice 3" />
  <br>
  <input type="text" name="entry.000001" />
  <br>
  <input type="submit" value="Submit" />
</form>

If a styled version of this solution works for your needs, great! You’re all done.

In my case, I needed to do some processing and special handling in Javascript prior to submission. If you need something similar, you’ll require a slightly more complex solution. The remainder of this post will cover that case.

Converting to JavaScript

To set up our JavaScript submission, we can start with the HTML snippet I provided earlier. We’ll remove the “action” attribute from the form element so that the HTML won’t automatically submit to our Google Forms endpoint. Then from an included JavaScript file, add a submit event listener to the form element. One way to do this is something like: document.getElementById(“formId”).addEventListener(“submit”, handleSubmit);.

Then you’ll need to define the handleSubmit function. It will likely start with whatever data transformations or handling that you require. Then you’ll need to submit the form’s payload to the Google Forms endpoint. There are many ways to accomplish this, and I provide a basic example below.

function handleSubmit(event) {
  const googleFormEndpoint = "https://docs.google.com/forms/.../formResponse";
  let request = new XMLHttpRequest();
  request.open('POST', googleFormEndpoint, true);

  request.onload = function() {
    // handle request sent successfully
  };

  request.onerror = function() {
    // handle request failed to send
  };

  request.send(new FormData(event.target));
  event.preventDefault();
}

CORS Error

At this point, the form should submit and receive an error. When you receive this error, you can take a moment to check that you’re submitting with the correct parameter names and that the submitted values look correct. The error you’ll see is dependent on what library you use to send the request, but it should boil down to a CORS policy rejection.

If you are unfamiliar with CORS, to put it simply, in order to make requests to a resource outside of your application’s domain, your domain must be listed on an allow list. In this case, Google Forms (the external resource) does not allow requests from whatever your domain may be (null, localhost, or www.example.com). This is a browser-level control to protect the external resources from things like denial of service attacks.

CORS Workaround

Because CORS is enforced at the browser level, we can bypass it with a proxy. Conveniently, a proxy for just such purposes already exists. It’s called CORS Anywhere. Rather than having the script directly sending data to Google Forms, we’ll proxy it to Google Forms through CORS Anywhere.

I found that I could host my own CORS Anywhere proxy within the free tier limits of Heroku. I would walk you through the process of configuring it, but working with Heroku is so easy that I’ll just leave you with the Heroku node.js deployment tutorial instead.

The only extra configuration I needed was to set the PORT environment variable to 8080 and the CORSANYWHERE_WHITELIST environment variable to include my domain. I love this solution for being free of cost and free from hassle.

Rerouting your post request to the proxy should be as simple as modifying the URL you are submitting to. The CORS Anywhere docs detail your options when doing this. It should turn into something like: https://<Heroku app name>.herokuapp.com/docs.google.com:443/forms/…/formResponse.

Once your script is updated and the Heroku proxy is deployed, you should be able to successfully submit your form and see data relayed in the Google Forms responses tab. I would recommend thoroughly testing each of the input options at this point to validate that you didn’t make any typos in the parameter names. I learned that one the hard way.


This approach offers much more customizability than embedding a Google Form iframe and isn’t difficult to implement if you can avoid some of the unnecessary iterations by following my advice. I hope it can save you a few bucks when you’re gathering user feedback.

Conversation
    • Brian May Brian May says:

      That looks like another interesting approach. Thanks for sharing!

  • billy says:

    hi! this is interesting.
    is there a way to integrate a (free, ofcourse) recaptcha check into this to prevent bot spam?

    • Brian May Brian May says:

      That’s a great idea! I haven’t done it myself, but a quick Google search turned up a handful of helpful walkthroughs on how to integrate reCaptcha into any form. It looks like you could add a reCaptcha input to the form (the HTML one, not the Google one). Then I suppose the Heroku app could be modified to handle the backend verification prior to proxying the request to Google forms.

  • You can use https://fabform.io to to save form data to google sheets

  • gh says:

    Thanks so much! Was searching for some other way to imbed google forms to my wp site! the html only solution is working for me. How do I deal with the redirect to google forms once submitted do I need to add js?

    • Brian May Brian May says:

      I’m glad you found the post helpful!
      The only way that I am aware of to avoid the redirect after submission is to handle submit events on your own with the Javascript provided in the post.

  • Comments are closed.