Back to BlogAutomation

Skip the Add-on: Build a Google Forms Approval Workflow in 30 Minutes

Build an automated Google Forms approval workflow in 30 minutes using Apps Script-route requests to managers and eliminate manual email back-and-forth.

December 22, 2025
5 min read

Skip the Add-on: Build a Google Forms Approval Workflow in 30 Minutes

Your manager just forwarded you a time-off request buried in a 47-email thread from three weeks ago. By the time she approves it, the employee's already on vacation. Google Forms dumps submissions into a spreadsheet and calls it done-no routing, no notifications, no approval logic.

Here's the fix: 30 minutes of Apps Script gives you automated manager sign-off without installing a single add-on.

The Problem

Right now: Employee fills Form → You manually check Sheet → You email manager → Manager replies separately → You update spreadsheet by hand → You notify employee. That's 15-20 minutes per request. Eight requests a week? You're spending 2.5 hours on approvals that could run automatically.

The Form Workflow Plus add-on has 72K installs—clearly people want this—but you don't need to pay for it. Apps Script's onFormSubmit trigger does this natively. You write it once, own the code, zero fees.

How We'll Set It Up

Form submission → Apps Script fires → Manager gets an email with approve/reject links → Manager clicks a link → Spreadsheet updates → Employee gets notified.

The Code: Complete Approval System

This solution requires two parts efficiently combined in one script file:

  1. The Trigger: Runs when form is submitted, sending the email.
  2. The Web App: Runs when the manager clicks "Approve" or "Reject".

1. Configuration & Helper Functions

Start by defining your configuration. Secure, organized, and easy to change.

// CONFIGURATION
const CONFIG = {
  // Replace with your manager's email
  MANAGER_EMAIL: 'manager@yourcompany.com', 
  
  // Replace with the URL from "Deploy > Test Deployments" initially, 
  // then "Manage Deployments" after publishing
  WEB_APP_URL: 'YOUR_WEB_APP_URL_HERE', 
  
  STATUS_COLUMN_INDEX: 7, // Column H (1-based index)
  SHEET_NAME: 'Form Responses 1'
};
 
function getSheet() {
  return SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
}

2. The Trigger (onFormSubmit)

This function executes legally when a specific event occurs.

/**
 * Triggered automatically on Form Submit.
 * Sets status to 'PENDING' and emails the manager.
 */
function onFormSubmit(e) {
  const sheet = getSheet();
  const row = e.range.getRow();
  
  // 1. Set Initial Status
  sheet.getRange(row, CONFIG.STATUS_COLUMN_INDEX).setValue('PENDING');
  
  // 2. Get Form Data
  const responses = e.namedValues;
  const employee = responses['Employee Name'] ? responses['Employee Name'][0] : 'Employee';
  const type = responses['Request Type'] ? responses['Request Type'][0] : 'Request';
  const reason = responses['Reason'] ? responses['Reason'][0] : 'No reason provided';
  
  // 3. Construct Approval Links
  // We pass the Row ID and Action as parameters to our Web App
  const approveLink = `${CONFIG.WEB_APP_URL}?action=APPROVE&row=${row}`;
  const rejectLink = `${CONFIG.WEB_APP_URL}?action=REJECT&row=${row}`;
  
  // 4. Send Email
  const htmlBody = `
    <h3>Approval Request: ${type}</h3>
    <p><strong>Employee:</strong> ${employee}</p>
    <p><strong>Reason:</strong> ${reason}</p>
    <br/>
    <p>
      <a href="${approveLink}" style="background-color:green;color:white;padding:10px 20px;text-decoration:none;border-radius:4px;">✅ APPROVE</a>
      &nbsp;&nbsp;
      <a href="${rejectLink}" style="background-color:red;color:white;padding:10px 20px;text-decoration:none;border-radius:4px;">❌ REJECT</a>
    </p>
  `;
  
  GmailApp.sendEmail(CONFIG.MANAGER_EMAIL, `Approval Needed: ${employee}`, '', {
    htmlBody: htmlBody,
    name: 'Approval Bot'
  });
}

3. The Web App (doGet)

This handles the incoming click from the email.

/**
 * Handle HTTP GET requests (Link Clicks)
 */
function doGet(e) {
  const params = e.parameter;
  const action = params.action; // 'APPROVE' or 'REJECT'
  const row = parseInt(params.row);
  
  if (!action || !row) return HtmlService.createHtmlOutput('Invalid Request');
  
  const sheet = getSheet();
  // Check current status to prevent double-approvals
  const statusRange = sheet.getRange(row, CONFIG.STATUS_COLUMN_INDEX);
  const currentStatus = statusRange.getValue();
  
  if (currentStatus !== 'PENDING') {
    return HtmlService.createHtmlOutput(`<h2>Request details have already been processed (${currentStatus}).</h2>`);
  }
  
  // Update Status
  const newStatus = action === 'APPROVE' ? 'APPROVED' : 'REJECTED';
  statusRange.setValue(newStatus);
  
  // Optional: Notify Employee of Decision
  // const employeeEmail = sheet.getRange(row, 2).getValue(); // Assuming email in Col B
  // GmailApp.sendEmail(employeeEmail, `Request ${newStatus}`, ...);
  
  return HtmlService.createHtmlOutput(`
    <div style="font-family:sans-serif; text-align:center; padding:50px;">
      <h1 style="color:${action === 'APPROVE' ? 'green' : 'red'}">Request ${newStatus}</h1>
      <p>The spreadsheet has been updated. You can close this window.</p>
    </div>
  `);
}

Deploy in 8 Steps

  1. Create your Google Form: Add fields like Employee Name, Request Type, etc.
  2. Link to Sheets: Google Form > Responses > Link to Sheets.
  3. Open Apps Script: Extensions > Apps Script in the spreadsheet.
  4. Paste Code: Delete default code, paste the 3 blocks above.
  5. Initial Deploy: Click Deploy > New Deployment.
    • Select type: Web app.
    • Execute as: Me.
    • Who has access: Anyone (This allows the manager to approve from mobile even if not logged into the specific Org account, or restrict to Organization).
    • Click Deploy.
  6. Copy URL: Copy the "Web app URL" and paste it into CONFIG.WEB_APP_URL in your code.
  7. Save: Save the script (Ctrl+S). Important: If you change code later, you must Deploy > Manage Deployments > Edit (pencil) > New Version to update the live Web App.
  8. Add Trigger: Triggers (clock) > Add Trigger.
    • Function: onFormSubmit
    • Event source: From spreadsheet
    • Event type: On form submit

Build vs Buy: 30-Day Math

DIY Form Workflow Plus
Setup 30 minutes 5 minutes
Cost $0 forever ~$8/user/month
Customization Unlimited Limited
Risk None Vendor shutdown, price hikes

Break-even: After 1 month, you've saved $96 annually. After 3 years, that's $288 per workflow.

Build it yourself if you have 30 minutes and need custom routing (department-specific approvals, multi-level sign-off, compliance audit trails).

Buy the add-on if you need it live in 5 minutes and your manager won't wait for OAuth scopes.


Newsletter

Get more tips like this

Subscribe for practical Google Workspace automation tips.

Free updates. No spam. Unsubscribe any time.

Want more help?

We're here to help. Drop us an email and let's explore how we can optimize your workflows.

Email us

hello@mereth.dev

Google Forms integrationApps Script automationworkflow automationbusiness automationprocess automationGoogle Sheets automationadmin workflowtime savings