appscript.dev
Automation Intermediate Forms Sheets Gmail

Build an asset or inventory request form

Track stock requests through approval — Northwind teammates ask, Awadesh approves, the warehouse ships.

Published Aug 30, 2025

When the Northwind studio runs out of camera batteries on a Friday afternoon, the fix is rarely the hard part — the hard part is knowing about it before the shoot on Monday. A shared spreadsheet works for a week, then someone forgets the columns, someone else overwrites a row, and the approver only hears about a request when it has already gone wrong.

This script turns a small Google Form into a proper request queue. Every submission gets a short ID, lands in a tracking sheet as pending, and pings the approver by email — so nothing falls through the gap between “I asked” and “the warehouse shipped it”.

What you’ll need

  • A Google Form with three short-answer questions named exactly Item, Quantity and Requester. Field names are case-sensitive — they have to match the e.namedValues keys in the script.
  • A Google Sheet linked to the form (or any sheet you want to use as the tracker) with the columns ID, Requester, Item, Quantity, Status, Submitted in row 1.
  • An approver email — here, Awadesh — with permission to read the sheet.

The script

// The sheet that stores the request queue. Form-linked sheets work; a
// separate tracker works too, as long as the header row matches.
const REQUESTS_SHEET_ID = '1abcAssetRequestsId';

// Where the approval ping goes. Change this if the approver moves on.
const APPROVER_EMAIL = '[email protected]';

// Default status for a new request. Approvals flip it to "approved" or
// "declined" later, by hand or by a second script.
const DEFAULT_STATUS = 'pending';

/**
 * Runs on every form submission. Reads the answers, writes a row to the
 * tracker with a short ID, and emails the approver so the request does
 * not sit unread in the sheet.
 *
 * @param {GoogleAppsScript.Events.FormsOnFormSubmit} e Form submit event.
 */
function onFormSubmit(e) {
  // 1. A short, unguessable ID. Used in the email subject and the sheet
  //    so the approver can reply with "approve <id>" later.
  const id = Utilities.getUuid().slice(0, 8);

  // 2. Pull the three answers out of namedValues. Forms always returns
  //    arrays — even single-answer questions — so index [0] is correct.
  const item = (e.namedValues.Item && e.namedValues.Item[0]) || '';
  const qtyRaw = (e.namedValues.Quantity && e.namedValues.Quantity[0]) || '';
  const requester = (e.namedValues.Requester && e.namedValues.Requester[0]) || '';

  // 3. Parse the quantity defensively. Forms hands strings back even for
  //    number fields, and a missing answer should not crash the trigger.
  const qty = parseInt(qtyRaw, 10) || 0;
  if (!item || !requester || qty <= 0) {
    Logger.log('Skipping malformed submission: ' + JSON.stringify(e.namedValues));
    return;
  }

  // 4. Append the row to the tracker. Order has to match the header row.
  SpreadsheetApp.openById(REQUESTS_SHEET_ID).getSheets()[0]
    .appendRow([id, requester, item, qty, DEFAULT_STATUS, new Date()]);

  // 5. Ping the approver. Plain text keeps it readable on mobile Gmail.
  GmailApp.sendEmail(
    APPROVER_EMAIL,
    `Asset request: ${qty} × ${item}`,
    `Requested by ${requester}.\nID: ${id}\nStatus: ${DEFAULT_STATUS}\n\n` +
    `Open the tracker to approve or decline.`
  );
}

How it works

  1. onFormSubmit fires on every new submission, receiving the form event e with answers grouped under e.namedValues.
  2. It mints a short ID with Utilities.getUuid().slice(0, 8) — long enough to be unique across a studio’s worth of requests, short enough to paste into an email subject.
  3. It reads the three answers, falling back to empty strings if a field is missing so the script never throws on a partial submission.
  4. It parses the quantity as an integer and bails out if any required field is blank or the quantity is zero. Bad data stays out of the sheet.
  5. It appends a row to the tracker in the exact order of the header row: ID, requester, item, quantity, status, submitted timestamp.
  6. It emails the approver with the quantity and item in the subject line — so the request is visible from the inbox list, no opening required.

Example run

A teammate submits the form with:

FieldValue
ItemCamera batteries (NP-FZ100)
Quantity4
RequesterPriya Shah

The tracker gains a new row:

IDRequesterItemQuantityStatusSubmitted
7f3a9c2ePriya ShahCamera batteries (NP-FZ100)4pending2026-05-27 10:14

And Awadesh receives an email with the subject Asset request: 4 × Camera batteries (NP-FZ100) and the ID in the body, ready to approve.

Trigger it

This script runs from a form submission, not a clock:

  1. Open the Apps Script project bound to the form (Extensions -> Apps Script from the form editor).
  2. In Triggers, add a new trigger for onFormSubmit, event source From form, event type On form submit.
  3. Approve the authorisation prompt — the script needs Sheets and Gmail scopes the first time it runs.

Submit a test response yourself before announcing the form to the team.

Watch out for

  • Field names are case-sensitive. If you rename Quantity to How many?, the script silently writes zero — guard against this by keeping the question titles in sync with the namedValues keys, or rename the keys to match.
  • Utilities.getUuid() produces a long ID; slicing to eight characters trades a tiny collision risk for readability. For a small studio that is fine; for thousands of requests a year, keep the full UUID.
  • The status column is plain text. If the approver flips a row to approved, a downstream script can pick it up — see the warehouse notification idea in the forms cluster for the second half of the workflow.
  • Gmail has a daily send quota. One email per submission is well under the limit; if a campaign drives hundreds of requests in a day, batch the approver email into a digest instead.

Related