Recipes / Recipe 5

Automatic nightly backups of a Google Sheet

A dated copy of your spreadsheet every night in a folder you choose, keeping the last 30 and trashing older ones.

Apps ScriptTriggersSafety Setup time: 5 minutes

You'll need: A Google Drive folder for the backups.

Steps

  1. Create a folder in Drive called e.g. Sheet backups and copy its ID from the URL.
  2. In the spreadsheet you want to protect, open Extensions → Apps Script and paste the script.
  3. Set folderId, how many copies to keep, and the hour to run.
  4. Run backupSpreadsheet once to test (approve permissions), and check the folder.
  5. Run installDailyBackup once. From now on it runs every night.
Code.gsApps Script
/**
 * Nightly backup: copies this spreadsheet into a Drive folder and keeps only the newest N copies.
 * Run installDailyBackup once; after that it runs on its own.
 */
const BACKUP_CONFIG = {
  folderId: 'PASTE_BACKUP_FOLDER_ID', // a folder you own, ideally not shared widely
  keep: 30,                           // how many backups to keep
  hour: 2                             // hour of day (script time zone) to run
};

function installDailyBackup() {
  ScriptApp.getProjectTriggers()
    .filter((t) => t.getHandlerFunction() === 'backupSpreadsheet')
    .forEach((t) => ScriptApp.deleteTrigger(t));
  ScriptApp.newTrigger('backupSpreadsheet').timeBased().everyDays(1).atHour(BACKUP_CONFIG.hour).create();
}

/** Pure function: given [{id, created}] returns the ids to delete, keeping the newest `keep`. */
function pickBackupsToDelete(files, keep) {
  return files
    .slice()
    .sort((a, b) => b.created - a.created)
    .slice(keep)
    .map((f) => f.id);
}

function backupSpreadsheet() {
  const cfg = BACKUP_CONFIG;
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const folder = DriveApp.getFolderById(cfg.folderId);
  const prefix = 'Backup - ' + ss.getName() + ' - ';
  const stamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd HHmm');
  DriveApp.getFileById(ss.getId()).makeCopy(prefix + stamp, folder);

  const existing = [];
  const files = folder.getFiles();
  while (files.hasNext()) {
    const f = files.next();
    if (f.getName().indexOf(prefix) === 0) existing.push({ id: f.getId(), created: f.getDateCreated().getTime() });
  }
  pickBackupsToDelete(existing, cfg.keep).forEach((id) => DriveApp.getFileById(id).setTrashed(true));
}

Good to know

Do it yourself

Recipe Club: $12/month

Prefer to set things up yourself? Join the Recipe Club and get access to a private GitHub repository of ready-made, tested automations for Sheets, Apps Script, Python and Node. Each one comes with setup notes and sample data.

  • A new recipe added every week
  • Everything in the free library, plus the longer, more complete versions
  • Use them in your own business as much as you like
  • Cancel anytime by email (or the Stripe customer portal once available)

Recipe Club opens soon: join the waitlist

You'll need a free GitHub account. After checkout, reply with your GitHub username and we'll send the repo invite, usually within one business day.

Or buy a one-time recipe pack ($19–$29)

  • Clean Data Pack $19
    Dedupe, cleaning, merging and normalizing scripts and formulas for messy exports, with a sample sheet.
    Coming soon
  • Forms & Inbox Pack $19
    Form auto-replies, request routing, a lead log and follow-up nudges, ready to paste into Apps Script.
    Coming soon
  • Money Ops Pack $29
    Invoice reminders, a monthly revenue dashboard with QUERY, a receipts log and nightly backups.
    Coming soon

Written with AI assistance. Script logic is unit-tested; formulas follow documented Google Sheets behavior. Always try new automations on a copy of your sheet first.