Google Apps Script Debugging: Why Your Script Works Until It Doesn't
Apps Script errors are rarely as mysterious as they look.
Usually, the error message is telling you something useful. The difficult part is connecting that message to what your script was actually doing when it failed.
A function might work perfectly with one spreadsheet and fail with another.
A trigger might work when you run a function manually but fail automatically.
An API call might work ten times and then suddenly hit a quota or return something your code wasn't expecting.
Here are some of the Apps Script problems I encounter most often and how to approach them.
1. Exception: Cannot call method...
One of the most frustrating errors is when Apps Script tells you that it cannot call a method on something.
For example:
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Tasks");
const data = sheet.getDataRange().getValues();
If getSheetByName("Tasks") returns null, the next line will fail.
The problem isn't getDataRange().
The problem happened one line earlier.
A simple defensive check can make this much easier to diagnose:
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Tasks");
if (!sheet) {
throw new Error("Sheet 'Tasks' was not found.");
}
When you see a "cannot call method" error, work backward from the failing line and ask:
Which object could actually be undefined or null?
2. Cannot find sheet
Sheet names are strings, which means tiny differences matter.
These are different:
Tasks
tasks
Tasks
Task
A trailing space can be enough to break your script.
Instead of assuming the sheet exists, check it:
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName("Tasks");
if (!sheet) {
throw new Error("Expected sheet was not found.");
}
For debugging larger projects, you can also inspect the available sheet names:
const sheets = spreadsheet.getSheets();
sheets.forEach(sheet => {
console.log(sheet.getName());
});
This can immediately reveal spelling or naming problems.
3. Authorization errors
Apps Script interacts with Google services on your behalf.
That means operations involving Gmail, Drive, Calendar, Sheets, and other services may require authorization.
A script can therefore fail even when the JavaScript itself is correct.
If you've recently added a new service or changed what your script accesses, check whether the required authorization has been granted.
Also remember that authorization behavior can differ depending on whether you're running a function manually, through a trigger, or under a different account.
4. Trigger-specific failures
This is one of the biggest sources of confusion.
You run:
function processTasks() {
// ...
}
It works.
Then you create a trigger for processTasks().
Suddenly it fails.
Why?
Because manually running a function and running it through a trigger aren't necessarily the same execution context.
Some functions also rely on information that isn't available during a trigger execution.
For example, code that depends on an active spreadsheet, active user, selected cell, or UI can behave differently when nobody is actively sitting in the editor.
When debugging a trigger, ask:
- What type of trigger is running?
- What account executes it?
- What services does it access?
- Does it depend on an active user or UI?
- What does the execution log show?
5. Quota errors
Apps Script isn't an unlimited execution environment.
Depending on the service and account, there are limits around execution time, service calls, emails, API requests, and other operations.
A common performance problem looks like this:
rows.forEach(row => {
sheet.getRange(row[0], 2).setValue("Processed");
});
It may work for 20 rows.
With thousands of rows, repeatedly communicating with the spreadsheet can become expensive.
Instead, collect your changes and write them in batches where possible.
The general principle is:
Reduce the number of service calls.
6. undefined values
JavaScript problems often become Apps Script problems.
Consider:
const name = row[5];
console.log(name.toUpperCase());
What happens if row[5] doesn't contain a value?
You'll get an error because you're trying to call toUpperCase() on something that isn't a string.
During debugging, don't assume your data has the shape you expect.
Inspect it:
console.log(JSON.stringify(row));
Then verify the value before using it.
if (typeof name === "string") {
console.log(name.toUpperCase());
}
7. API response errors
External APIs introduce another layer of uncertainty.
Your code may expect:
{
"status": "success",
"data": []
}
But the API might return:
{
"error": "Unauthorized"
}
If your code immediately does:
const data = response.data;
you may end up debugging the wrong line.
Always inspect the response status and body before assuming the expected structure exists.
const response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true
});
console.log(response.getResponseCode());
console.log(response.getContentText());
muteHttpExceptions can be particularly useful while diagnosing HTTP failures because it lets your script inspect the response instead of immediately throwing an exception.
8. Timeouts
Apps Script executions have time limits.
A script that works with 100 rows may become unreliable when the spreadsheet grows to 50,000 rows.
Look for expensive operations such as:
- repeated calls to Sheets
- unnecessary API requests
- processing the same data multiple times
- large loops
- unnecessary reads and writes
One of the first optimization questions should be:
How much work is the script actually doing?
9. Logging
Good logging can turn debugging from guessing into investigation.
Instead of:
processData();
you can temporarily add:
console.log("Starting processData");
console.log(`Rows found: ${rows.length}`);
For important values:
console.log(JSON.stringify(data));
The goal isn't to log everything forever.
It's to capture enough information to understand the execution path.
When debugging a complicated automation, useful logs can answer:
- Did the function start?
- Which branch executed?
- How many records were found?
- What value caused the failure?
- Did the API respond?
- Did the script reach the final step?
10. Reproduce the error
This is probably the most important debugging habit.
Don't immediately change five things at once.
First reproduce the problem.
Then reduce it.
If a function processes 5,000 rows, try 10.
If it uses three APIs, test each request separately.
If a trigger fails, run the underlying logic independently where possible.
The smaller the reproduction case, the easier it becomes to identify the actual cause.
Where AI Debugging Helps
AI is particularly useful when you give it enough context.
A vague prompt such as:
My Apps Script doesn't work. Fix it.
doesn't provide much to work with.
A better debugging prompt includes:
- the exact error message
- the function that failed
- the relevant file or code section
- what you expected to happen
- what actually happened
- any recent change that may have caused the problem
For example:
This trigger fails with
Cannot read properties of null. Here is the function, the sheet structure, and the execution log. The expected behavior is to update the Status column after a form submission.
That's a much more useful debugging problem.
AI can help explain the error, identify suspicious lines, suggest defensive checks, and propose possible fixes.
But you still need to verify the suggested change.
Fixing Errors Inside the Apps Script Workflow
This is also where editor-integrated AI assistance can be useful.
Google Apps Script Copilot includes a Fix with GS Copilot workflow that can use an execution error in context to help identify and apply a fix.
I see that as more useful for debugging than simply asking an AI to generate an entire script from scratch.
The developer still needs to understand the change, review it, and test the result.
A Better Debugging Process
When an Apps Script suddenly stops working, I generally work through this sequence:
Read the error
↓
Find the failing line
↓
Inspect the values
↓
Reproduce the problem
↓
Reduce the test case
↓
Identify the root cause
↓
Apply one change
↓
Run the test again
The important part is not fixing the error as quickly as possible.
It's understanding why it happened.
Once you know that, you're much less likely to create the same bug somewhere else.
Top comments (0)