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.
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:
- The Trigger: Runs when form is submitted, sending the email.
- 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>
<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
- Create your Google Form: Add fields like Employee Name, Request Type, etc.
- Link to Sheets: Google Form > Responses > Link to Sheets.
- Open Apps Script: Extensions > Apps Script in the spreadsheet.
- Paste Code: Delete default code, paste the 3 blocks above.
- 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.
- Copy URL: Copy the "Web app URL" and paste it into
CONFIG.WEB_APP_URLin your code. - 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.
- Add Trigger: Triggers (clock) > Add Trigger.
- Function:
onFormSubmit - Event source:
From spreadsheet - Event type:
On form submit
- Function:
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.
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 ushello@mereth.dev