Roll a form over each cycle
Archive old responses and reset for the next Northwind cycle — quarterly OKR check-ins.
Published Sep 27, 2025
Northwind runs OKR check-ins every quarter. Same form, same questions, same people — but each quarter’s responses need to live in their own bucket so the team can compare Q1 to Q2 without scrolling through nine months of rows. Doing that by hand on the first of each quarter is exactly the sort of chore that gets forgotten until someone notices the chart has gone weird.
This script takes a snapshot of the response sheet, files it in an archive folder, and clears the live sheet so the next cycle starts empty. The form itself is untouched — same URL, same trigger wiring, same destination — so respondents notice nothing.
What you’ll need
- A Google Form linked to a Google Sheet as its response destination. Save
the sheet’s ID in
RESPONSES. - A Drive folder for the archives. Save its ID in
ARCHIVE_FOLDER. Anything the script owner can write to works — a team-shared folder is ideal. - A schedule. Quarterly is the example, but the same script handles monthly, fortnightly, or annual rollovers.
The script
// The live response spreadsheet — the one the form writes to.
const RESPONSES = '1abcResponsesId';
// Drive folder where dated snapshots live.
const ARCHIVE_FOLDER = '1abcResponseArchivesId';
// Date format for the archive filename. Sortable, plain, no surprises.
const STAMP_FORMAT = 'yyyy-MM-dd';
/**
* Snapshot the current response sheet into the archive folder, then clear
* the live sheet ready for the next cycle.
*/
function rolloverForm() {
const ss = SpreadsheetApp.openById(RESPONSES);
const sheet = ss.getSheets()[0];
// 1. Build the archive filename and copy the file into the archive folder.
// makeCopy clones the data, formatting, and protected ranges in one shot.
const stamp = Utilities.formatDate(new Date(), 'GMT', STAMP_FORMAT);
const archiveName = `Responses archive — ${stamp}`;
const archive = DriveApp.getFileById(ss.getId())
.makeCopy(archiveName, DriveApp.getFolderById(ARCHIVE_FOLDER));
Logger.log(`Archived to ${archive.getUrl()}`);
// 2. Clear every row below the header on the live sheet. Skipping when
// lastRow is 1 avoids an invalid range error on an already-empty sheet.
const lastRow = sheet.getLastRow();
if (lastRow > 1) {
sheet
.getRange(2, 1, lastRow - 1, sheet.getLastColumn())
.clearContent();
Logger.log(`Cleared ${lastRow - 1} rows from the live sheet.`);
} else {
Logger.log('Live sheet was already empty — nothing to clear.');
}
}
How it works
rolloverFormopens the live response spreadsheet by ID and grabs its first sheet — the one Forms writes into by default.- It builds a date-stamped archive name like
Responses archive — 2025-09-27usingUtilities.formatDateso the stamp sorts cleanly in Drive listings. DriveApp.makeCopyclones the whole spreadsheet into the archive folder. You get a full point-in-time snapshot — values, formatting, charts, and any summary tabs you have built on top.- It reads
getLastRowto find the last filled row, then clears rows 2 to the end withclearContent. The header stays put. - The guard around
lastRow > 1matters — callinggetRangewith a zero row count throws, which would block the trigger silently on an empty sheet. - Both steps log to the executions panel so you can confirm the rollover happened and how many rows moved.
Example run
On 27 September 2025, with 142 responses in the live sheet, a single run of
rolloverForm produces:
- A new file in the archive folder:
Responses archive — 2025-09-27holding all 142 rows plus the header and any pivot tabs. - The live sheet trimmed back to one row — just the headers — ready for Q4 submissions.
The execution log shows:
Archived to https://docs.google.com/spreadsheets/d/1xyz.../edit
Cleared 142 rows from the live sheet.
Trigger it
OKR check-ins are quarterly at Northwind, so the trigger fires on the first day of each quarter:
- In the Apps Script editor, open Triggers (clock icon, left sidebar).
- Click Add trigger and pick
rolloverForm. - Event source: Time-driven. Type: Month timer, day 1, then in the
trigger function add a guard so it only runs in Jan, Apr, Jul, Oct:
function quarterlyRollover() { const month = new Date().getMonth(); if (![0, 3, 6, 9].includes(month)) return; rolloverForm(); } - Point the trigger at
quarterlyRolloverinstead ofrolloverForm.
For monthly or weekly cycles, drop the guard and pick the matching schedule.
Watch out for
- The form-response link is intact. The script copies and clears the destination sheet — it does not unlink the form. New submissions keep flowing into the live sheet exactly as before.
- Formulas in the live sheet are wiped.
clearContentremoves every value, including formulas in rows below the header. If you keep summary formulas on the response tab, move them to a second sheet that reads from the response sheet instead. - Archive copies count against Drive storage. A long-running form with rich
formatting can be several megabytes per snapshot — twelve quarters in, that
matters. Either archive to a shared drive with more headroom, or export the
archive as
.xlsxand trash the live copy. - The rollover is not atomic. If the copy succeeds and the clear fails, the live sheet still holds last cycle’s data — re-run safely, because the next archive will just be a near-duplicate. Avoid the reverse (clear before copy); the script orders the two steps deliberately.
Related
Trigger an onboarding sequence on form submit
Kick off tasks when a new Northwind hire submits their starter form.
Updated Oct 17, 2025
Build a content-submission queue
Collect Northwind guest posts or ideas for review through a Form.
Updated Oct 9, 2025
Score sentiment in open-text feedback
Rate Northwind feedback comments without manual review — using the in-Sheet sentiment function.
Updated Oct 5, 2025
Build a peer-nomination and voting system
Collect and tally Northwind nominations for awards or initiatives — one ballot, anonymous.
Updated Oct 1, 2025
Build an RSVP system with live headcounts
Track attendance and dietary needs for Northwind events — running count, live.
Updated Sep 23, 2025