Google Apps Script for beginners: spreadsheet automation that compounds
every Sheets user eventually hits a wall. ARRAYFORMULA and QUERY cover most of the everyday work, but some tasks need procedural code: loop through every customer in a list, send each one a personalized email, log the result. or fetch a daily exchange rate from an API and write it to a sheet. or run a cleanup script every night at 2am.
Google Apps Script is the answer. it is a JavaScript-based scripting platform that lives inside your Google account, ships with every Google Workspace product, and runs on Google’s servers for free. you do not install anything. you do not host anything. you write a script, click Run, and it executes.
this guide takes a non-coder through their first three Apps Scripts: a custom Sheets function, a daily email digest, and a time-triggered automation. we use a realistic solopreneur stack (sales tracker, customer list, daily KPI reporting) so the patterns mirror real automation work. by the end you will have working scripts you can adapt for your own business.
Google Apps Script is a JavaScript-based scripting platform built into Google Workspace. It can automate Sheets, Gmail, Drive, Calendar, Docs, and Forms with no separate hosting. Workflow: open Extensions → Apps Script in any Google file, write a function, click Run, authorize, and execute. Scripts can run on demand, on a time-driven trigger (every minute, hour, day), or on user events (onEdit, onOpen). Custom functions extend Sheets with new formulas. The free quota is generous enough for most solopreneur automation needs.
what Apps Script is and what it can do
Apps Script is JavaScript that runs server-side in Google’s infrastructure with built-in libraries for every Google Workspace service. you can:
- read and write any cell in any sheet you have access to
- send emails from your Gmail account
- create, edit, and share Google Drive files
- read and write Google Calendar events
- call external APIs (REST, GraphQL) and use the response
- run on a schedule (every 5 minutes, hourly, daily at 6am)
- respond to user actions (onEdit, onOpen, button click)
- build custom Sheets functions that work just like SUM or VLOOKUP
three concrete examples of what solopreneurs use Apps Script for:
- daily KPI digest: every morning at 7am, pull yesterday’s revenue from a Sheets dashboard, format it, email to yourself.
- lead nurture: when a new row is added to a leads sheet, automatically send a templated welcome email.
- CSV cleanup: a button on a worksheet runs a script that trims spaces, fixes dates, and deduplicates the active sheet.
if a task involves Google Workspace + repetition, Apps Script can probably automate it.
the Apps Script editor
every Google file has an Apps Script editor accessible from Extensions → Apps Script.
what you see when you open it:
- Code.gs — the default file holding your script
- Files sidebar — add more .gs files for organization
- Triggers (clock icon) — schedule scripts to run automatically
- Executions (list icon) — log of recent runs and any errors
- Run button — run the active function immediately
- Save (Ctrl+S) — saves the project; required before Run
[SCREENSHOT: Apps Script editor with Code.gs file open, Run button visible at the top, Triggers and Executions icons in the left sidebar]
scripts attached to a Sheet are called “container-bound” — they only work in that file. for scripts that should work across files, create a “standalone” project at script.google.com.
your first Apps Script: a custom Sheets function
custom functions are the gentlest entry point. you write a JavaScript function and call it from a cell like any built-in formula.
problem: Sheets has no built-in INITIALS function that returns a name’s initials.
step 1: open the editor
- open any Sheets file.
- Extensions → Apps Script.
step 2: write the function
replace the default code with:
function INITIALS(fullName) {
if (!fullName) return "";
return fullName
.split(" ")
.map(word => word.charAt(0).toUpperCase())
.join("");
}
step 3: save the script
press Ctrl+S. give the project a name like “Sheets Helpers”. click Save.
step 4: use the function
go back to the Sheets file. in any cell type:
=INITIALS("Sarah Lee")
result: “SL”.
now use it on a column of names:
=INITIALS(C2)
drag down. or use ARRAYFORMULA for the auto-extending version:
=ARRAYFORMULA(IF(C2:C="", "", INITIALS(C2:C)))
wait — that last one might not work. custom functions called from ARRAYFORMULA need extra handling. fix:
function INITIALS(input) {
if (Array.isArray(input)) {
return input.map(row => row.map(cell => cellInitials(cell)));
}
return cellInitials(input);
}
function cellInitials(name) {
if (!name) return "";
return name.toString().split(" ").map(w => w.charAt(0).toUpperCase()).join("");
}
now INITIALS handles both single cells and ranges, working with ARRAYFORMULA.
[SCREENSHOT: Sheets sheet with column C showing names and column D showing initials, formula bar showing =ARRAYFORMULA(IF(C2:C=””, “”, INITIALS(C2:C)))]
your second Apps Script: a daily email digest
problem: every morning you want yesterday’s total revenue emailed to you with a one-line summary.
step 1: prepare the data
assume your sales tracker has a sheet called “Orders” with Date in column B and Revenue in column G.
step 2: write the script
function sendDailyDigest() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Orders");
const data = sheet.getRange("A2:G" + sheet.getLastRow()).getValues();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = Utilities.formatDate(yesterday, Session.getScriptTimeZone(), "yyyy-MM-dd");
let total = 0;
let orderCount = 0;
data.forEach(row => {
const orderDate = Utilities.formatDate(new Date(row[1]), Session.getScriptTimeZone(), "yyyy-MM-dd");
if (orderDate === yesterdayStr) {
total += row[6];
orderCount++;
}
});
const email = Session.getActiveUser().getEmail();
const subject = `Daily revenue: $${total.toFixed(2)} (${orderCount} orders)`;
const body = `Yesterday (${yesterdayStr}):\n\nRevenue: $${total.toFixed(2)}\nOrders: ${orderCount}\n\nDashboard: ${SpreadsheetApp.getActiveSpreadsheet().getUrl()}`;
MailApp.sendEmail(email, subject, body);
}
step 3: authorize and test
- save the script.
- click Run with
sendDailyDigestselected. - Apps Script asks for permission to access Sheets and Gmail. click Continue → choose your account → Advanced → Go to project (unsafe) → Allow.
- the script runs. check your inbox for the digest.
the “unsafe” warning is normal for custom scripts because they have not been Google-verified. for personal use, it is safe.
step 4: schedule it
- in the Apps Script editor, click the Triggers icon (clock) in the left sidebar.
- click Add Trigger.
- set:
– Function: sendDailyDigest
– Event source: Time-driven
– Type: Day timer
– Time of day: 7am to 8am - click Save.
every morning between 7-8am, the script runs and emails you the digest. no further intervention needed.
your third Apps Script: an onEdit trigger
problem: whenever a row is added to a “Leads” sheet, automatically send a welcome email.
step 1: write the script
function onEdit(e) {
const sheet = e.source.getActiveSheet();
if (sheet.getName() !== "Leads") return;
const range = e.range;
if (range.getRow() === 1) return;
if (range.getColumn() !== 1) return;
const row = range.getRow();
const name = sheet.getRange(row, 1).getValue();
const email = sheet.getRange(row, 2).getValue();
if (!email || !name) return;
const subject = `Welcome, ${name}`;
const body = `Hi ${name},\n\nThanks for reaching out. I'll follow up with you within 24 hours.\n\nBest,\nXavier`;
MailApp.sendEmail(email, subject, body);
sheet.getRange(row, 5).setValue("Welcome sent");
}
step 2: save and test
- save the script.
- open the Leads sheet.
- add a new row with Name in A, Email in B.
- as soon as you press Enter, the welcome email is sent and column E shows “Welcome sent”.
onEdit is a special trigger — Apps Script automatically runs it whenever the user edits the file. no manual trigger setup needed.
note: onEdit only runs when a user edits the sheet, not when other scripts edit it. for bulk imports, you need a different approach. and for any onEdit script that calls external services (like email), you must add an installable trigger via the Triggers panel — simple onEdit cannot use Mail or external APIs.
Apps Script vs ARRAYFORMULA vs QUERY
three Sheets automation tools. when do you use each?
| tool | best for | learning curve |
|---|---|---|
| ARRAYFORMULA | vectorizing simple formulas across columns | low |
| QUERY | filter + group + pivot in one formula | medium |
| Apps Script | procedural logic, external APIs, scheduling, emails | medium-high |
start with ARRAYFORMULA and QUERY. graduate to Apps Script only when:
- the logic involves an external service (email, calendar, drive, API)
- the task should run on a schedule
- the calculation requires loops or conditionals that formulas cannot express cleanly
- you need to write back to other files based on changes
most solopreneurs use Apps Script sparingly — a handful of triggered scripts that handle the 5% of automation that formulas cannot.
the Apps Script quota system
free Apps Script accounts have daily quotas. the most relevant ones:
| quota | free tier limit |
|---|---|
| script runtime per execution | 6 minutes |
| total daily runtime | 90 minutes |
| email recipients per day | 100 |
| URL fetch calls per day | 20,000 |
| triggers per script | 20 |
| Sheets API calls per day | 50,000 |
for solopreneur automation, these are generous. you would have to email 101+ people daily to hit the email quota. the runtime quota is more restrictive — long batch jobs need to be broken into smaller pieces.
if you hit a quota, scripts fail silently with an error in the Executions log. always check Executions if your script suddenly stops working.
debugging Apps Script
three tools for finding what went wrong:
tool 1: console.log
add log statements to see what is happening:
console.log("Total so far:", total);
view the output in View → Logs (Ctrl+Enter) immediately after a Run.
tool 2: Executions panel
every script run is logged in the Executions panel (left sidebar, list icon). shows start time, runtime, status, and any error. essential for debugging time-triggered scripts.
tool 3: try/catch with notification
wrap risky code in try/catch to recover gracefully:
try {
// risky code
} catch (err) {
MailApp.sendEmail(Session.getActiveUser().getEmail(), "Script error", err.toString());
}
now if a scheduled script fails at 3am, you get an email instead of silent failure.
common Apps Script mistakes and fixes
mistake 1: forgetting to authorize
new scripts must be authorized once. click Run, work through the auth dialogs. without auth, triggered runs will fail.
mistake 2: using getValue inside a loop
for (let i = 2; i <= 1000; i++) {
const value = sheet.getRange(i, 1).getValue();
}
each getValue is a separate API call. 1,000 rows = 1,000 calls. very slow.
fix:
const values = sheet.getRange(2, 1, 999, 1).getValues();
for (let i = 0; i < values.length; i++) {
const value = values[i][0];
}
one batch call, instant.
mistake 3: hardcoding sheet IDs
const sheet = SpreadsheetApp.openById("1abcd1234").getSheetByName("Orders");
if the sheet ID changes, the script breaks. for container-bound scripts, prefer:
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Orders");
mistake 4: triggers that consume quota silently
a trigger set to run every minute consumes quota even when nothing changes. set the lowest necessary frequency. daily is usually enough for most solopreneur automations.
related tutorials on DRAC
- Google Sheets QUERY function complete guide — handle most aggregation needs without scripts
- Google Sheets ARRAYFORMULA complete walkthrough — vectorize formulas before reaching for Apps Script
- IMPORTRANGE Google Sheets tutorial — Apps Script can refresh IMPORTRANGE on a schedule
- Looker Studio complete tutorial 2026 — when your dashboard outgrows Sheets entirely
conclusion: Apps Script earns its keep on the 5%
most spreadsheet automation lives in formulas. the last 5% — the time-triggered emails, the API integrations, the cross-file syncs — needs procedural code. Apps Script handles that 5% without infrastructure, hosting, or paid services.
start with the custom function example. it is the lowest-stakes way to get used to the editor and the auth flow. once you have one custom function in production, add a daily digest with a time trigger. then add an onEdit trigger for a workflow that needs to fire on user input. by the third script, the editor stops feeling foreign.
the compounding payoff: every Apps Script you write becomes infrastructure for your business. one script writes your morning report forever. another keeps two sheets in sync forever. another sends every new lead a welcome email forever. each is a one-time cost with permanent return.
next action: pick one repetitive Sheets task you do every week. write the simplest possible Apps Script that automates it. set a time-driven trigger. let it run for one month and tally the time saved. that ratio is what Apps Script is worth to you.