Google Workspace Automation

How Do You Add Error Handling and Retry Logic to a Google Apps Script Automation?

Last updated 23 July 2026 · 6 min read

Direct Answer

Add error handling to a Google Apps Script automation by wrapping the risky part of the script in a try/catch block, logging the specific error caught (with `console.error` or a dedicated logging Sheet) rather than letting the script fail silently, and — for calls likely to hit a transient problem, like an external API request or a Google service under momentary load — building a manual retry loop with exponential backoff around just that call, since Apps Script has no built-in retry policy the way some platforms do. Pair this with an explicit failure notification (`MailApp.sendEmail` to the script owner, or a message posted into Google Chat) inside the catch block, so a real failure reaches a person immediately instead of relying on Google's own automatic trigger-disabling as the only signal something went wrong.

Detailed Explanation

A Google Apps Script that runs unattended — on a time-driven trigger, or firing off a form submission or an edit event — has no supervisor watching it work. Left with no error handling, a script that hits a problem partway through either stops with an unhandled exception that Google logs somewhere nobody's looking, or, worse, continues past a step that silently failed and leaves downstream data wrong with no record of what happened. This is a construction-time discipline, distinct from why did your Google Apps Script trigger stop running, which covers diagnosing a trigger that has already gone quiet — this page covers building a script that handles its own failures gracefully in the first place, so there's less to diagnose later.

Because Apps Script is genuine JavaScript code rather than a visual flow-builder, the tools are the standard programming ones — try/catch blocks and a manually written retry loop — rather than a platform feature you configure through a menu. That's a meaningful difference from Power Automate's approach, which builds the same try/catch/finally shape out of Scope actions and a default per-action retry policy the platform applies automatically; Apps Script gives you the same underlying pattern, but every part of it has to be written explicitly.

Building the Pattern

1. Wrap the risky part of the script in try/catch, not the whole function. Put the specific steps that can plausibly fail — an external API call, a reference to a Sheet or Doc that might have been moved or deleted, a Gmail or Calendar operation touching data outside the script's own control — inside a try block, and keep straightforward, low-risk operations outside it so the catch block's error handling stays specific to genuine failure points.

function processInvoice(row) {
  try {
    const response = UrlFetchApp.fetch(apiUrl, options);
    // process response
  } catch (error) {
    logError_('processInvoice', error, row);
    notifyOwner_('Invoice processing failed', error.message);
  }
}

2. Log the actual error, not just that something failed. console.error(error) writes to the Executions log with the real stack trace and message; for a script where you want a persistent, searchable record beyond what the Executions log retains, log the timestamp, function name, and error message to a dedicated Sheet instead of (or alongside) the console.

3. Build a manual retry loop around calls prone to transient failure. Apps Script has no automatic retry policy, so a call to an external API or a Google service under momentary load needs its own loop — attempt the call, catch a failure, wait using Utilities.sleep() with an increasing delay (exponential backoff: roughly 1 second, then 2, then 4), and give up after a capped number of attempts rather than retrying indefinitely.

function fetchWithRetry_(url, options, maxAttempts) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return UrlFetchApp.fetch(url, options);
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      Utilities.sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

4. Send an explicit notification from inside the catch block. MailApp.sendEmail() to the script's owner, or a message posted into a monitoring space via Google Chat automation, turns a caught error into something a person actually sees promptly, rather than something that only shows up if someone happens to open the Executions log.

5. Decide what happens after a caught failure — stop, skip, or continue. For a script processing multiple rows or records in one run, an unhandled failure on row 12 can either crash the entire run (leaving rows 13 onward unprocessed) or, with a try/catch inside the loop, log that one row's failure and continue to the next — the right choice depends on whether the remaining rows are independent of the failed one.

6. Set a retry cap and a genuine give-up path. Retrying forever against a permanently broken dependency (a deleted external endpoint, a permission that was never restored) just delays the same eventual failure — cap the attempts, and make the final failure after the cap loud (a notification), not silent.

Things to Consider

  • This reduces, but doesn't replace, Google's own automatic trigger-disabling. A script with solid error handling still hits Apps Script's execution-time and daily quotas, covered in why did your Google Apps Script trigger stop running — good error handling means you find out about a failure immediately via your own notification, rather than only when Google disables the trigger after repeated consecutive failures.
  • Retrying isn't the right response to every failure. A permission error or a resource that's been permanently deleted won't resolve itself no matter how many times the call is retried — reserve retry loops for genuinely transient conditions (a momentary timeout, a rate limit) and let anything else fail straight to the catch block's notification.
  • Not every function needs this treatment. A short script with an easy, obvious manual recovery — rerun it, redo the one step — often isn't worth the added complexity; reserve explicit error handling and retry logic for scripts where an unnoticed partial failure would actually cost something (a business process depending on it, data reaching an inconsistent state).
  • A script that itself sends the only alert for a broken process is a single point of failure. If the script's job is to notify someone else when something goes wrong elsewhere, an unhandled failure in the script means nobody gets told anything failed at all — apply the same error handling described here to alerting scripts especially.

Common Mistakes

  • Wrapping code in try/catch but leaving the catch block empty or just logging to the console. A caught error that goes nowhere a person will see provides barely more protection than no error handling at all — always pair a catch block with a real notification for anything that matters operationally.
  • Retrying an API call indefinitely with no cap. An uncapped retry loop against a call that's failing for a non-transient reason can itself exhaust the script's execution-time quota, turning one failure into a second, unrelated failure.
  • Treating a broad try/catch around an entire function as equivalent to targeted error handling. Wrapping everything in one block makes it harder to tell which specific step failed — scope try/catch blocks around the specific risky operations, not the whole function indiscriminately.
  • Assuming the Executions log is sufficient visibility on its own. Most scripts run unattended for long stretches; without an explicit notification triggered from the catch block, a real failure can sit unnoticed in the log for days.

Frequently Asked Questions

Does Apps Script have a built-in retry policy like some other automation platforms?
No — unlike a platform such as Power Automate, which applies a default retry policy to most connector actions automatically, Apps Script runs your code exactly as written with no automatic retry behavior. Any retry logic — waiting and re-attempting a failed call a set number of times — has to be written explicitly inside the script itself, typically as a loop around the specific call that's prone to transient failure.
Should every function in a script be wrapped in try/catch?
No — reserve it for steps where a failure is plausible and where knowing about it matters: calls to an external API, operations on a resource that might have been moved or deleted, or a step where a partial failure would leave data in an inconsistent state. Wrapping simple, low-risk operations (reading a cell value already confirmed to exist) in try/catch mostly adds noise without adding real resilience.
Is logging to the Apps Script Executions log enough, or do you need a separate notification?
The Executions log is enough for a script someone checks regularly, but most scripts run unattended, which means an error sitting only in the log goes unnoticed until something downstream visibly breaks. An explicit notification (email or a chat message) triggered from inside the catch block is what actually gets a failure in front of a person promptly, rather than depending on someone thinking to check the log.

References

Related Questions