Automation

Apps Script 6-Minute Execution Limit: Official Quota

Marc SeanJuly 8, 20267 min read

That's the short version. The longer version matters if you're trying to automate anything meaningful in a multi-tab financial model.

The Official Quota Numbers

According to Google's Apps Script quotas documentation (verified July 2026):

Account typeMax execution time per run
Consumer (free @gmail.com)6 minutes
Google Workspace (paid)6 minutes

Time-based triggers are subject to separate daily caps:

Account typeTotal daily trigger runtime
Consumer90 minutes/day
Google Workspace6 hours/day

There's also a cap of 30 simultaneous executions per script, and the Sheets API quota of 300 write requests per minute per project can force Utilities.sleep() calls that eat into your execution window before you've accomplished anything.

Workspace's larger daily trigger-runtime allowance does not extend an individual run. A script that needs more than 6 minutes still needs batching and checkpoints.

Why FP&A Models Hit This Wall

A simple =SUMIFS or =INDEX/MATCH formula doesn't use Apps Script at all. The problem starts when you need to do things formulas can't:

Data ingestion loops. Pulling actuals from a REST endpoint row by row, matching against your Assumptions! tab, and writing results back. A 24-month model with 40 line items is 960 individual Sheets API calls minimum. At typical latency, you're looking at 3-4 minutes for that alone - before any calculation logic.

Cross-tab reconciliation. Scripts that read from P&L, Balance Sheet, and Cash Flow simultaneously, check that retained earnings ties, and flag discrepancies. Reading ranges across multiple tabs, doing the comparison, and writing a status column back adds up fast.

Board pack formatting. Looping through 12 monthly columns, applying conditional formatting, hiding zero rows, setting print areas, and generating PDFs. The PDF generation step alone - using DriveApp.createFile() - can consume 45-90 seconds per sheet.

IMPORTDATA refresh workarounds. Because IMPORTDATA and IMPORTXML have their own refresh problems (see Auto Update Google Sheets), some analysts use Scripts to delete and re-enter the formula to force a refresh. Doing this across 15 cells with a sleep between each one chews through the budget.

What Actually Kills Your Script (It's Not What You Think)

The obvious culprit is loop count. The less obvious one is SpreadsheetApp.flush().

Every getValue() or setValue() call inside a loop triggers a round-trip to Google's servers. A script doing this:

// Slow: 500 API round-trips
for (let i = 2; i <= 501; i++) {
  let val = sheet.getRange(i, 3).getValue();      // 1 API call
  sheet.getRange(i, 4).setValue(val * 1.085);     // 1 API call
}

...makes 1,000 API calls for 500 rows. At ~30ms per call in ideal conditions, that's 30 seconds just in I/O. Add network jitter, quota backoff, and any Utilities.sleep() calls to avoid rate limits, and you can hit the wall at 300 rows.

The fix is batching:

// Fast: 2 API calls total
const data = sheet.getRange(2, 3, 500, 1).getValues();   // read all at once

// Apply growth rate in memory - no API calls in the loop
const updated = data.map(row => [row[0] * 1.085]);

sheet.getRange(2, 4, 500, 1).setValues(updated);          // write all at once

This processes the same 500 rows in roughly 200ms of I/O. The difference is 30 seconds vs 0.2 seconds - that's not a micro-optimization, it's whether your quarterly refresh completes at all.

The Continuation Pattern: Running Past 6 Minutes

When batching still isn't enough - when you're processing 5,000 rows or running multi-step operations across tabs - the standard approach is to save state and restart.

Apps Script provides PropertiesService.getScriptProperties() as persistent storage that survives between executions. You checkpoint your progress, kill the current run before the timer does it for you, and schedule the next chunk.

function processActualsWithContinuation() {
  const props = PropertiesService.getScriptProperties();
  const startTime = Date.now();
  const MAX_RUNTIME_MS = 5 * 60 * 1000; // 5 min - stop before the 6-min wall hits

  // Resume from where we left off, or start at row 2
  let currentRow = parseInt(props.getProperty('lastRow') || '2');

  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Actuals');
  const lastRow = sheet.getLastRow();

  while (currentRow <= lastRow) {
    // Check time budget every 100 rows
    if (currentRow % 100 === 0 && (Date.now() - startTime) > MAX_RUNTIME_MS) {
      props.setProperty('lastRow', currentRow.toString()); // save progress
      // Schedule ourselves to run again in 1 minute
      ScriptApp.newTrigger('processActualsWithContinuation')
        .timeBased()
        .after(60 * 1000)
        .create();
      return; // graceful exit
    }

    // Your processing logic here
    // ...

    currentRow++;
  }

  // Finished - clear state and remove any pending triggers
  props.deleteProperty('lastRow');
}

This works, but it has real costs: latency between chunks, trigger accumulation if you're not cleaning up, and debugging complexity when something fails mid-continuation. It's the right answer when you need it, but it's not a pleasant development experience.

Other Quotas That Bite Without Warning

The 6-minute limit is the most famous, but these catch people in production:

Spreadsheet read/write rate: 300 requests/minute per project. If your script calls getValues() in rapid succession across tabs, you'll hit this and need Utilities.sleep(200) between calls - which then compounds your execution time problem.

Email quota: 100 emails/day (consumer), 1,500/day (Workspace). A quarterly board pack distribution script that sends to a 200-person list will fail on consumer accounts. On Workspace, a daily digest plus ad-hoc sends can approach the limit.

External URL fetch: UrlFetchApp has a 20,000 call/day limit and a 50MB response size cap. Relevant if you're pulling market data or bank feeds.

Properties storage: PropertiesService stores at most 500KB per script and 9KB per property. If you're serializing large arrays as checkpoint state (a common pattern), you'll exceed this without realizing it.

When to Stop Fighting the Quota

The continuation pattern solves the time problem but not the complexity problem. A multi-stage automation that chunks across multiple trigger invocations, saves state in PropertiesService, cleans up its own triggers, and handles partial failures gracefully is now a software project - not a spreadsheet automation.

For FP&A use cases where the logic is complex but the interaction is simple ("refresh my FCFF assumptions from this quarter's actuals and recalculate terminal value"), a server-side approach sidesteps the quota entirely. Tools like ModelMonkey run outside Apps Script - there's no 6-minute timer, no batch size management, and no continuation tokens. You describe what needs to happen across your 8-tab model, it executes it, and writes back the results.

That's not always the right call. If you have a working 3-minute script that runs on a trigger and never changes, don't break it. But if you're engineering around the quota, it's worth asking whether the engineering time is better spent elsewhere.

The Google Apps Script execution limit is 6 minutes for both consumer and Google Workspace accounts, as documented in Google's official quotas page. FP&A models hit this limit primarily through inefficient API patterns and complex multi-step automations. Batch range reads and writes first; when batching is not enough, use idempotent continuation checkpoints or move the job to server-side execution.


Frequently Asked Questions