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.
You'll need: A Google Sheet. Works for anyone who edits the sheet in the browser or app.
Steps
- Open Extensions → Apps Script and paste the script below.
- Set
sheetName, the columns to watch, and the column for the timestamp (keep the timestamp column out ofwatchColumns). - Want a “created at” stamp that never changes? Set
onlyIfEmpty: true. - Save. That's it:
onEditis a simple trigger, so it runs automatically. Edit a watched cell to test it.
/**
* 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
- Why not
=NOW()? NOW recalculates constantly, so every row would show the current time. A script writes a fixed value. - Pasting a block of cells stamps every row in the paste.
- Simple triggers only fire for edits made by people, not for changes made by formulas, other scripts, or Google Form submissions.
- Already have an
onEditfunction in this project? Keep oneonEditand callstampOnEdit(e)from it.
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.