Export last night’s filtered rows to a dated CSV in Drive

TL;DR — After an Apps Script nightly sync, filter rows (status=done or last 24h), build CSV, and save Exports/YYYY-MM-dd.csv in Drive with a daily trigger — no Sheets UI required.

Ops ask we hear a lot: after the nightly sync, dump “status = done” (or last 24h) rows into Exports/2026-09-18.csv without opening Sheets. Filter → values → CSV → Drive folder (create-if-missing), then hang it on a daily trigger.

TL;DR

  • Read the sheet once; filter in memory (status column or timestamp window).
  • Reuse valuesToCsv / escapeCsvCell for correct quoting.
  • writeValuesAsCsvToDrive into an Exports folder; filename = yyyy-MM-dd.csv in the script timezone.
  • Installable time-driven trigger after the sync job (or at the end of the same handler).
  • Soft tip: search NitroGAS for CSV / Drive helpers, or let Co-Pilot narrate the export for your ops teammate.

Using the Apps Script editor a lot? NitroGAS drops free themes & snippets right into script.google.com — optional Co-Pilot when you want a boost.

Pipeline

function exportFilteredRowsToDrive() { var ss = openConfiguredSpreadsheet(); // or SpreadsheetApp.getActive() var sheet = ss.getSheetByName('Queue'); var values = sheet.getDataRange().getDisplayValues(); if (values.length < 2) { console.log('No data rows'); return null; } var headers = values[0]; var statusCol = headers.indexOf('status'); if (statusCol < 0) throw new Error('Missing status column'); var body = values.slice(1).filter(function (row) { return String(row[statusCol]).toLowerCase() === 'done'; }); var out = [headers].concat(body); var root = DriveApp.getRootFolder(); // or a fixed parent folder id var folder = getOrCreateFolder(root, 'Exports'); var tz = ss.getSpreadsheetTimeZone() || Session.getScriptTimeZone(); var name = Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd') + '.csv'; var file = writeValuesAsCsvToDrive(out, folder, name); console.log('Wrote', body.length, 'rows →', file.getUrl()); return file; }

Wire getOrCreateFolder, valuesToCsv, and writeValuesAsCsvToDrive from the snippets library (or paste them above this function).

Last-24-hours variant

If you filter on a date column instead of status:

var updatedCol = headers.indexOf('updated_at'); var cutoff = new Date().getTime() - 24 * 60 * 60 * 1000; var body = values.slice(1).filter(function (row) { var t = new Date(row[updatedCol]).getTime(); return !isNaN(t) && t >= cutoff; });

Prefer getValues() for real Dates; use getDisplayValues() when the CSV should match what humans see in the grid.

Trigger hook

function installExportTrigger() { deleteTriggersByHandler('exportFilteredRowsToDrive'); // snippet ScriptApp.newTrigger('exportFilteredRowsToDrive') .timeBased() .everyDays(1) .atHour(6) // after your nightly sync — adjust .inTimezone(Session.getScriptTimeZone()) .create(); }

Or call exportFilteredRowsToDrive() at the end of the sync handler so one failure story stays in one execution log.

Checklist

  • Headers include the filter column (status / updated_at)
  • Timezone for the filename matches how ops name files
  • Exports folder shared with whoever needs the CSVs
  • Dry Run / pause flags still respected if this sits inside the sync job
  • Overwrite-same-name vs dated archive — pick one and document it

Soft CTA

Search NitroGAS for the CSV and Drive helpers, then let Co-Pilot add a short comment block so the next person knows why 6am exists. Happy Coding.