Blog / Small Business Automation
Google Apps Script Limits Every Builder Should Know (2026)
By Keyona Meeks, Founder, ReRev Labs · Oct 31, 2026 · 10 min read
The short answer: As of October 31, 2026, Google's quotas page lists these limits: a script may run 6 minutes per execution, custom functions 30 seconds, and each user may have 20 triggers per script. Daily limits depend on the account. Trigger runtime is 90 minutes a day on a consumer account and 6 hours on Google Workspace. Email is 100 recipients a day on consumer accounts and 1,500 on Workspace, and URL Fetch is 20,000 calls a day versus 100,000. My tool did not return the full page, so check every number on Google's page before you rely on it. I tested the script below with a mock in Node, not in Google.
By the end of this post, you'll be able to:
- Name the main Apps Script limits.
- Tell which ones change with the account type.
- Run a long job in batches that stop before the runtime limit.
- Log how much email quota is left.
We sell AI operations work, so weigh this post with that in mind.
What are the main Apps Script limits?
I read Google's Apps Script quotas page on October 31, 2026. My tool returned the first 100,000 of about 102,000 characters, so I did not read the last 2,000. The page has more rows than the ones below, and I picked the ones builders hit most. I quote the cells as my tool returned them. The column headings are "Consumer accounts (for example, gmail.com)" and "Google Workspace accounts."
| Feature | Consumer accounts | Google Workspace accounts |
|---|---|---|
| Script runtime | 6 min / execution | 6 min / execution |
| Custom function runtime | 30 sec / execution | 30 sec / execution |
| Triggers | 20 / user / script | 20 / user / script |
| Triggers total runtime | 90 min / day | 6 hr / day |
| Email recipients per day (for example, with MailApp) | 100 / day | 1,500 / day |
| URL Fetch calls | 20,000 / day | 100,000 / day |
The page puts a footnote mark on the email figures. I did not read the footnote text, so check it on the page. Google can change these numbers. The date above is when I read them.
Which limits change with the account type?
Three of the rows above: total trigger runtime, email recipients and URL Fetch calls. The per-run limits (6 minutes, 30 seconds) and the 20-trigger limit are the same in both columns. If your script runs under a Workspace account, you get the larger daily numbers. Per an earlier review of Google's triggers page, installable triggers "always run under the account of the person who created them." I read that as meaning the creator's account sets which daily limits apply, but I could not confirm that from the quotas page.
What happens when a script hits the runtime limit?
It stops. I could not confirm the exact error text from the pages I read. Google's troubleshooting page, as my tool returned it, had nothing on the "Exceeded maximum execution time" message. So I do not quote an error message. If a long job stops without finishing, the fix is to do less per run. The script below does that.
How do you keep a long job under the runtime limit?
Split the work. Process a few rows per run, record each finished row, and stop early. The next run picks up where the last one stopped. The script below keeps a Queue tab of rows to process, a Done tab of finished keys, and a time budget of 4 minutes, so it stops with room to spare under the 6-minute limit. Four minutes is my choice, not Google's. Change it for your job.
Paste this into the Apps Script project attached to the sheet. Put your own work in doWork. Nothing in it runs until you call runBatch.
// Works through a list of rows in one run and stops before the six-minute limit. Each finished key is logged in a "Done" tab, so the next run skips it.
var QUEUE_TAB = 'Queue';
var DONE_TAB = 'Done';
var BUDGET_MS = 4 * 60 * 1000; // Stop after 4 minutes. Your decision. The runtime limit is 6 minutes per execution.
var HEADERS = { key: 'Key' }; // Change the text on the right to match YOUR column heading.
function now() { return Date.now(); }
function doWork(row) {
// Put the work for one row here. Throw an error if the row fails.
}
function findColumns(headerRow) {
var idx = {};
Object.keys(HEADERS).forEach(function (k) {
var at = headerRow.map(function (h) { return String(h).trim(); }).indexOf(HEADERS[k]);
if (at === -1) throw new Error('Missing column in ' + QUEUE_TAB + ': ' + HEADERS[k]);
idx[k] = at;
});
return idx;
}
function pickTodo(rows, idx, doneKeys) {
var seen = {};
doneKeys.forEach(function (k) { seen[String(k)] = true; });
var todo = [];
rows.forEach(function (r) {
var key = String(r[idx.key]).trim();
if (key === '' || seen[key]) return;
seen[key] = true;
todo.push({ key: key, row: r });
});
return todo;
}
function runBatch() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var queue = ss.getSheetByName(QUEUE_TAB);
var done = ss.getSheetByName(DONE_TAB);
if (!queue || !done) throw new Error('Missing a tab: ' + QUEUE_TAB + ' and ' + DONE_TAB + ' are both needed.');
var values = queue.getDataRange().getValues();
if (values.length === 0) throw new Error('The ' + QUEUE_TAB + ' tab is empty.');
var idx = findColumns(values[0]);
var doneKeys = done.getDataRange().getValues().map(function (r) { return r[0]; });
var todo = pickTodo(values.slice(1), idx, doneKeys);
var started = now();
var finished = 0;
var failed = 0;
var stoppedForTime = false;
for (var i = 0; i < todo.length; i++) {
if (now() - started >= BUDGET_MS) { stoppedForTime = true; break; }
try {
doWork(todo[i].row);
done.appendRow([todo[i].key]);
finished++;
} catch (e) {
failed++;
}
}
var left = todo.length - finished - failed;
console.log('Finished ' + finished + ', failed ' + failed + ', left ' + left + '.');
return { finished: finished, failed: failed, left: left, stoppedForTime: stoppedForTime };
}
function logQuota() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var tab = ss.getSheetByName('Quota log');
if (!tab) throw new Error('Missing tab: Quota log');
var tz = Session.getScriptTimeZone();
var left = MailApp.getRemainingDailyQuota();
tab.appendRow([Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd HH:mm'), left]);
return left;
}
What the parts do, with Google's own descriptions as I read them in the method tables this session:
getActiveSpreadsheet(): "Returns the currently active spreadsheet, ornullif there is none."getSheetByName(name): "Returns a sheet with the given name."getDataRange(): "Returns aRangecorresponding to the dimensions in which data is present."getValues(): "Returns the rectangular grid of values for this range."Sheet.appendRow(rowContents): "Appends a row to the bottom of the current data region in the sheet." The script uses it to log finished keys, so it never edits a cell.MailApp.getRemainingDailyQuota(): "Returns the number of recipients you can send emails to for the rest of the day."Session.getScriptTimeZone(): "Gets the time zone of the script."Utilities.formatDate"Formats date according to specification described in Java SE SimpleDateFormat class."Date.now()is plain JavaScript, not a Google method. I did not look it up in Google's docs.
The script reads the Key column, skips keys already in Done and skips blank keys. It counts a row as finished only when doWork returns without an error. A row that fails is not logged, so the next run tries it again. It never sends email. logQuota adds one line to a Quota log tab with the time and the number of recipients left, so you can see the trend.
How do you watch the email quota?
Run logQuota on a schedule you set, or by hand. It writes the time and getRemainingDailyQuota() to the Quota log tab. If the number drops fast, a script is sending more than you think. Make the Quota log tab with any heading in row 1 before you run it.
How do you check these numbers yourself?
- Open the quotas page linked above.
- Find your account type in the column headings.
- Compare each row with the table in this post.
- Write down the date you checked.
Where does an LLM fit in this?
An LLM can help with the messy input. For example, it can read a long script and suggest where the slow part is, or read an error log and suggest which limit the script hit. A person checks the suggestion against Google's page, because an LLM can quote an old number and sound sure. Limits change, so do not trust a number an LLM gives from memory. The queue, the time budget and the quota log are plain rules. My stance: AI writes the code, plain automation runs it, and an LLM shows up only where the input is messy and a person checks the result. For background on the tool itself, see what Google Apps Script is.
How do you test it?
- Make the tabs
Queue,DoneandQuota log. Put the headingKeyin A1 ofQueue, and add keys K1 to K5 below it. - Run
runBatch.Doneshould list all five keys. - Run it again. Nothing new should appear.
- Make
doWorkthrow an error for K3, clearDoneand run again. K3 should be missing fromDone. - To see the time stop, set
BUDGET_MSto 0 and clearDone. The first check stops the loop before any row, and the run logs nothing. Set it back to 4 minutes afterwards. - Run
logQuotaand check the new line. - Delete the test rows.
What did the test show?
I tested the script in Node with a mock of the Google services and a clock I could move. All 17 checks passed. They covered finishing a list, a rerun skipping finished keys, keys finished in an earlier run, the time budget stopping the loop, a failed row being left out and retried, blank and duplicate keys, a missing heading, an empty tab, missing tabs, numeric keys, the quota line, and no email being sent. The mock cannot show how fast real work runs in Google. Nothing was run in Google.
Where this has limits
- My tool did not return the whole quotas page. I read the first 100,000 of about 102,000 characters. Check every number on Google's page.
- I did not read the email footnote.
- I could not confirm the runtime error text.
- Nothing was run in Google. The test was a mock in Node.
- The 4-minute budget is my choice, not Google's.
- Limits change. The date above is when I read them.
- No ReRev prices and no promises about results.
If you want help turning a long script into a system that finishes, see our AI operations service or tell us what the script does. Do not send API keys, passwords or login details.
Frequently asked questions
How long can a Google Apps Script run?
The quotas page lists 6 minutes per execution for both account types, as of October 31, 2026. Check the page, because Google can change it.
How many emails can Apps Script send per day?
The page lists 100 recipients a day for consumer accounts and 1,500 for Google Workspace. It footnotes these figures, and I did not read the footnote.
How many triggers can one script have?
The page lists 20 per user per script.
What do I do when my script runs too long?
Do less per run. Process a batch, record what you finished and stop early, as the script above does.
Do these limits change?
Yes, they can. Read Google's page on the day you build.
Does the script use an LLM?
No. An LLM can help find the slow part, and a person checks the answer.
Read next: Send an email when a cell changes is a real build that stays inside these limits, with the quota math shown.