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.
1 of 10
TypeError: Cannot read properties of null (reading 'getRange')
The script tried to call getRange() on a null value. In a Sheets script, that value is almost always the result of getSheetByName().
getSheetByName() returns null when no tab has that exact name. The name is case sensitive, and a trailing space counts, so "Customers " is not the same as "Customers".
Check the tab name at the bottom of the spreadsheet and make the code match. Then add a check, so the next time the message names the tab instead of the method. The same wording with a different method inside the brackets means the same thing: whatever came before the dot was null.
function readCustomersTab() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) {
throw new Error('No tab named Customers. Check the spelling on the tab at the bottom of the spreadsheet.');
}
Logger.log('Customers holds ' + sheet.getLastRow() + ' rows of content.');
} 2 of 10
TypeError: Cannot read properties of undefined (reading 'range')
Trigger functions such as onEdit(e) and an on form submit handler are handed an event object when they run. The letter e holds details of what happened: e.range for the cell that was edited, e.values for the answers in a form response.
When you click Run in the editor, nobody edited anything and nobody submitted anything. Apps Script calls the function with no event object at all, so e is undefined and e.range fails.
This is not a bug in your code. Test it by editing a cell in the sheet, or by submitting the form, and let the trigger call the function. If you want a button to press in the editor, write a second function that builds the parts of the event you need and passes them in.
/** @param {Events.SheetsOnEdit} e */
function onEdit(e) {
if (!e || !e.range) {
Logger.log('Run this by editing a cell in the sheet, not with the Run button.');
return;
}
Logger.log('Edited ' + e.range.getA1Notation() + ' on the tab ' + e.range.getSheet().getName());
}
function testOnEdit() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) throw new Error('No tab named Customers.');
onEdit({
authMode: ScriptApp.AuthMode.NONE,
range: sheet.getRange('A2'),
source: SpreadsheetApp.getActiveSpreadsheet()
});
} 3 of 10
Exception: The coordinates or dimensions of the range are invalid
getRange(row, column, numRows, numColumns) needs four numbers that describe a real rectangle. Every one of them has to be 1 or more. A row count of 0 or of minus 3 is not a rectangle, so the run stops here.
An empty tab is the usual cause. On a tab that holds only a header row, getLastRow() returns 1, so lastRow - 1 is 0 rows. On a tab with nothing at all it returns 0, and the count goes negative.
Work out the row count first, and return early when it is not at least 1. The script then does nothing on an empty tab instead of failing.
function countCustomerRows() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) throw new Error('No tab named Customers.');
const firstDataRow = 2;
const numRows = sheet.getLastRow() - firstDataRow + 1;
if (numRows < 1) {
Logger.log('Customers has a header but no data rows yet, so there is nothing to read.');
return;
}
const values = sheet.getRange(firstDataRow, 1, numRows, 3).getValues();
Logger.log('Read ' + values.length + ' rows.');
} 4 of 10
Exception: The number of rows in the data does not match the number of rows in the range
setValues() writes a rectangle, and the array you give it must be exactly the size of the range. Twelve rows of two columns means twelve inner arrays, each holding two items.
It usually breaks when the range is sized from the sheet but the array is built from a shorter list, for example after rows were skipped. It also breaks when a row is a plain value instead of an array holding one value, so ["Checked"] is right and "Checked" is not.
Size the range from the array rather than the other way round, and make sure every inner array has the same number of items. The column version of this message says "columns" instead of "rows" and means the same thing.
function writeStatusColumn() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) throw new Error('No tab named Customers.');
const firstDataRow = 2;
const numRows = sheet.getLastRow() - firstDataRow + 1;
if (numRows < 1) return;
const statuses = [];
for (let index = 0; index < numRows; index += 1) {
statuses.push(['Checked']);
}
sheet.getRange(firstDataRow, 4, statuses.length, 1).setValues(statuses);
Logger.log('Wrote ' + statuses.length + ' rows into column D.');
} 5 of 10
Exception: You do not have permission to call SpreadsheetApp.getUi
You may see the same problem worded as "Cannot call SpreadsheetApp.getUi() from this context". Both mean the code tried to put something on screen when there is no screen to put it on.
getUi() gives you menus, alerts, and prompts. It works when a person has the spreadsheet open and started the code, for example by clicking your custom menu item. It does not work inside a time driven trigger that fires overnight, inside a custom function typed into a cell, or in a script somebody else set running.
Replace the alert with Logger.log() to write the message to the Execution log, or email yourself the result. Keep getUi() for the code paths a person starts by hand.
function reportRowCountForATrigger() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) throw new Error('No tab named Customers.');
const message = 'Customers has ' + sheet.getLastRow() + ' rows.';
Logger.log(message);
} 6 of 10
Exceeded maximum execution time
One run of a script may take up to six minutes at the time of writing. When it goes past that, Apps Script stops it and records this message. Anything already written to the sheet or already sent stays done, so a script stopped halfway can leave half the rows updated.
The usual cause is calling a Google service inside a loop, such as getValue() on every cell or sendEmail() on every one of 400 rows. Each call is a round trip to Google servers. Read the whole block once with getValues() and write once with setValues(), and the same job often finishes in seconds.
When there is more work than six minutes allows, split it. Handle part of the list, save the row number to continue from, and let the next run carry on from there.
7 of 10
Exception: Service invoked too many times for one day: email
This is a daily quota. A quota is a cap on how much your scripts may do in one day, counted per Google account rather than per script. The word after the colon names which cap you filled, and email is the one people meet first.
At the time of writing a free Gmail account may send to 100 recipients a day from Apps Script, and a Google Workspace account may send to 1,500. Recipients are counted, not messages, so one email addressed to 20 people uses 20 of them.
There is no way to raise it or reset it early. Wait for the daily reset, then send fewer per run, and mark each row as sent so a second run does not send it again. MailApp.getRemainingDailyQuota() tells you how much room is left before you start.
function checkRemainingEmailQuota() {
const remaining = MailApp.getRemainingDailyQuota();
Logger.log('This account can still send to ' + remaining + ' recipients today.');
if (remaining < 10) {
Logger.log('Fewer than 10 left. Wait for the daily reset before sending more.');
}
} 8 of 10
ReferenceError: foo is not defined
The code uses a name that is not declared anywhere it can reach. In the real message foo is replaced by the name you used, for example "ReferenceError: sheeet is not defined".
The name may be misspelled. A variable may be declared inside one function and then used in another, where it does not exist. Or the function may live in a different Apps Script project, because separate projects do not share code.
Read the name in the message, then find where you meant to declare it.
- Check the spelling, including capital letters. sheetName and sheetname are two different names.
- A const or let declared inside a function cannot be reached from another function. Pass it in as a parameter, or declare it at the top of the file.
- Functions in another Apps Script project are not visible. Copy the function into this project, or add the other project as a library.
- A word without quotes is a variable name. getSheetByName(Customers) looks for a variable called Customers, while getSheetByName('Customers') looks for the tab.
10 of 10
#NAME? Unknown function in a spreadsheet cell
Google Sheets displays #NAME? when it evaluates a formula in a cell and cannot find a built-in or custom function matching the name you typed, such as =DOUBLE_(A2).
In Apps Script, any function whose name ends with an underscore is treated as private. Private functions are hidden from Google Sheets, so removing the trailing underscore lets Sheets find the function.
Custom functions must also live in a script bound to the spreadsheet, not a standalone project. Save the project before typing the formula into a cell. The @customfunction JSDoc tag only adds the name to autocomplete, so a missing tag does not cause #NAME?.
Remember
The short version
- Read the whole message. The words after the colon name the thing that went wrong.
- A null value before a method call almost always means getSheetByName() did not find a tab with that name.
- An undefined event object means a trigger function was started with the Run button instead of by the trigger.
- The two range errors mean a row or column count is wrong, so check the counts on both sides.
- A quota error means the script did too much, and an authorization error means it needs approval. Fix them by doing less per run or by approving the script.
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 Add a timestamp when a row changes
When you change a status cell, the script writes the current date and time into the same row. You save the script and nothing else needs to be installed.
- 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.
- 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 Rows, columns, and 2D arrays in Sheets scripts
A range is a rectangle of cells. getValues() returns that rectangle as an array of rows, and each row is an array of cell values.
- 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.
- 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 TypeError: Cannot read properties of null (reading 'getRange')
Why getSheetByName() returned null one line earlier and how to guard the lookup before calling getRange().
- Diagnostic ReferenceError: X is not defined
How to tell a misspelled variable name from a function scope gap, an unsaved file, or a cross-file load order problem.
- Diagnostic The number of rows in the data does not match the number of rows in the range
Why a setValues() range sized from a separate count disagrees with the 2D array and how to size from values.length.
- 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.