Google Apps Script Error Handling Best Practices

Unhandled errors in production scripts are the worst — a trigger silently fails at 2 AM and nobody notices until Monday. Here's how to write Apps Script that handles failures gracefully, notifies you when things go wrong, and recovers automatically where possible.

Basic try/catch

function riskyOperation() { try { const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); const value = sheet.getRange('A1').getValue(); // ... do something with value Logger.log('Success: ' + value); } catch (error) { Logger.log('Error: ' + error.message); Logger.log('Stack: ' + error.stack); } }

Get notified by email when a trigger fails

function runWithErrorAlert(fn, fnName) { try { fn(); } catch (error) { const message = ` Script: ${fnName} Error: ${error.message} Stack: ${error.stack} Time: ${new Date().toLocaleString()} `.trim(); GmailApp.sendEmail( Session.getActiveUser().getEmail(), `⚠️ Apps Script Error: ${fnName}`, message ); Logger.log('Error alert sent: ' + error.message); throw error; // Re-throw so Apps Script also logs it } } // Wrap your main function function myDailyJob() { runWithErrorAlert(() => { // Your actual logic here processData(); }, 'myDailyJob'); }

Log errors to a Google Sheet

For persistent error history:

function logError(functionName, error) { const ss = SpreadsheetApp.getActiveSpreadsheet(); let logSheet = ss.getSheetByName('Error Log'); if (!logSheet) { logSheet = ss.insertSheet('Error Log'); logSheet.appendRow(['Timestamp', 'Function', 'Error', 'Stack']); } logSheet.appendRow([ new Date(), functionName, error.message, error.stack || '', ]); }

Retry logic for flaky operations

APIs and Sheets operations can fail transiently. Retry with exponential backoff:

function withRetry(fn, maxAttempts = 3, delayMs = 1000) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return fn(); } catch (error) { if (attempt === maxAttempts) throw error; Logger.log(`Attempt ${attempt} failed: ${error.message}. Retrying in ${delayMs}ms...`); Utilities.sleep(delayMs); delayMs *= 2; // Exponential backoff } } } // Usage: function fetchWithRetry() { return withRetry(() => { const response = UrlFetchApp.fetch('https://api.example.com/data', { muteHttpExceptions: true, }); if (response.getResponseCode() !== 200) { throw new Error(`API returned ${response.getResponseCode()}`); } return JSON.parse(response.getContentText()); }); }

Validate inputs before processing

function processRow(row) { const [id, email, amount] = row; if (!id) throw new Error('Missing ID'); if (!email || !email.includes('@')) throw new Error(`Invalid email: ${email}`); if (isNaN(amount) || amount < 0) throw new Error(`Invalid amount: ${amount}`); // Safe to proceed Logger.log(`Processing: ${id}, ${email}, $${amount}`); }

Handle specific error types

function handleSpecificErrors() { try { const file = DriveApp.getFileById('NONEXISTENT_ID'); } catch (error) { if (error.message.includes('No item with the given ID')) { Logger.log('File not found — it may have been deleted.'); } else if (error.message.includes('Access denied')) { Logger.log('Permission error — check sharing settings.'); } else { throw error; // Unknown error — re-throw } } }

Set up Apps Script's built-in error notifications

Apps Script can email you automatically when a trigger-based execution fails:

  1. Open the script editor.
  2. Go to Triggers (clock icon in the left sidebar).
  3. Edit your trigger.
  4. Under Failure notification settings, choose your notification frequency.

This is a quick win — enable it for all production triggers.

Summary checklist

PracticeBenefit
Wrap triggers in try/catchPrevents silent failures
Send email on errorImmediate notification
Log errors to a SheetPersistent history for debugging
Retry transient failuresResilience against flaky APIs
Validate inputs upfrontClearer error messages
Re-throw unknown errorsEnsures nothing is silently swallowed
Enable built-in failure notificationsZero-effort baseline alerting