appscript.dev
Guide Beginner

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.

  1. Open the script in the editor.
  2. Click the gutter to the left of a line number — a red dot marks a breakpoint.
  3. 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.log output.
  • 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.

SituationBest tool
A function you can run by handBreakpoints / debugger
A trigger that already failedExecutions panel
A bug that only appears in productionconsole.log + Executions
An intermittent fault you cannot reproduceTracing 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.log calls in production code. Trim them, or gate them behind a DEBUG flag.
  • 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.stringify on a circular object throws. Log the specific fields you need, or wrap the stringify in a try.