send.webforms.toRead the docs →

how-togoogle-sheets

A Google Sheets form pipeline without Google Forms

Your form, your styling, and still a spreadsheet at the end. The Apps Script route, start to finish.

WebForms6 min read

Google Forms solves the spreadsheet part and ruins the form part. You get rows in a sheet for free, but the form itself is a Google-branded page you can barely style, hosted on their domain, with their fonts. If the form is on your marketing site, that trade is usually wrong: the form is the thing your visitors touch, and the spreadsheet is plumbing.

This is the inverted setup: your own HTML form, styled however you like, and an Apps Script web app that appends each submission to a sheet. WebForms sits in the middle as the delivery layer, which is what gets you spam filtering before junk reaches the sheet, and lets the same submission also go to email or Slack.

Prepare the sheet so columns never scramble

Create a sheet with a header row whose labels exactly match your form field names: name, email, message, plus a Submitted at column. The script below matches columns by header name, not position. That one decision means someone reordering columns in the sheet six months from now does not silently scramble every future row - the most common way these pipelines rot.

The Apps Script web app

Code.gs
function doPost(e) {
  const body = JSON.parse(e.postData.contents);
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName("Submissions");

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

  const row = headers.map(function (h) {
    if (h === "Submitted at") return new Date();
    return body.data[h] || "";
  });

  sheet.appendRow(row);
  return ContentService.createTextOutput("ok");
}

Deploy it as a web app: Deploy, New deployment, type Web app. Now the step everyone gets wrong: set Who has access to Anyone. Not Anyone with a Google account - Anyone. A POST from a server is not signed in to Google, and with the stricter setting Apps Script responds with a redirect to a login page. Your delivery log will show the redirect status, the sheet stays empty, and nothing obviously says why.

Wire the form to it

In WebForms, add a webhook destination and paste the deployment URL. Point your form at your endpoint, submit once, and watch the row appear. The webhook payload puts your fields under a data key, which is why the script reads body.data - if you adapt this for another provider, check where fields live in their payload first.

  • Spam is quarantined before delivery, so the sheet only gets real submissions - scripts cannot tell junk from gold, so filtering has to happen upstream.
  • The same form can deliver to email or Slack in parallel; the sheet does not have to be the only consumer.
  • If you redeploy the script, the URL changes. Update the webhook destination or rows quietly stop arriving.

A native Google Sheets destination with OAuth and automatic header mapping is in development; the guide above is the route that works today, and it will keep working after the native adapter ships.

Get your endpoint

Free for 300 submissions a month. No card required.

Start free