Recipes / Recipe 2

Add an automatic timestamp when a row is edited

Record 'last updated' (or 'created at') automatically whenever someone changes a row. No trigger setup needed.

Apps ScriptTracking Setup time: 5 minutes

You'll need: A Google Sheet. Works for anyone who edits the sheet in the browser or app.

Steps

  1. Open Extensions → Apps Script and paste the script below.
  2. Set sheetName, the columns to watch, and the column for the timestamp (keep the timestamp column out of watchColumns).
  3. Want a “created at” stamp that never changes? Set onlyIfEmpty: true.
  4. Save. That's it: onEdit is a simple trigger, so it runs automatically. Edit a watched cell to test it.
Code.gsApps Script
/**
 * Write a timestamp in a column whenever someone edits a watched column on that row.
 * Uses the simple onEdit trigger: nothing to install, just save the script.
 */
const STAMP_CONFIG = {
  sheetName: 'Tasks',      // tab to watch
  headerRows: 1,
  watchColumns: [1, 2, 3], // edits in A, B or C...
  stampColumn: 5,          // ...write the time into E (keep it out of watchColumns)
  onlyIfEmpty: false,      // true = "created at" (stamp once), false = "last updated"
  numberFormat: 'yyyy-mm-dd hh:mm:ss'
};

function onEdit(e) {
  stampOnEdit(e); // if you also use the "archive done rows" recipe, call archiveOnEdit(e) here too
}

function stampOnEdit(e) {
  const cfg = STAMP_CONFIG;
  if (!e || !e.range) return;
  const range = e.range;
  const sheet = range.getSheet();
  if (sheet.getName() !== cfg.sheetName) return;

  const firstCol = range.getColumn();
  const lastCol = range.getLastColumn();
  const touchesWatched = cfg.watchColumns.some((c) => c >= firstCol && c <= lastCol);
  if (!touchesWatched) return;

  const startRow = Math.max(range.getRow(), cfg.headerRows + 1);
  const endRow = range.getLastRow(); // pastes can cover many rows
  if (startRow > endRow) return;

  const count = endRow - startRow + 1;
  const stampRange = sheet.getRange(startRow, cfg.stampColumn, count, 1);
  const now = new Date();
  const current = stampRange.getValues();
  const next = current.map(([value]) => [cfg.onlyIfEmpty && value !== '' ? value : now]);
  stampRange.setValues(next).setNumberFormat(cfg.numberFormat);
}

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 from your Stripe receipt or by email

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.