The short answer.
- 6 minutes per execution, consumer and Workspace alike. It cannot be raised.
- Custom functions get 30 seconds, not 6 minutes. Most surprise timeouts are this.
- Chunking with triggers works, and swaps the 6-minute wall for a daily runtime budget: 90 minutes on consumer, 6 hours on Workspace.
- Past a few thousand rows of API-calling work, the fix is not a better script.
Exceeded maximum execution time arrives at the worst possible moment: the script has processed 900 of your 3,000 rows, written some of them, and stopped. There is no partial-completion record unless you built one, so now you also do not know exactly where it stopped.
Here are the actual numbers, why the limit exists where it does, and the four things that genuinely work.
The quota table people argue about
These are from Google's Apps Script quota reference, which is the only authority worth quoting.
| Limit | Consumer (gmail.com) | Google Workspace |
|---|---|---|
| Script runtime, per execution | 6 minutes | 6 minutes |
| Custom function runtime | 30 seconds | 30 seconds |
| URL Fetch calls per day | 20,000 | 100,000 |
| URL Fetch response size | 50 MB per call | 50 MB per call |
| Simultaneous executions per user | 30 | 30 |
| Triggers total runtime per day | 90 minutes | 6 hours |
The first row is the one that surprises people: paying for Workspace does not buy you longer executions. Workspace raises the daily budgets substantially, and leaves the per-execution wall exactly where it is.
The 30-second trap
This is the most common version of the problem and it does not look like a runtime limit at all.
Write a function and call it from a cell as =SUMMARISE(A2) and it is a custom function, which gets 30 seconds. Run the identical code from a menu item or a trigger and it gets 6 minutes. Same code, twelve times the budget, purely because of how it was invoked.
This produces a very specific and very confusing symptom: your function works when you test it on one cell and fails when you fill it down a column, because Sheets recalculates many of them and each one is racing its own 30-second clock while competing for the same execution slots. It reads as flakiness. It is arithmetic.
If your work involves a network call per row, a custom function is the wrong container for it, full stop.
Fix 1: read and write in ranges, not cells
Before restructuring anything, check that you are not spending your six minutes on spreadsheet round-trips. This is the single highest-return change in most scripts and it is usually a few lines.
// Slow: two calls per row.
for (var i = 2; i <= 1000; i++) {
var v = sheet.getRange(i, 1).getValue();
sheet.getRange(i, 2).setValue(process(v));
}
// Fast: two calls total.
var values = sheet.getRange(2, 1, 999, 1).getValues();
var out = values.map(function (r) { return [process(r[0])]; });
sheet.getRange(2, 2, out.length, 1).setValues(out);
Each getValue and setValue is a separate call across the boundary between your script and the spreadsheet service. A thousand rows done cell by cell can spend most of the six minutes on that alone. If your per-row work is pure computation, this change frequently removes the problem entirely.
If your per-row work is an API call, it will not, and you need fix 2.
Fix 2: chunk, checkpoint, and resume
The standard pattern. Process a batch, record where you stopped, schedule the next run, exit before the wall.
function processBatch() {
var props = PropertiesService.getScriptProperties();
var start = Number(props.getProperty('cursor') || 2);
var sheet = SpreadsheetApp.getActiveSheet();
var last = sheet.getLastRow();
var began = new Date().getTime();
var row = start;
while (row <= last) {
// Stop with headroom: never race the wall, and never leave a row
// half-written. Four minutes leaves time to finish and checkpoint.
if (new Date().getTime() - began > 4 * 60 * 1000) break;
sheet.getRange(row, 2).setValue(doWork(sheet.getRange(row, 1).getValue()));
row++;
}
props.setProperty('cursor', String(row));
if (row <= last) {
ScriptApp.newTrigger('processBatch')
.timeBased().after(60 * 1000).create();
} else {
props.deleteProperty('cursor');
}
}
Two things about this that matter more than the code.
Stop early, on purpose. The four-minute check is not caution, it is correctness. Being killed mid-row means the row is neither done nor recorded as undone, and on the next pass you either skip it or repeat it. Both are worse than being slower.
This does not give you unlimited compute. It converts a per-execution wall into a daily budget, and that budget is 90 minutes a day on a consumer account and 6 hours a day on Workspace. If a row takes 2 seconds, a consumer account processes about 2,700 rows per day in total, across every trigger you have. Chunking is a real fix and it has a visible ceiling.
Fix 3: do less per row
Before scaling the machinery, check the shape of the work. Three things are usually available:
- Filter first. Running an expensive step on 3,000 rows when 800 qualify is the most common source of self-inflicted volume.
- Cache repeats. A lead list of 3,000 contacts often contains only 400 distinct company domains. Enrich domains once, join back.
- Fetch once, extract many times. If you pull a page to read four fields, pull it once and parse four values, not four times.
Fix 4: move the work off the platform
There is a point where the honest answer is that Apps Script is not the right runtime for the job. It is a scripting layer attached to a document, and it is genuinely good at that. It is not a job queue, and per-row network work over a long list is a job queue problem: it needs retries, backoff, concurrency, a record of what succeeded, and somewhere for failures to go.
You can build all of that on top of triggers and PropertiesService. People do. The result is a distributed system living in a spreadsheet's script editor, with no observability and a hard daily budget, and it is usually more code than the thing it was meant to enable.
The alternative is to let the sheet stay a sheet. ReplyLabs keeps the spreadsheet as the interface, runs the batch on infrastructure built for it, retries failures, reports per-row status, and writes results back into the cells. No 6-minute wall, no trigger budget, no checkpoint code to maintain. You are billed for rows that succeed.
If your script is comfortably inside the limits, none of that is worth changing. Apps Script is free and it is right there.
Related reading
- The 350-cell limit on Google's AI function
- Running one prompt across thousands of rows
- Estimating what an AI batch will cost
Quotas from Google's Apps Script quota reference, checked August 2026. Google states these are subject to change without notice.