Debug Apps Script effectively
Logging, breakpoints, and execution transparency — find bugs faster in Northwind scripts.
Published Jul 15, 2025
Debugging Apps Script is different from debugging a normal program. There is no
terminal, no print you watch scroll by, and much of your code runs on triggers
when you are not even at the keyboard. The bug you need to fix often happened
hours ago, in a run nobody saw.
Effective debugging means knowing which tool fits which situation: the debugger for code you can run by hand, the execution log for code that already ran, and tracing for the awkward middle ground where neither quite works. This guide covers all three and when to reach for each.
Logging
Logging is the workhorse — a value printed at the right moment usually finds the bug faster than anything else.
console.log(value)— appears in the Execution log in the modern editor, and also flows to Cloud Logging where it persists across runs.Logger.log(value)— the legacy logger; still works and shows in the same panel.
Prefer console.log. Log labelled values — console.log('row count', rows.length)
beats a bare number you have to guess the meaning of. For objects, log them
directly; the editor renders them expandably.
Breakpoints
For code you can run yourself, the interactive debugger beats scattering log lines. It pauses execution so you can inspect the program mid-flight.
- Open the script in the editor.
- Click the gutter to the left of a line number — a red dot marks a breakpoint.
- Choose the function in the toolbar and click Debug (the bug icon).
Execution pauses at the breakpoint. The right-hand pane shows every variable in scope, and the step controls let you walk forward line by line, into a call, or out of it. This is the fastest way to answer “what is this variable actually holding right now”.
The execution log
The debugger only helps with code you trigger by hand. Trigger-driven and time-based functions run on their own, and the Executions panel is your only window into them.
- Executions in the left sidebar lists every run — manual, triggered, or
scheduled — with its status, duration, and any
console.logoutput. - A failed run shows the error and stack trace. A run that never appears means the trigger did not fire at all.
- Logs here persist after the run ends, so you can debug yesterday’s failure today.
Check this panel first whenever a triggered job misbehaves.
Choosing the right tool
Each tool fits a different situation. Match the tool to how the code runs.
| Situation | Best tool |
|---|---|
| A function you can run by hand | Breakpoints / debugger |
| A trigger that already failed | Executions panel |
| A bug that only appears in production | console.log + Executions |
| An intermittent fault you cannot reproduce | Tracing wrapper |
When the debugger isn’t enough
The debugger cannot attach to a trigger, and an intermittent bug may refuse to appear while you are watching. For those cases, wrap the suspect function in a tracer that logs its inputs and outputs automatically on every run.
// Wrap a function so each call logs its arguments, its return value,
// and any error — without editing the function's own body.
function trace(fn, name = fn.name) {
return (...args) => {
// Log inputs on entry. Truncate so a huge argument cannot flood the log.
console.log('→', name, JSON.stringify(args).slice(0, 200));
try {
const result = fn(...args);
// Log the return value on a clean exit.
console.log('←', name, JSON.stringify(result).slice(0, 200));
return result;
} catch (e) {
// Log the failure, then re-throw so behaviour is unchanged.
console.log('✗', name, e.message);
throw e;
}
};
}
Apply it with const syncOrders = trace(syncOrders). Every run now leaves a
breadcrumb trail in the Executions panel, so when the intermittent failure
finally happens you have the exact inputs that triggered it.
Common mistakes
- Reaching for the debugger on a trigger. It cannot attach — use the Executions panel and logging instead.
- Logging bare values with no label, then being unable to tell which line produced which line of output.
- Leaving noisy
console.logcalls in production code. Trim them, or gate them behind aDEBUGflag. - Assuming a missing failure means success. If a run does not appear in the Executions panel at all, the trigger never fired — that is the bug.
JSON.stringifyon a circular object throws. Log the specific fields you need, or wrap the stringify in atry.