Pipeline hygiene fails when stages lag reality. Automate forward transitions when milestone checkboxes are set.
pipelineStageAutomation reads discovery/demo/proposal flags and advances one stage at a time per run.
Won stays manual here so revenue recognition is deliberate; pair with a deal-won notification script.
truthy_ accepts common Sheets checkbox and Yes/y forms.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Sheet | Column | Purpose |
|---|---|---|
| Deals | A id | Deal id |
| Deals | B stage | Current stage |
| Deals | C discoveryDone | Checkbox |
| Deals | D demoDone | Checkbox |
| Deals | E proposalSent | Checkbox |
| Deals | F lastTransition | Timestamp |
What this script does
Forward-only stage updates driven by milestone columns.
Prerequisites
Deals sheet with stages matching STAGE_ORDER names.
- Checkboxes or Yes values
- Run on edit or hourly
- Won handled elsewhere
Walkthrough
Set New deal discoveryDone; run; stage becomes Discovery and F stamps.
Edge cases
Skipping stages intentionally is not supported — require each flag.
- No automatic regression
- One hop per execution
How to test
Toggle flags out of order; ensure it still waits for the current stage's flag.
Hardening for production
Log transitions to StageHistory; notify owners on change.
Variations
Map stages from HubSpot via API instead of local checkboxes.
Full code: pipelineStageAutomation()
Maintain checklist columns, then run pipelineStageAutomation() on a trigger or after bulk edits.
/**
* Move deals to the next stage when checklist columns are complete.
*/
const DEALS = "Deals";
// A id, B stage, C discoveryDone, D demoDone, E proposalSent, F lastTransition
const STAGE_ORDER = ["New", "Discovery", "Demo", "Proposal", "Won"];
function pipelineStageAutomation() {
const sheet = SpreadsheetApp.getActive().getSheetByName(DEALS);
const values = sheet.getDataRange().getValues();
for (let i = 1; i < values.length; i++) {
let stage = values[i][1];
const discovery = truthy_(values[i][2]);
const demo = truthy_(values[i][3]);
const proposal = truthy_(values[i][4]);
let next = stage;
if (stage === "New" && discovery) next = "Discovery";
else if (stage === "Discovery" && demo) next = "Demo";
else if (stage === "Demo" && proposal) next = "Proposal";
// Won is manual or via deal-won flow
if (next !== stage) {
values[i][1] = next;
values[i][5] = new Date();
}
}
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}
function truthy_(v) {
return v === true || v === "TRUE" || v === "Yes" || v === "y" || v === 1;
}- Line 7: Documents allowed stages (Won manual).
- Line 15: Normalizes checkbox/Yes values.
- Line 25: Only writes when advancing.
- Line 5: Column F timestamp.
- Line 30: Batch-writes stage updates.
Deploy this example
- 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.
- 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.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 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: pipeline stages
- 1Stage names match code exactly
- 2Checkbox columns validated
- 3Won not auto-set here
- 4Owners know automation is forward-only
- 5History logging considered
- 6Test on a copy of Deals
Frequently asked questions
Not in this sample — it advances one stage per required flag.
Manual edit; or add explicit regress rules.
Possible, but batch hourly is gentler on large pipelines.
Edit the if/else chain and STAGE_ORDER comments.
Filter by a pipeline column before applying rules.
truthy_ handles both Sheets boolean and text.
Compare old vs next and MailApp the owner email column.