Apps Script example · 8 min read

Html Service Form Submit: Copy-Paste Apps Script Pattern

Working html service form submit example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

HtmlServiceFormsSheets

HtmlService forms avoid Google Forms when you need custom validation UX inside a web app.

submitIntake receives a payload object from the browser, validates name/email/topic, and appends a row with a generated INT- id.

Throwing Error messages surfaces in withFailureHandler on the client — keep messages user-safe.

doGet only serves Form.html; all writes go through the server function so SpreadsheetApp runs with deployment identity.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

Sheet / FileColumn / FieldPurpose
Form.htmlname, email, topicClient inputs
IntakeA TimestampServer time
IntakeB IdINT-########
IntakeC NameValidated name
IntakeD EmailLowercased email
IntakeE TopicTopic text
IntakeF StatusNew

What this script does

submitIntake validates and appends; returns {id, message} for the success handler to show a confirmation.

Prerequisites

Form.html calling google.script.run.submitIntake; Intake sheet with headers.

  • Web app deployed
  • Client success/failure handlers wired
  • Intake tab writable by execution identity

Walkthrough

Open the web app, submit valid and invalid payloads, confirm errors and Intake rows.

Edge cases

Duplicate emails are allowed here — add a lookup if you need upserts.

  • Trim + lowercase email before storage
  • UUID slice keeps ids short but not cryptographic secrets

How to test

Submit three topics; verify ids unique and Status is New.

Hardening for production

Add LockService around appendRow under concurrent load; rate-limit by email in CacheService.

Variations

Send a confirmation MailApp email after append; or write to Data Store instead of Sheets.

Full code: submitIntake()

Create Form.html that calls submitIntake(payload). Deploy as web app and test validation paths.

/**
 * HtmlService form posts to a server function that validates and appends a row.
 * Client: google.script.run.withSuccessHandler(...).submitIntake(payload)
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile("Form")
    .setTitle("Intake form");
}

function submitIntake(payload) {
  payload = payload || {};
  const name = String(payload.name || "").trim();
  const email = String(payload.email || "").trim().toLowerCase();
  const topic = String(payload.topic || "").trim();

  if (name.length < 2) throw new Error("Name is required");
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) throw new Error("Valid email required");
  if (!topic) throw new Error("Topic is required");

  const sheet = SpreadsheetApp.getActive().getSheetByName("Intake");
  const id = "INT-" + Utilities.getUuid().slice(0, 8).toUpperCase();
  sheet.appendRow([new Date(), id, name, email, topic, "New"]);
  return { id: id, message: "Submitted" };
}
  1. Line 6: Serves the form UI.
  2. Line 12: Reads fields from the client object.
  3. Line 21: Builds a readable intake id prefix INT-.
  4. Line 22: Persists the validated record.
  5. Line 23: Success payload for the browser handler.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before you run: HTML form

  • 1Form.html exists and calls submitIntake
  • 2Intake sheet headers match columns A–F
  • 3Failure handler displays e.message
  • 4Test empty name and bad email
  • 5Confirm execution identity can edit the spreadsheet
  • 6Decide execute-as user vs developer for who owns the rows

Frequently asked questions

script.run is simpler for same-origin HtmlService UIs. Use doPost for external webhooks.

google.script.run.withFailureHandler(function(e){...}) — thrown Error messages arrive there.

Pass a base64 string or use a Drive file picker pattern; multipart browser uploads need special handling.

No. Always re-validate on the server as in submitIntake.

Require Google sign-in, add CacheService rate limits, or use reCAPTCHA with a secret checked server-side.

Return JSON-like objects; let the client update the DOM.

new Date() stored in Sheets uses the spreadsheet timezone when displayed.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.