100 high-impact, copy-pasteable Google Sheets Apps Script snippets
How to use
- In your Sheet: Extensions ▸ Apps Script
- Paste a script into Code.gs (ONE at a time to learn or as many as you want).
- Save ▶️ Run (grant permissions when prompted) or use the custom menu where provided.
A. Setup & Quality-of-Life
1) Add a Custom Menu
Creates a “Sheet Tools” menu with quick actions.
function onOpen() {
SpreadsheetApp.getUi()
.createMenu(‘Sheet Tools’)
.addItem(‘Clear Selected Range’, ‘clearSelectedRange’)
.addItem(‘Timestamp Selected Cells’, ‘timestampSelection’)
.addToUi();
}
function clearSelectedRange() {
const range = SpreadsheetApp.getActiveRange();
if (range) range.clearContent();
}
function timestampSelection() {
const range = SpreadsheetApp.getActiveRange();
if (range) range.setValue(new Date());
}
What it does & why: Adds handy actions to the UI so common tasks are one click away—no more hunting for functions.
2) Create a New “Log” Sheet (no blank first row)
function ensureLogSheet() {
const ss = SpreadsheetApp.getActive();
let log = ss.getSheetByName(‘log’);
if (!log) {
log = ss.insertSheet(‘log’);
log.getRange(1,1).setValue(‘Timestamp’);
log.getRange(1,2).setValue(‘Message’);
}
return log;
}
What & why: Ensures a log exists and seeds headers immediately so there’s no empty first row.
3) Quick Logger
function logMessage(msg) {
const log = ensureLogSheet();
log.appendRow([new Date(), msg]);
}
What & why: Dead-simple logging for troubleshooting or lightweight audit trails.
4) Sheet Snapshot (duplicate active sheet with timestamp)
function snapshotActiveSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const name = sheet.getName() + ‘ Snapshot ‘ + Utilities.formatDate(new Date(), Session.getScriptTimeZone(), ‘yyyy-MM-dd HH:mm’);
sheet.copyTo(SpreadsheetApp.getActive()).setName(name);
}
What & why: One-click “backup” copies before big edits.
5) Protect a Sheet (except header row editable by all)
function protectSheetExceptHeader() {
const sheet = SpreadsheetApp.getActiveSheet();
const protection = sheet.protect().setDescription(‘Protected except header’);
protection.removeEditors(protection.getEditors());
const unprotectedRange = sheet.getRange(1,1,1,sheet.getMaxColumns()); // header row
protection.setUnprotectedRanges([unprotectedRange]);
}
What & why: Prevents accidental edits while keeping headers tweakable.
B. Data Entry & Cleanup
6) Add Timestamp on Edit (specific column)
Adds a timestamp in column B when column A is edited.
function onEdit(e) {
const sheet = e.range.getSheet();
if (sheet.getName() !== ‘Sheet1’) return;
if (e.range.getColumn() === 1 && e.value !== undefined) {
sheet.getRange(e.range.getRow(), 2).setValue(new Date());
}
}
What & why: Auto-audit for entries—when something in A changes, B logs when.
7) Fill Down Last Row (copy formulas)
function fillDownLastRow() {
const sh = SpreadsheetApp.getActiveSheet();
const lastRow = sh.getLastRow();
if (lastRow <= 1) return;
const source = sh.getRange(lastRow – 1, 1, 1, sh.getLastColumn());
const target = sh.getRange(lastRow, 1, 1, sh.getLastColumn());
source.copyTo(target, {contentsOnly:false});
}
What & why: Keeps formulas alive for new rows without manual dragging.
8) Trim Spaces & Normalize Case in Selection
function cleanSelection() {
const r = SpreadsheetApp.getActiveRange();
const values = r.getValues().map(row =>
row.map(v => (typeof v === ‘string’ ? v.trim().replace(/\s+/g,’ ‘) : v))
);
r.setValues(values);
}
What & why: Removes double spaces/stray whitespace that break lookups & matching.
9) Remove Duplicates (entire sheet, by first column)
function removeDuplicatesByFirstColumn() {
const sh = SpreadsheetApp.getActiveSheet();
const data = sh.getDataRange().getValues();
const header = data.shift();
const seen = new Set();
const filtered = data.filter(r => {
const key = r[0];
if (seen.has(key)) return false;
seen.add(key);
return true;
});
sh.clearContents();
sh.getRange(1,1,1,header.length).setValues([header]);
if (filtered.length) sh.getRange(2,1,filtered.length,header.length).setValues(filtered);
}
What & why: Dedupes dataset using column A as the unique key.
10) Find & Replace (in selection)
function findReplaceInSelection() {
const ui = SpreadsheetApp.getUi();
const resFind = ui.prompt(‘Find text’, ui.ButtonSet.OK_CANCEL);
if (resFind.getSelectedButton() !== ui.Button.OK) return;
const resReplace = ui.prompt(‘Replace with’, ui.ButtonSet.OK_CANCEL);
if (resReplace.getSelectedButton() !== ui.Button.OK) return;
const find = resFind.getResponseText();
const replace = resReplace.getResponseText();
const rng = SpreadsheetApp.getActiveRange();
const vals = rng.getValues().map(row => row.map(v =>
(typeof v === ‘string’) ? v.split(find).join(replace) : v
));
rng.setValues(vals);
}
What & why: Targeted, safe in-range replacements—no global disasters.
11) Split Full Name to First/Last (into adjacent columns)
function splitNames() {
const r = SpreadsheetApp.getActiveRange();
const vals = r.getValues().map(row => {
const name = row[0] ? String(row[0]).trim() : ”;
const parts = name.split(/\s+/);
const first = parts.shift() || ”;
const last = parts.join(‘ ‘);
return [first, last];
});
r.offset(0,1,vals.length,2).setValues(vals);
}
What & why: Quick parsing of names into first/last for CRM imports.
C. Sorting, Filtering, Grouping
12) Sort by Column (A asc, then B desc)
function sortByAThenB() {
const sh = SpreadsheetApp.getActiveSheet();
const lr = sh.getLastRow();
const lc = sh.getLastColumn();
if (lr <= 1) return;
const range = sh.getRange(2,1,lr-1,lc);
range.sort([{column:1, ascending:true}, {column:2, ascending:false}]);
}
What & why: Common multi-key sort you can tweak as needed.
13) Filter Rows (copy matches to a new sheet)
function filterToNewSheet() {
const sh = SpreadsheetApp.getActiveSheet();
const ui = SpreadsheetApp.getUi();
const res = ui.prompt(‘Filter: keep rows where Column A equals…’, ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() !== ui.Button.OK) return;
const match = res.getResponseText();
const data = sh.getDataRange().getValues();
const header = data.shift();
const filtered = data.filter(r => String(r[0]) === match);
const out = SpreadsheetApp.getActive().insertSheet(‘Filtered ‘ + match);
out.getRange(1,1,1,header.length).setValues([header]);
if (filtered.length) out.getRange(2,1,filtered.length,header.length).setValues(filtered);
}
What & why: Extracts a subset to its own sheet for sharing/analysis.
14) Group by Category & Sum (simple pivot in script)
function groupByFirstColumnSumSecond() {
const sh = SpreadsheetApp.getActiveSheet();
const rows = sh.getDataRange().getValues();
const header = rows.shift();
const map = new Map();
rows.forEach(r => {
const key = r[0];
const val = Number(r[1]) || 0;
map.set(key, (map.get(key) || 0) + val);
});
const out = SpreadsheetApp.getActive().insertSheet(‘Grouped Summary’);
out.getRange(1,1,1,2).setValues([[‘Category’,’Sum’]]);
const arr = […map.entries()];
if (arr.length) out.getRange(2,1,arr.length,2).setValues(arr);
}
What & why: A quick script-pivot when you don’t want a full PivotTable.
D. Formatting & Validation
15) Apply Header Style (bold, frozen, background)
function styleHeader() {
const sh = SpreadsheetApp.getActiveSheet();
const lc = sh.getLastColumn();
if (lc === 0) return;
const header = sh.getRange(1,1,1,lc);
header.setFontWeight(‘bold’).setBackground(‘#f2f2f2’);
sh.setFrozenRows(1);
}
What & why: Professional, readable tables in one click.
16) Number Formats (currency & date for selection)
function formatSelectionCurrencyDate() {
const r = SpreadsheetApp.getActiveRange();
r.setNumberFormat(‘[$$-409]#,##0.00;[Red]-[$$-409]#,##0.00’);
}
What & why: Standard currency formatting for clean reporting.
17) Conditional Formatting: Highlight Duplicates in Column A
function highlightDuplicatesColA() {
const sh = SpreadsheetApp.getActiveSheet();
const rules = sh.getConditionalFormatRules();
const range = sh.getRange(2,1,sh.getMaxRows()-1,1);
const rule = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied(‘=COUNTIF($A:$A,$A2)>1’)
.setBackground(‘#ffcccc’)
.setRanges([range])
.build();
rules.push(rule);
sh.setConditionalFormatRules(rules);
}
What & why: Visual duplicate finder that updates as you type.
18) Data Validation: Dropdown from List
function addDropdownValidation() {
const sh = SpreadsheetApp.getActiveSheet();
const r = SpreadsheetApp.getActiveRange();
const rule = SpreadsheetApp.newDataValidation()
.requireValueInList([‘Open’,’In Progress’,’Done’], true)
.setAllowInvalid(false)
.build();
r.setDataValidation(rule);
}
What & why: Enforces controlled inputs for status/tags.
E. Import/Export & Connectivity
19) Import JSON from a URL to a Sheet
function importJsonToSheet() {
const url = ‘https://example.com/api/items’; // change me
const resp = UrlFetchApp.fetch(url, {muteHttpExceptions:true});
const json = JSON.parse(resp.getContentText());
const ss = SpreadsheetApp.getActive();
const sh = ss.insertSheet(‘Import JSON’);
if (!Array.isArray(json) || json.length === 0) {
sh.getRange(1,1).setValue(‘No data’);
return;
}
const headers = Object.keys(json[0]);
sh.getRange(1,1,1,headers.length).setValues([headers]);
const rows = json.map(o => headers.map(h => o[h]));
sh.getRange(2,1,rows.length,headers.length).setValues(rows);
}
What & why: Pulls an API response into a nice tabular sheet.
20) Export Active Sheet as CSV (Drive file link)
function exportActiveSheetCSV() {
const sh = SpreadsheetApp.getActiveSheet();
const csv = sh.getDataRange().getValues()
.map(row => row.map(v => {
v = (v===null || v===undefined) ? ” : String(v);
return /[“,\n]/.test(v) ? `”${v.replace(/”/g,'””‘)}”` : v;
}).join(‘,’))
.join(‘\n’);
const file = DriveApp.createFile(sh.getName() + ‘.csv’, csv, MimeType.CSV);
SpreadsheetApp.getUi().alert(‘CSV created: ‘ + file.getUrl());
}
What & why: One-click CSV exporter without downloading manually.
21) Send Sheet as PDF via Email
function emailActiveSheetAsPdf() {
const sh = SpreadsheetApp.getActiveSheet();
const ss = sh.getParent();
const url = Drive.Files.export(ss.getId(), ‘application/pdf’); // Advanced Drive API not required
const blob = UrlFetchApp.fetch(
`https://docs.google.com/feeds/download/spreadsheets/Export?key=${ss.getId()}&exportFormat=pdf&gid=${sh.getSheetId()}`
, {headers:{‘Authorization’:’Bearer ‘ + ScriptApp.getOAuthToken()}}).getBlob()
.setName(sh.getName() + ‘.pdf’);
MailApp.sendEmail({
to: Session.getActiveUser().getEmail(),
subject: ‘Sheet PDF: ‘ + sh.getName(),
body: ‘Attached PDF of the active sheet.’,
attachments: [blob]
});
}
What & why: Emails the current sheet as a PDF—good for approvals/reports.
(Note: Uses export endpoint; works without enabling Advanced services.)
F. Lookups, Ranges, and Names
22) Create Named Range for Selection
function nameSelection() {
const r = SpreadsheetApp.getActiveRange();
const ui = SpreadsheetApp.getUi();
const res = ui.prompt(‘Enter a name for this range:’);
if (res.getSelectedButton() !== ui.Button.OK) return;
SpreadsheetApp.getActive().setNamedRange(res.getResponseText(), r);
}
What & why: Friendly naming for ranges used in formulas/scripts.
23) VLOOKUP via Script (write results)
function vlookupScript() {
const sh = SpreadsheetApp.getActiveSheet();
const keyCol = 1; // lookup from A
const lookupTable = sh.getRange(1,5,sh.getLastRow(),2).getValues(); // E:F
const map = new Map(lookupTable.map(r => [String(r[0]), r[1]]));
const rng = sh.getRange(2, keyCol, sh.getLastRow()-1, 1);
const vals = rng.getValues().map(r => [map.get(String(r[0])) ?? ”]);
rng.offset(0,1,vals.length,1).setValues(vals); // output to column B
}
What & why: Imperative vlookup—useful for snapshots or removing volatile formulas.
G. Sheet & Range Utilities
24) Delete Empty Rows (below last content)
function deleteEmptyRowsBelow() {
const sh = SpreadsheetApp.getActiveSheet();
const lastRow = sh.getLastRow();
const max = sh.getMaxRows();
if (max > lastRow) sh.deleteRows(lastRow + 1, max – lastRow);
}
What & why: Cleans up scrollbars/blank rows for tidy sheets.
25) Resize Columns to Fit
function autoResizeAllColumns() {
const sh = SpreadsheetApp.getActiveSheet();
const lc = sh.getLastColumn();
for (let c = 1; c <= lc; c++) sh.autoResizeColumn(c);
}
What & why: Presentation-ready columns after import/cleanup.
H. Forms & Responses
26) Append Google Form Responses to a “Master” Sheet
function onFormSubmit(e) {
// Installable trigger: From form-linked sheet ▸ Triggers ▸ onFormSubmit
const ss = SpreadsheetApp.getActive();
const master = ss.getSheetByName(‘Master’) || ss.insertSheet(‘Master’);
const row = e.values; // includes timestamp
master.appendRow(row);
}
What & why: Keeps a consolidated “Master” even if you add multiple form tabs later.
27) Auto-Assign Incrementing Ticket IDs on New Response
function onFormSubmitTicketId(e) {
const sheet = e.range.getSheet(); // the responses sheet
const row = e.range.getRow();
const idCol = 2; // e.g., put Ticket ID in column B
const lastId = Number(sheet.getRange(2, idCol, sheet.getLastRow()-1, 1).getValues()
.map(r => r[0]).filter(Boolean).sort((a,b)=>b-a)[0]) || 1000;
sheet.getRange(row, idCol).setValue(lastId + 1);
}
What & why: Generates unique IDs without formulas.
28) Auto-Email on Form Response (simple)
function onFormSubmitEmail(e) {
const values = e.namedValues; // { “Email”: [“x@y.com”], “Message”: [“…”] }
const to = (values[‘Email’] || [Session.getActiveUser().getEmail()])[0];
const msg = (values[‘Message’] || [‘(no message)’])[0];
MailApp.sendEmail(to, ‘Thanks for your submission’, ‘We received: ‘ + msg);
}
What & why: Sends an acknowledgement—great UX and instant feedback.
29) Validate Required Columns After Response
function onFormSubmitValidate(e) {
const sh = e.range.getSheet();
const row = sh.getRange(e.range.getRow(), 1, 1, sh.getLastColumn()).getValues()[0];
const requiredCols = [2,3]; // B and C must not be blank
const missing = requiredCols.filter(c => !row[c-1]);
if (missing.length) {
// flag the row in column A
sh.getRange(e.range.getRow(), 1).setNote(‘Missing required fields in columns: ‘ + missing.join(‘,’));
}
}
What & why: Flags incomplete responses for follow-up.
30) Copy Important Fields from Form Sheet to a Clean “Intake” Sheet
function onFormSubmitIntake(e) {
const src = e.range.getSheet();
const intake = src.getParent().getSheetByName(‘Intake’) || src.getParent().insertSheet(‘Intake’);
const row = e.range.getRow();
const vals = src.getRange(row,1,1,src.getLastColumn()).getValues()[0];
// Map columns: [Timestamp(A), Name(C), Email(D), Priority(F)]
const out = [vals[0], vals[2], vals[3], vals[5]];
intake.appendRow(out);
}
What & why: Keeps a human-friendly intake list without clutter.
I. Triggers & Automation
31) Create a Time-Based Trigger (Daily 8am)
function createDaily8amTrigger() {
ScriptApp.newTrigger(‘dailyJob’)
.timeBased().atHour(8).everyDays(1).create();
}
function dailyJob() {
logMessage(‘Daily job ran at ‘ + new Date());
}
What & why: Schedules a daily routine—reports, cleanups, etc.
32) Clear a Range Weekly (Mondays)
function clearWeekly() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘Weekly’);
sh.getRange(‘B2:F1000’).clearContent();
}
What & why: Resets a working area for the new week; pair with Monday trigger.
33) Send Weekly Digest Email (summary of counts)
function weeklyDigest() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘Tasks’);
const data = sh.getDataRange().getValues();
const header = data.shift();
const statusIdx = header.indexOf(‘Status’);
const counts = data.reduce((m, r) => (m[r[statusIdx]] = (m[r[statusIdx]]||0)+1, m), {});
MailApp.sendEmail(Session.getActiveUser().getEmail(), ‘Weekly Task Digest’, JSON.stringify(counts, null, 2));
}
What & why: Lightweight snapshot to your inbox.
34) Archive Rows Older Than N Days
function archiveOldRows() {
const DAYS = 30;
const ss = SpreadsheetApp.getActive();
const src = ss.getSheetByName(‘Live’);
const arc = ss.getSheetByName(‘Archive’) || ss.insertSheet(‘Archive’);
const rows = src.getDataRange().getValues();
const head = rows.shift();
const now = Date.now();
const keep = [head];
const move = [];
rows.forEach(r => {
const ts = r[0] instanceof Date ? r[0].getTime() : null;
if (ts && (now – ts) > DAYS*86400000) move.push(r);
else keep.push(r);
});
if (move.length) {
if (arc.getLastRow() === 0) arc.appendRow(head);
arc.getRange(arc.getLastRow()+1,1,move.length,head.length).setValues(move);
src.clearContents();
src.getRange(1,1,keep.length,head.length).setValues(keep);
}
}
What & why: Keeps the main sheet fast and tidy.
35) Rotate Log Sheet (start a fresh one monthly)
function rotateLogMonthly() {
const ss = SpreadsheetApp.getActive();
const old = ss.getSheetByName(‘log’);
if (!old) return;
old.setName(‘log ‘ + Utilities.formatDate(new Date(), Session.getScriptTimeZone(), ‘yyyy-MM’));
ss.insertSheet(‘log’);
}
What & why: Prevents massive logs from getting unwieldy.
36) Auto-Format New Rows on Edit
function onEditAutoFormat(e) {
const r = e.range;
const sh = r.getSheet();
const row = r.getRow();
if (row === 1) return;
sh.getRange(row,1,1,sh.getLastColumn())
.setVerticalAlignment(‘middle’)
.setWrap(true);
}
What & why: Consistent look as data grows.
37) Auto-Insert Today’s Date if Column A is Edited
function onEditDateStamp(e) {
if (e.range.getColumn() === 1 && e.value !== undefined) {
const sh = e.range.getSheet();
sh.getRange(e.range.getRow(), 3).setValue(new Date()); // put in column C
}
}
What & why: Separate from timestamps for tracking “last touched” date.
38) Delete Triggers by Function Name
function deleteTriggersFor(fnName) {
ScriptApp.getProjectTriggers()
.filter(t => t.getHandlerFunction() === fnName)
.forEach(ScriptApp.deleteTrigger);
}
What & why: Clean up old triggers to avoid duplicates.
J. Analytics & Charts
39) Build a Summary Sheet with KPIs
function buildKPIs() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘Data’);
const data = sh.getDataRange().getValues();
const head = data.shift();
const amountIdx = head.indexOf(‘Amount’);
const cnt = data.length;
const sum = data.reduce((a,r)=>a+(Number(r[amountIdx])||0),0);
const avg = cnt ? sum/cnt : 0;
const out = SpreadsheetApp.getActive().getSheetByName(‘KPIs’) || SpreadsheetApp.getActive().insertSheet(‘KPIs’);
out.clear();
out.getRange(‘A1:B3’).setValues([[‘Metric’,’Value’],[‘Count’,cnt],[‘Total’,sum]]);
out.getRange(‘A4:B4’).setValues([[‘Average’,avg]]);
}
What & why: One-click KPI snapshot.
40) Create a Chart Programmatically (Column Chart)
function makeColumnChart() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘KPIs’);
const chart = sh.newChart()
.setChartType(Charts.ChartType.COLUMN)
.addRange(sh.getRange(‘A2:B4’))
.setPosition(1,4,0,0)
.build();
sh.insertChart(chart);
}
What & why: Script-built charts for dashboards.
41) Conditional Alert if KPI Threshold Breached
function kpiAlert() {
const kpi = SpreadsheetApp.getActive().getSheetByName(‘KPIs’);
const total = Number(kpi.getRange(‘B3’).getValue()) || 0;
if (total < 1000) {
MailApp.sendEmail(Session.getActiveUser().getEmail(),’KPI Alert’,’Total fell below 1000: ‘ + total);
}
}
What & why: Early warning system via email.
42) Rolling 7-Day Summary
function rolling7Day() {
const dataSh = SpreadsheetApp.getActive().getSheetByName(‘Data’);
const rows = dataSh.getDataRange().getValues();
const head = rows.shift();
const dateIdx = head.indexOf(‘Date’);
const amtIdx = head.indexOf(‘Amount’);
const cutoff = Date.now() – 7*86400000;
const sum = rows.reduce((a,r)=>{
const d = r[dateIdx] instanceof Date ? r[dateIdx].getTime() : 0;
if (d >= cutoff) a += Number(r[amtIdx])||0;
return a;
}, 0);
const out = SpreadsheetApp.getActive().getSheetByName(‘Rolling’) || SpreadsheetApp.getActive().insertSheet(‘Rolling’);
out.getRange(‘A1:B1’).setValues([[‘Metric’,’Value’]]);
out.getRange(‘A2:B2’).setValues([[‘Last 7 days total’, sum]]);
}
What & why: Quick time-window analytics.
43) Top-N by Amount (write to new sheet)
function topN() {
const N = 10;
const sh = SpreadsheetApp.getActive().getSheetByName(‘Data’);
const data = sh.getDataRange().getValues();
const head = data.shift();
const amtIdx = head.indexOf(‘Amount’);
data.sort((a,b)=>(Number(b[amtIdx])||0)-(Number(a[amtIdx])||0));
const out = SpreadsheetApp.getActive().getSheetByName(‘TopN’) || SpreadsheetApp.getActive().insertSheet(‘TopN’);
out.clear();
out.getRange(1,1,1,head.length).setValues([head]);
out.getRange(2,1,Math.min(N,data.length),head.length).setValues(data.slice(0,N));
}
What & why: Leaderboard report—sales, time, whatever metric.
44) Add Sparklines to Show Trends
function addSparklines() {
const sh = SpreadsheetApp.getActiveSheet();
const lr = sh.getLastRow();
if (lr < 3) return;
// Assume values in B2:B, put sparkline in C
for (let r=2; r<=lr; r++) {
sh.getRange(r,3).setFormula(`=SPARKLINE(B$2:B${r})`);
}
}
What & why: Tiny trendlines for quick visual scanning.
45) Bucketize Values (e.g., “Low/Med/High”)
function bucketize() {
const sh = SpreadsheetApp.getActiveSheet();
const lr = sh.getLastRow();
const amtCol = 2;
const outCol = 3;
const vals = sh.getRange(2,amtCol,lr-1,1).getValues().map(r=>{
const v = Number(r[0])||0;
return [v<50 ? ‘Low’ : v<200 ? ‘Medium’ : ‘High’];
});
sh.getRange(2,outCol,vals.length,1).setValues(vals);
}
What & why: Categorize numeric data for easier filtering.
K. Collaboration & Notifications
46) Share Sheet with Editors by Email List
function shareWithEditors() {
const emails = [‘user1@example.com’,’user2@example.com’]; // customize
const file = DriveApp.getFileById(SpreadsheetApp.getActive().getId());
emails.forEach(e => file.addEditor(e));
}
What & why: Bulk share without opening Drive UI.
47) Revoke Commenter Access for a Domain
function revokeCommentersByDomain() {
const file = DriveApp.getFileById(SpreadsheetApp.getActive().getId());
file.getCommenters().forEach(u => {
if (/@example\.com$/i.test(u.getEmail())) file.removeCommenter(u);
});
}
What & why: Tighten permissions, especially after a project ends.
48) Email Me Changes to a Specific Range
function onEditNotifyRange(e) {
const watch = e.range.getSheet().getRange(‘B2:D50’);
if (e.range.getRow() >= watch.getRow() && e.range.getRow() <= watch.getLastRow()
&& e.range.getColumn() >= watch.getColumn() && e.range.getColumn() <= watch.getLastColumn()) {
MailApp.sendEmail(Session.getActiveUser().getEmail(),
‘Change in watched range’,
`Cell ${e.range.getA1Notation()} changed to: ${e.value}`);
}
}
What & why: Get alerts when critical cells change.
49) Add a Comment to a Cell Programmatically
function addCommentToCell() {
const r = SpreadsheetApp.getActiveRange();
r.setNote(‘Please verify this value. Updated on ‘ + new Date());
}
What & why: Lightweight “comment” using Notes from script.
50) Make a Review Checklist Sheet
function makeReviewChecklist() {
const sh = SpreadsheetApp.getActive().insertSheet(‘Review Checklist’);
sh.getRange(‘A1:C1’).setValues([[‘Task’,’Owner’,’Done?’]]).setFontWeight(‘bold’);
sh.getRange(‘A2:A10’).setValues([[‘Data validated’],[‘Headers styled’],[‘No duplicates’],[‘Formulas checked’],[‘Permissions set’],[‘KPIs built’],[‘Chart updated’],[‘Archive done’],[‘Backup taken’],[‘Notes added’]]);
const rule = SpreadsheetApp.newDataValidation().requireCheckbox().build();
sh.getRange(‘C2:C10’).setDataValidation(rule);
}
What & why: Standardizes QA before sharing.
51) Notify Slack via Incoming Webhook
function notifySlack(text) {
const url = ‘https://hooks.slack.com/services/XXX/YYY/ZZZ’; // replace
const payload = JSON.stringify({ text });
UrlFetchApp.fetch(url, {method: ‘post’, contentType:’application/json’, payload});
}
What & why: Push sheet events into your team’s Slack.
52) Notify on New Top-5 Change
function notifyOnTop5Change() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘TopN’);
const curr = sh.getRange(2,1,5,1).getValues().flat().join(‘|’);
const props = PropertiesService.getScriptProperties();
const last = props.getProperty(‘top5’) || ”;
if (curr !== last) {
props.setProperty(‘top5’, curr);
MailApp.sendEmail(Session.getActiveUser().getEmail(),’Top 5 changed’, curr);
}
}
What & why: Alerts when leaderboard meaningfully shifts.
L. Advanced Imports / Exports / Drive
53) Import All CSVs from a Drive Folder
function importCSVsFromFolder() {
const folderId = ‘YOUR_FOLDER_ID’;
const folder = DriveApp.getFolderById(folderId);
const ss = SpreadsheetApp.getActive();
const files = folder.getFilesByType(MimeType.CSV);
while (files.hasNext()) {
const f = files.next();
const sh = ss.insertSheet(f.getName().replace(/\.csv$/i,”));
const csv = Utilities.parseCsv(f.getBlob().getDataAsString());
sh.getRange(1,1,csv.length,csv[0].length).setValues(csv);
}
}
What & why: Bulk-import multiple CSVs into individual tabs.
54) Merge All Sheets into One “All Data”
function mergeAllSheets() {
const ss = SpreadsheetApp.getActive();
const out = ss.getSheetByName(‘All Data’) || ss.insertSheet(‘All Data’);
out.clear();
let writeRow = 1;
ss.getSheets().forEach(sh => {
if (sh.getName() === ‘All Data’) return;
const values = sh.getDataRange().getValues();
out.getRange(writeRow,1,values.length,values[0].length).setValues(values);
writeRow += values.length;
});
}
What & why: One long table (useful for pivoting across tabs).
55) Export Every Sheet as its Own CSV File
function exportEachSheetAsCSV() {
const ss = SpreadsheetApp.getActive();
ss.getSheets().forEach(sh => {
const data = sh.getDataRange().getValues()
.map(row => row.map(v => {
v = v==null?”:String(v);
return /[“,\n]/.test(v) ? `”${v.replace(/”/g,'””‘)}”` : v;
}).join(‘,’)).join(‘\n’);
DriveApp.createFile(`${sh.getName()}.csv`, data, MimeType.CSV);
});
}
What & why: Batch export for downstream tools.
56) List Files in a Drive Folder into a Sheet
function listFolderFiles() {
const folderId = ‘YOUR_FOLDER_ID’;
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFiles();
const sh = SpreadsheetApp.getActive().insertSheet(‘Folder List’);
sh.getRange(‘A1:C1’).setValues([[‘Name’,’URL’,’Last Updated’]]).setFontWeight(‘bold’);
let r = 2;
while (files.hasNext()) {
const f = files.next();
sh.getRange(r,1,1,3).setValues([[f.getName(), f.getUrl(), f.getLastUpdated()]]);
r++;
}
}
What & why: Inventory your assets for audits.
57) Import Google Sheet Range from Another Spreadsheet
function importFromAnotherSpreadsheet() {
const sourceId = ‘SOURCE_SPREADSHEET_ID’;
const sourceRangeA1 = ‘Sheet1!A1:D100’;
const values = SpreadsheetApp.openById(sourceId).getRangeByName
? SpreadsheetApp.openById(sourceId).getRange(sourceRangeA1).getValues()
: SpreadsheetApp.openById(sourceId).getSheetByName(‘Sheet1’).getRange(‘A1:D100’).getValues();
const dest = SpreadsheetApp.getActive().getSheetByName(‘Imported’) || SpreadsheetApp.getActive().insertSheet(‘Imported’);
dest.clear();
dest.getRange(1,1,values.length,values[0].length).setValues(values);
}
What & why: Pull data from a different file on demand.
58) Export Active Sheet to New Spreadsheet
function exportActiveToNewSpreadsheet() {
const sh = SpreadsheetApp.getActiveSheet();
const temp = SpreadsheetApp.create(sh.getName() + ‘ Export’);
const dst = temp.getSheets()[0];
dst.clear();
const vals = sh.getDataRange().getValues();
dst.getRange(1,1,vals.length,vals[0].length).setValues(vals);
SpreadsheetApp.getUi().alert(‘Created: ‘ + temp.getUrl());
}
What & why: Share a single tab as its own file.
59) Snapshot to PDF in Drive (no email)
function saveActiveSheetAsPdf() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const url = `https://docs.google.com/spreadsheets/d/${ss.getId()}/export?format=pdf&gid=${sh.getSheetId()}`;
const blob = UrlFetchApp.fetch(url, {headers: {‘Authorization’:’Bearer ‘ + ScriptApp.getOAuthToken()}}).getBlob()
.setName(sh.getName() + ‘.pdf’);
DriveApp.createFile(blob);
}
What & why: Generate a PDF artifact for records.
60) Move Current Spreadsheet to a Specific Drive Folder
function moveSpreadsheetToFolder() {
const folder = DriveApp.getFolderById(‘YOUR_FOLDER_ID’);
const file = DriveApp.getFileById(SpreadsheetApp.getActive().getId());
const parents = file.getParents();
while (parents.hasNext()) file.removeFromFolder(parents.next());
folder.addFile(file);
}
What & why: Organize files programmatically.
M. Errors & Audit
61) Try/Catch Wrapper with Logging
function safeRun() {
try {
// do risky things
fillDownLastRow();
logMessage(‘safeRun: success’);
} catch (err) {
logMessage(‘safeRun ERROR: ‘ + err.stack);
throw err;
}
}
What & why: Captures stack traces in your log sheet.
62) Capture onEdit Errors Gracefully
function onEditSafe(e) {
try {
onEditDateStamp(e);
onEditAutoFormat(e);
} catch (err) {
const sh = e.range.getSheet();
sh.getRange(e.range.getRow(), e.range.getColumn()).setNote(‘Error: ‘ + err.message);
}
}
What & why: Avoids breaking edits with unhandled exceptions.
63) Change Log: Who Edited What Cell
function onEditChangeLog(e) {
const sh = e.range.getSheet();
const user = Session.getActiveUser().getEmail() || ‘Unknown’;
const msg = `${user} edited ${sh.getName()}!${e.range.getA1Notation()} -> ${e.value}`;
logMessage(msg);
}
What & why: Lightweight audit trail.
64) Validate Data Types in a Range (numbers only)
function validateNumbersOnly() {
const r = SpreadsheetApp.getActiveRange();
const values = r.getValues();
const bad = [];
values.forEach((row,i) => row.forEach((v,j)=>{ if (v!==” && isNaN(Number(v))) bad.push(r.getCell(i+1,j+1).getA1Notation()); }));
if (bad.length) SpreadsheetApp.getUi().alert(‘Non-numeric cells: ‘ + bad.join(‘, ‘));
}
What & why: Quick sanity check before analysis/export.
65) Flag Outliers (Z-score > 3)
function flagOutliers() {
const r = SpreadsheetApp.getActiveRange();
const vals = r.getValues().flat().map(Number).filter(v=>!isNaN(v));
const mean = vals.reduce((a,b)=>a+b,0)/vals.length || 0;
const std = Math.sqrt(vals.reduce((a,b)=>a+Math.pow(b-mean,2),0)/Math.max(1,vals.length-1));
for (let i=1; i<=r.getNumRows(); i++) {
for (let j=1; j<=r.getNumColumns(); j++) {
const v = Number(r.getCell(i,j).getValue());
if (!isNaN(v) && std>0 && Math.abs((v-mean)/std) > 3) {
r.getCell(i,j).setBackground(‘#ffe6e6’);
}
}
}
}
What & why: Visually tag extreme values.
66) Create a “Data Dictionary” Sheet from Headers
function buildDataDictionary() {
const sh = SpreadsheetApp.getActiveSheet();
const headers = sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
const dict = SpreadsheetApp.getActive().getSheetByName(‘Data Dictionary’) || SpreadsheetApp.getActive().insertSheet(‘Data Dictionary’);
dict.clear();
dict.getRange(1,1,1,3).setValues([[‘Field’,’Type’,’Description’]]).setFontWeight(‘bold’);
const rows = headers.map(h => [h, ‘string|number|date’, ‘Describe this field…’]);
dict.getRange(2,1,rows.length,3).setValues(rows);
}
What & why: Documentation for collaborators and future you.
N. Performance, Properties, Cache
67) Batch Write Example (fast updates)
function batchUpdateExample() {
const sh = SpreadsheetApp.getActiveSheet();
const lr = sh.getLastRow(), lc = sh.getLastColumn();
const values = sh.getRange(2,1,lr-1,lc).getValues();
const out = values.map(row => row.map(v => typeof v===’string’ ? v.trim() : v));
sh.getRange(2,1,out.length,lc).setValues(out);
}
What & why: Read once, write once—significantly faster.
68) Use Script Properties to Store Config
function setConfig() {
const props = PropertiesService.getScriptProperties();
props.setProperty(‘ALERT_THRESHOLD’, ‘1000’);
}
function getConfigThreshold() {
return Number(PropertiesService.getScriptProperties().getProperty(‘ALERT_THRESHOLD’)) || 1000;
}
What & why: Keep magic numbers out of the code.
69) Cache Expensive Computation for 10 Minutes
function getExpensiveResult() {
const cache = CacheService.getScriptCache();
const cached = cache.get(‘expensive’);
if (cached) return JSON.parse(cached);
const result = { time: new Date(), value: Math.random() }; // simulate
cache.put(‘expensive’, JSON.stringify(result), 600);
return result;
}
What & why: Reduces quota usage and speeds repeated runs.
70) Range Chunk Writer (avoid 50k cell limits)
function writeInChunks(sh, startRow, startCol, data, chunkRows) {
for (let i = 0; i < data.length; i += chunkRows) {
const slice = data.slice(i, i + chunkRows);
sh.getRange(startRow + i, startCol, slice.length, slice[0].length).setValues(slice);
}
}
What & why: Helper for huge writes that risk timeouts.
71) Detect and Skip Unchanged Rows
function updateOnlyChanged() {
const sh = SpreadsheetApp.getActiveSheet();
const data = sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn()).getValues();
const out = [];
data.forEach((r,i) => {
if (String(r[1]).trim() !== String(r[2]).trim()) { // example compare B vs C
r[3] = ‘Updated’; // write to D
out.push({row:i+2, rowData:r});
}
});
out.forEach(o => sh.getRange(o.row,1,1,data[0].length).setValues([o.rowData]));
}
What & why: Minimizes write operations for speed.
72) Store & Use Per-User Preferences
function setMyPreference(key, value) {
PropertiesService.getUserProperties().setProperty(key, String(value));
}
function getMyPreference(key, def=”) {
return PropertiesService.getUserProperties().getProperty(key) ?? def;
}
What & why: Personalize behavior per collaborator.
O. Power Tools
73) Multi-Criteria Filter into New Sheet
function filterMulti() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘Data’);
const rows = sh.getDataRange().getValues();
const head = rows.shift();
const idxStatus = head.indexOf(‘Status’);
const idxOwner = head.indexOf(‘Owner’);
const matches = rows.filter(r => (r[idxStatus]===’Open’ || r[idxStatus]===’In Progress’) && r[idxOwner]===’Lars’);
const out = SpreadsheetApp.getActive().getSheetByName(‘Filtered Multi’) || SpreadsheetApp.getActive().insertSheet(‘Filtered Multi’);
out.clear(); out.getRange(1,1,1,head.length).setValues([head]);
if (matches.length) out.getRange(2,1,matches.length,head.length).setValues(matches);
}
What & why: Reusable pattern for complex filters.
74) Pivot-Like Summary by Two Keys
function pivotTwoKeys() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘Data’);
const data = sh.getDataRange().getValues();
const head = data.shift();
const colA = head.indexOf(‘Region’);
const colB = head.indexOf(‘Product’);
const colV = head.indexOf(‘Amount’);
const keysetA = new Set(), keysetB = new Set();
const map = new Map();
data.forEach(r=>{
const a = r[colA], b = r[colB], v = Number(r[colV])||0;
keysetA.add(a); keysetB.add(b);
const key = a+’||’+b;
map.set(key, (map.get(key)||0)+v);
});
const A = […keysetA], B = […keysetB];
const out = SpreadsheetApp.getActive().getSheetByName(‘Pivot2’) || SpreadsheetApp.getActive().insertSheet(‘Pivot2’);
out.clear();
out.getRange(1,1,1,B.length+1).setValues([[‘Region/Prod’, …B]]);
const rows = A.map(a => [a, …B.map(b => map.get(a+’||’+b)||0)]);
out.getRange(2,1,rows.length, rows[0].length).setValues(rows);
}
What & why: Quick “pivot” without the UI.
75) Merge Duplicate Keys and Keep the Latest
function mergeKeepLatest() {
const sh = SpreadsheetApp.getActiveSheet();
const data = sh.getDataRange().getValues();
const head = data.shift();
const keyIdx = 1; // key in column B
const dateIdx = 3; // date in column D
const map = new Map();
data.forEach(r => {
const key = String(r[keyIdx]);
const d = r[dateIdx] instanceof Date ? r[dateIdx].getTime() : 0;
const prev = map.get(key);
if (!prev || d > prev._ts) map.set(key, Object.assign({_ts:d}, r));
});
const merged = […map.values()].map(o => {
const {_ts, …rest} = o;
return Object.values(rest);
});
sh.clearContents();
sh.getRange(1,1,1,head.length).setValues([head]);
if (merged.length) sh.getRange(2,1,merged.length,head.length).setValues(merged);
}
What & why: For CRMs/exports where duplicates arrive—keeps the freshest row.
P. Custom Functions (usable directly in cells)
76) UNIQUECOUNT(range)
Counts distinct, non-empty values.
/**
* =UNIQUECOUNT(A2:A)
*/
function UNIQUECOUNT(range) {
range = range.filter(r => r[0] !== ” && r[0] !== null && r[0] !== undefined);
const set = new Set(range.map(r => String(r[0])));
return set.size;
}
What & why: Quick unique count without building a pivot.
77) TEXTJOINIF(delim, conditionRange, criterion, joinRange)
Conditional text join (like Excel’s TEXTJOIN + IF).
/**
* =TEXTJOINIF(“, “, A2:A, “Open”, B2:B)
*/
function TEXTJOINIF(delim, conditionRange, criterion, joinRange) {
const out = [];
for (let i = 0; i < conditionRange.length; i++) {
if (String(conditionRange[i][0]) === String(criterion)) {
const v = joinRange[i] && joinRange[i][0] != null ? String(joinRange[i][0]) : ”;
if (v) out.push(v);
}
}
return out.join(delim);
}
What & why: Build comma-separated lists based on a condition.
78) SUMVISIBLE(range)
Sums only the visible (unfiltered/unhidden) cells.
/**
* =SUMVISIBLE(B2:B100)
*/
function SUMVISIBLE(range) {
const sheet = SpreadsheetApp.getActiveRange().getSheet();
const v = range.reduce((sum, row, i) => {
const r = SpreadsheetApp.getActiveRange().getRow() + i;
if (!sheet.isRowHiddenByFilter(r) && !sheet.isRowHiddenByUser(r)) {
const x = Number(row[0]);
if (!isNaN(x)) sum += x;
}
return sum;
}, 0);
return v;
}
What & why: Totals that respect your filters/hiding.
79) REGEXEXTRACTALL(text, pattern)
Returns all matches (one per row).
/**
* =REGEXEXTRACTALL(A2, “[A-Za-z]+”)
*/
function REGEXEXTRACTALL(text, pattern) {
const re = new RegExp(pattern, ‘g’);
const out = [];
let m;
while ((m = re.exec(String(text))) !== null) out.push([m[0]]);
return out.length ? out : [[”]];
}
What & why: Google Sheets’ REGEXEXTRACT returns only the first match—this gets them all.
80) PARSEJSONKEY(json, key)
Gets a top-level key’s value from a JSON string.
/**
* =PARSEJSONKEY(A2, “name”)
*/
function PARSEJSONKEY(json, key) {
try {
const obj = JSON.parse(String(json));
const val = obj[key];
if (val === undefined || val === null) return ”;
if (Array.isArray(val)) return val.join(‘, ‘);
if (typeof val === ‘object’) return JSON.stringify(val);
return String(val);
} catch (_) {
return ”;
}
}
What & why: Extract quick fields without full scripting.
Q. UI (Sidebar/Dialog) Utilities
81) Sidebar “Quick Tools”
Adds a sidebar with buttons calling functions.
function showQuickToolsSidebar() {
const html = HtmlService.createHtmlOutput(`
<div style=”font-family:sans-serif”>
<h3>Quick Tools</h3>
<button onclick=”google.script.run.autoResizeAllColumns()”>Auto-resize columns</button>
<button onclick=”google.script.run.styleHeader()”>Style header</button>
<button onclick=”google.script.run.snapshotActiveSheet()”>Snapshot sheet</button>
<p id=”status”></p>
<script>
google.script.run.withSuccessHandler(() => {
document.getElementById(‘status’).textContent = ‘Ready’;
}).logMessage(‘Sidebar opened’);
</script>
</div>
`);
html.setTitle(‘Quick Tools’);
SpreadsheetApp.getUi().showSidebar(html);
}
What & why: A friendly panel for non-technical users to run stuff.
82) Simple Input Dialog → Append to Sheet
Prompt for Name/Email; append a row.
function promptAndAppend() {
const ui = SpreadsheetApp.getUi();
const name = ui.prompt(‘Enter Name’).getResponseText();
const email = ui.prompt(‘Enter Email’).getResponseText();
const sh = SpreadsheetApp.getActive().getSheetByName(‘Intake’) || SpreadsheetApp.getActive().insertSheet(‘Intake’);
if (sh.getLastRow() === 0) sh.getRange(1,1,1,3).setValues([[‘Timestamp’,’Name’,’Email’]]);
sh.appendRow([new Date(), name, email]);
}
What & why: Zero-setup intake without Google Forms.
83) Global Find/Replace (all sheets)
Searches every tab and replaces text.
function findReplaceAllSheets() {
const ui = SpreadsheetApp.getUi();
const f = ui.prompt(‘Find what?’).getResponseText();
const r = ui.prompt(‘Replace with?’).getResponseText();
const ss = SpreadsheetApp.getActive();
ss.getSheets().forEach(sh => {
const range = sh.getDataRange();
const vals = range.getValues().map(row => row.map(v => typeof v === ‘string’ ? v.split(f).join(r) : v));
range.setValues(vals);
});
}
What & why: Safer than manual: one run, consistent.
R. Formatting & Layout
84) Freeze Top N Rows (all sheets)
function freezeTopNAllSheets() {
const ui = SpreadsheetApp.getUi();
const n = Number(ui.prompt(‘Freeze how many header rows?’).getResponseText()) || 1;
SpreadsheetApp.getActive().getSheets().forEach(sh => sh.setFrozenRows(n));
}
What & why: Uniform headers across the workbook.
85) Hide Columns by Header Name
function hideColumnsByHeaderNames() {
const namesToHide = [‘Internal Notes’,’Cost’]; // edit me
const sh = SpreadsheetApp.getActiveSheet();
const head = sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
head.forEach((h, i) => {
if (namesToHide.includes(String(h))) sh.hideColumns(i+1);
});
}
What & why: Quickly hide sensitive columns.
86) Unhide All Rows & Columns (current sheet)
function unhideAll() {
const sh = SpreadsheetApp.getActiveSheet();
sh.showColumns(1, sh.getMaxColumns());
sh.showRows(1, sh.getMaxRows());
}
What & why: Reset visibility when things get messy.
87) Zebra Striping (banded rows)
function zebraStripe() {
const sh = SpreadsheetApp.getActiveSheet();
const lr = sh.getLastRow(), lc = sh.getLastColumn();
const rng = sh.getRange(2,1,Math.max(0,lr-1),lc);
rng.setBackground(null);
for (let r = 2; r <= lr; r+=2) {
sh.getRange(r,1,1,lc).setBackground(‘#f7f7f7’);
}
}
What & why: Improves readability at a glance.
88) Build an “Index” Sheet with Links to Tabs
function buildSheetIndex() {
const ss = SpreadsheetApp.getActive();
const idx = ss.getSheetByName(‘Index’) || ss.insertSheet(‘Index’);
idx.clear();
idx.getRange(‘A1:B1’).setValues([[‘Sheet Name’,’Link’]]).setFontWeight(‘bold’);
const rows = ss.getSheets().filter(s=>s.getName()!==’Index’).map(s => [s.getName(), `=HYPERLINK(“#gid=${s.getSheetId()}”, “Open”)`]);
if (rows.length) idx.getRange(2,1,rows.length,2).setValues(rows);
}
What & why: Quick navigation hub.
89) Protect Ranges by Header Name (lock everything except allowed)
function protectExceptAllowedHeaders() {
const allowed = [‘Notes’,’Status’]; // editable columns
const sh = SpreadsheetApp.getActiveSheet();
const head = sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
const protection = sh.protect().setDescription(‘Protected except some columns’);
protection.removeEditors(protection.getEditors());
const unprotected = head.map((h,i)=> allowed.includes(String(h)) ? sh.getRange(1,i+1,sh.getMaxRows(),1) : null).filter(Boolean);
protection.setUnprotectedRanges(unprotected);
}
What & why: Control edits by column names.
90) Data Validation from Named Range
function validateFromNamedRange() {
const ss = SpreadsheetApp.getActive();
const r = ss.getRangeByName(‘StatusList’); // e.g., a named list
if (!r) throw new Error(‘Named range “StatusList” not found.’);
const rule = SpreadsheetApp.newDataValidation().requireValueInRange(r, true).setAllowInvalid(false).build();
SpreadsheetApp.getActiveRange().setDataValidation(rule);
}
What & why: Centralized list control, easy to update.
S. Sheet Generation & Mass Actions
91) Duplicate Template for Each Value in a List
function duplicateTemplateForList() {
const ss = SpreadsheetApp.getActive();
const template = ss.getSheetByName(‘Template’);
const listing = ss.getSheetByName(‘Names’); // column A list
const names = listing.getRange(2,1,listing.getLastRow()-1,1).getValues().map(r=>String(r[0])).filter(Boolean);
names.forEach(n => {
const copy = template.copyTo(ss);
copy.setName(n.substring(0, 99));
copy.getRange(‘B1’).setValue(n); // example fill
});
}
What & why: Batch-generate personalized tabs.
92) Compact Data: Remove Blank Rows Within Data Range
function compactRemoveBlankRows() {
const sh = SpreadsheetApp.getActiveSheet();
const range = sh.getDataRange();
const data = range.getValues();
const header = data.shift();
const filtered = data.filter(r => r.some(c => c !== ” && c !== null));
sh.clearContents();
sh.getRange(1,1,1,header.length).setValues([header]);
if (filtered.length) sh.getRange(2,1,filtered.length,header.length).setValues(filtered);
}
What & why: Eliminates pesky empty rows inside your table.
93) Append Selection to “log” (values only)
function appendSelectionToLog() {
const ss = SpreadsheetApp.getActive();
const log = ss.getSheetByName(‘log’) || ss.insertSheet(‘log’);
const sel = SpreadsheetApp.getActiveRange();
const vals = sel.getValues();
vals.forEach(row => log.appendRow([new Date(), …row]));
}
What & why: Quick paste-as-values history.
94) Toggle “Done” Checkbox → Set Completed Timestamp
function onEditToggleDoneTimestamp(e) {
const sh = e.range.getSheet();
const head = sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
const doneCol = head.indexOf(‘Done?’) + 1;
const tsCol = head.indexOf(‘Completed At’) + 1;
if (!doneCol || !tsCol) return;
if (e.range.getColumn() === doneCol && e.range.getRow() > 1) {
if (String(e.value) === ‘TRUE’) sh.getRange(e.range.getRow(), tsCol).setValue(new Date());
else sh.getRange(e.range.getRow(), tsCol).clearContent();
}
}
What & why: Lightweight task tracking.
95) Fill Missing Dates in a Daily Series (A column)
function fillMissingDatesDaily() {
const sh = SpreadsheetApp.getActiveSheet();
const dates = sh.getRange(2,1,sh.getLastRow()-1,1).getValues().map(r=>r[0]).filter(Boolean).sort((a,b)=>a-b);
if (!dates.length) return;
const start = new Date(dates[0]), end = new Date(dates[dates.length-1]);
const set = new Set(dates.map(d=>new Date(d).toDateString()));
const fill = [];
for (let d = new Date(start); d <= end; d.setDate(d.getDate()+1)) {
if (!set.has(d.toDateString())) fill.push([new Date(d)]);
}
if (fill.length) sh.getRange(sh.getLastRow()+1,1,fill.length,1).setValues(fill);
}
What & why: Ensures continuity for charts/time-series.
96) Generate Business Days Between Two Cells
Takes start in B1, end in B2, writes to column D.
function listBusinessDays() {
const sh = SpreadsheetApp.getActiveSheet();
const start = new Date(sh.getRange(‘B1’).getValue());
const end = new Date(sh.getRange(‘B2’).getValue());
const out = [];
for (let d = new Date(start); d <= end; d.setDate(d.getDate()+1)) {
const day = d.getDay();
if (day !== 0 && day !== 6) out.push([new Date(d)]);
}
sh.getRange(2,4,out.length,1).setValues(out); // column D
}
What & why: Helps plan workloads & SLAs.
97) Create a Calendar Grid for a Month
Year in B1, month (1–12) in B2. Outputs a 6×7 grid.
function buildMonthCalendar() {
const sh = SpreadsheetApp.getActiveSheet();
const year = Number(sh.getRange(‘B1’).getValue());
const month = Number(sh.getRange(‘B2’).getValue()) – 1;
const first = new Date(year, month, 1);
const start = new Date(first);
start.setDate(first.getDate() – ((first.getDay()+6)%7)); // start Monday
const grid = [];
for (let r=0; r<6; r++) {
const row = [];
for (let c=0; c<7; c++) {
row.push(new Date(start));
start.setDate(start.getDate()+1);
}
grid.push(row);
}
const out = sh.getRange(‘E2:K7’);
out.setNumberFormat(‘d’); out.setValues(grid);
sh.getRange(‘E1:K1’).setValues([[‘Mon’,’Tue’,’Wed’,’Thu’,’Fri’,’Sat’,’Sun’]]).setFontWeight(‘bold’);
}
What & why: Roll-your-own calendar inside Sheets.
98) Email Reminders for Tasks Due in 3 Days
Assumes columns: Task | Due Date | Assignee Email.
function emailDueSoon() {
const sh = SpreadsheetApp.getActive().getSheetByName(‘Tasks’);
const data = sh.getDataRange().getValues();
const head = data.shift();
const idxTask = head.indexOf(‘Task’);
const idxDue = head.indexOf(‘Due Date’);
const idxEmail = head.indexOf(‘Assignee Email’);
const now = new Date();
const cutoff = new Date(now.getFullYear(), now.getMonth(), now.getDate()+3);
data.forEach(r => {
const due = r[idxDue];
const to = r[idxEmail];
if (due instanceof Date && due <= cutoff && due >= now && to) {
MailApp.sendEmail(String(to), ‘Task due soon: ‘ + r[idxTask], `Hi,\n\nReminder: “${r[idxTask]}” is due on ${due}.\n\nThanks`);
}
});
}
What & why: Nudge people before deadlines slip.
99) Remove All Conditional Formatting Rules (sheet)
function clearConditionalFormatting() {
const sh = SpreadsheetApp.getActiveSheet();
sh.setConditionalFormatRules([]);
}
What & why: Start fresh when rules pile up.
100) Safe Delete Sheets Except a Whitelist
function deleteSheetsExcept() {
const keep = new Set([‘Index’,’Template’,’Data’,’KPIs’]); // edit me
const ss = SpreadsheetApp.getActive();
ss.getSheets().forEach(sh => {
if (!keep.has(sh.getName())) ss.deleteSheet(sh);
});
}
What & why: Clean a workbook without nuking critical tabs.