Basics
Google Apps Script quotas and limits
Google caps how long one run may take and how much your scripts may do in a day. Knowing the caps changes how you write anything that touches a lot of rows.
1 of 8
What a quota is
A quota is a cap Google puts on how much your scripts may do. There are two kinds. A limit applies to one run, such as the six minutes a single execution may take. A daily quota applies to everything your scripts do in a day, such as the number of email recipients.
Quotas are counted per Google account, not per script. Five scripts sending email from the same account share one daily email allowance between them.
Going over a quota costs nothing and does no lasting damage. The run that goes over stops with an error. Daily quota windows reset about 24 hours after the first request that used that quota, not 24 hours after you exhaust it.
2 of 8
A personal account and a work account get different numbers
A consumer account is a free address ending in gmail.com. A Google Workspace account is one your employer or your school pays for, usually at your own domain.
Workspace accounts get much larger daily quotas. Email is where people notice first: 100 recipients a day on a consumer account against 1,500 on Workspace.
What counts is the account that owns the run. A trigger runs as the person who created it, so a script run by a colleague's trigger uses that colleague's quota.
3 of 8
The numbers, at the time of writing
Google changes these from time to time, so treat the list as a guide and check the official page before you build something that depends on an exact number. It is at https://developers.google.com/apps-script/guides/services/quotas and it shows the consumer and the Workspace figures side by side. The cache and sleep limits are on the Cache reference at https://developers.google.com/apps-script/reference/cache/cache and the Utilities reference at https://developers.google.com/apps-script/reference/utilities/utilities.
- Script runtime: 6 minutes for one execution.
- Custom function runtime: 30 seconds for one call from a cell.
- Triggers total runtime: 90 minutes a day on a consumer account and 6 hours a day on Google Workspace.
- Email recipients a day: 100 on a consumer account, 1,500 on Google Workspace.
- URL Fetch calls a day: 20,000 on a consumer account, 100,000 on Google Workspace.
- Properties reads and writes a day: 50,000 on a consumer account, 500,000 on Google Workspace.
- Triggers: 20 per user per script.
- Simultaneous executions: 30 per user.
- Properties: 9 KB per value and 500 KB per property store.
- Cache entries: 100 KB per value, kept for at most 6 hours.
- Utilities.sleep: at most 5 minutes in one call.
4 of 8
The six minute limit
One execution gets six minutes. Past that, Apps Script stops it and records "Exceeded maximum execution time". Everything already written to the sheet or already sent stays done, so a run stopped halfway leaves half the job finished.
A custom function typed into a cell gets 30 seconds for each call, because a sheet can run many of them at once. All of your triggers together get 90 minutes a day on a consumer account or 6 hours on Google Workspace, so a five-minute job cannot run every hour around the clock on a consumer account but can fit within the Workspace runtime allowance.
The usual first fix is to make fewer service calls per row, as the next section shows.
5 of 8
Read and write in bulk
Every call to a Google service is a round trip from the script to Google servers and back. A loop that calls getValue() once per cell across a thousand rows makes a thousand round trips, and that on its own can use the whole six minutes.
One getValues() call reads those thousand rows in a single trip. One setValues() writes the answers back in one more. The same script then finishes in seconds and uses far fewer service calls.
The idea carries over to the other services. Search Gmail once and loop over the results. Fetch one web address that returns many records instead of one address per record.
6 of 8
Split a long job into batches
When there is more work than six minutes allows, do part of it per run and remember where you stopped. PropertiesService gives you a small store of text that survives between runs, so it can hold a resume-from-row marker. You can see what is stored under Project Settings, in the Script Properties table.
The loop below checks the elapsed time. Once four minutes have passed it stops, saves the row number to start from next time, and returns. A time-driven trigger calls the function again later. That run reads the marker and continues where the last run stopped.
Stop well before the limit. Four minutes leaves room for a slow last row and for the writing at the end. When the marker passes the last row, the function clears it so the next run starts fresh.
function processCustomersInBatches() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) throw new Error('Add a Sheet tab named Customers before running this function.');
const properties = PropertiesService.getScriptProperties();
const firstDataRow = 2;
const startRow = Number(properties.getProperty('nextRowToProcess') || firstDataRow);
const lastRow = sheet.getLastRow();
if (startRow > lastRow) {
properties.deleteProperty('nextRowToProcess');
Logger.log('Every row is done. The next run starts again from row ' + firstDataRow + '.');
return;
}
const values = sheet.getRange(startRow, 1, lastRow - startRow + 1, 2).getValues();
const stopAfterMilliseconds = 4 * 60 * 1000;
const startedAt = Date.now();
let index = 0;
while (index < values.length) {
const [name] = values[index];
Logger.log('Handling row ' + (startRow + index) + ': ' + name);
index += 1;
if (Date.now() - startedAt > stopAfterMilliseconds) break;
}
const nextRow = startRow + index;
properties.setProperty('nextRowToProcess', String(nextRow));
Logger.log('Handled ' + index + ' rows. The next run starts at row ' + nextRow + '.');
} 7 of 8
How a limit looks in Executions
Open the left sidebar of the editor and click Executions, the icon that looks like a list with a clock. Every run of every function is listed there with its status, when it started, and how long it took.
A run that hit the six minute limit shows Timed out. A run stopped by a quota shows Failed, and opening the row shows the message. "Exceeded maximum execution time" is the six minute limit. "Service invoked too many times for one day" is a daily quota, and the word after the colon names which one.
The duration column is your early warning. A run that already takes four or five minutes will start failing as soon as there is a little more data.
8 of 8
What to do when you hit a limit
A script usually hits a limit because it calls a service once per cell or row. Try these fixes:
- Wait for the reset. A daily quota window clears about 24 hours after its first request, not after it filled up, and there is no way to buy more or to clear it early.
- Do less per run. Send to fewer recipients, or handle a few hundred rows rather than all of them.
- Split the work across runs with a saved marker, as in the batch example above.
- Cut the round trips. Use one getValues() and one setValues() instead of a call per cell.
- Mark finished rows with a status column, so a second run does not repeat work that already cost you quota.
- Check which account is running it. A Workspace account has far more headroom than a personal one for the same script.
Remember
The short version
- One run gets 6 minutes, a custom function gets 30 seconds, and all triggers together get 90 minutes a day on a consumer account or 6 hours on Google Workspace.
- A free Gmail account may email 100 recipients a day and a Google Workspace account 1,500, counted per account rather than per script.
- A trigger uses the quota of the account that created it, not the quota of the person who edited the sheet or submitted the form.
- One getValues() and one setValues() are much faster than a call per cell.
- Check the current figures on the Apps Script quotas page before you rely on any of them.
Keep going
Put it to work
- Task guide Send an email for each row in a Google Sheet
Send one email to each person listed on a Customers tab. The script marks each row Sent as it goes, so a later run sends only to rows that are not marked yet.
- Task guide Run an Apps Script function every day
Make a function run on its own once a day. Run the setup function once, and from then on Apps Script calls the reminder function each day around the hour you chose.
- Task guide Store settings with PropertiesService
Keep a setting such as an email address out of your code and change it without editing the script. The project stores it as a script property that every run can read.
- Task guide Move finished rows to an Archive tab
Move every row marked Done from a Tasks tab to the bottom of an Archive tab. The script deletes rows from Tasks, so test it on a copy of the file first.
- Basics Common Apps Script errors and what they mean
Apps Script error messages say what failed, for example TypeError: Cannot read properties of null (reading 'getRange') or ReferenceError. Find the exact message below for its cause and fix.
- Basics Google Apps Script triggers explained
Google Apps Script triggers run functions automatically when a file opens, a cell changes, a form is submitted, or a scheduled time arrives. Use a simple onEdit or onOpen for work in the bound file. Choose an installable trigger for a schedule, form submission, or work that needs authorization.
- Basics Execution log, Logger.log, and debugging
Most Apps Script debugging is printing a value with Logger.log or console.log and reading the error line. This page shows where the output goes and how to trace a failure.
- Basics Custom functions in Google Sheets
A custom function runs from a cell formula. Each call has 30 seconds, receives cell values instead of ranges, and cannot use your personal data or edit other cells.
- Diagnostic Service call inside a loop
How moving getValues() and setValues() outside a row loop keeps long spreadsheet runs under the 6-minute execution limit.
- Book chapter Debug executions and recover from limits
Find a failing input with the debugger, read past executions and their logs, and resume a batch job from its last checkpoint.