Skip to content

Diagnostic coverage

What myFunction checks in Apps Script code.

Every Apps Script check myFunction adds on top of type checking, with the code each one flags, a corrected version, and the quick fix it offers.

Two layers

Type checking, then Apps Script rules.

myFunction type-checks each file against declarations generated from the Apps Script runtime. That layer catches the mistakes below in any service.

  • Unknown method or property

    SpreadsheetApp.getActiveSheet().getValue()

  • Missing or extra arguments

    GmailApp.createDraft("a@example.com")

  • Wrong argument type

    sheet.getRange("1", 1)

  • Name that is never declared

    fetch(url)

  • Syntax errors

    Logger.log("unterminated)

Implicit any and possibly-null results are reported as warnings because ordinary Apps Script code produces them often and still runs.

On top of that, myFunction adds the 82 Apps Script rules listed here. For 48 of them, the editor offers a quick fix or written guidance. The Chrome extension reports every rule. The gas_diagnostics MCP tool reports every rule except MF90006. The VS Code extension reports every rule except MF90006, MF92007, and the HTML page rules, because it does not yet read a project's HTML files or file order. The examples on this page come from the language server's tests, which check that each rule flags the first example and not the corrected one.

In the Chrome extension, the manifest and OAuth scope rules run when Show "appsscript.json" manifest file in editor is on in Project Settings, because the extension reads the manifest from the editor. In HTML files, the extension marks HTML page rule problems but does not offer their quick fixes yet.

Triggers and web apps

MF93001 Warning

Trigger or web app function name has the wrong case

Apps Script calls onOpen, onEdit, onSelectionChange, onInstall, doGet, and doPost by exact name. A function named onedit or DoGet never runs.

Quick fix: Rename the function to the exact trigger name.

Flagged 1 changed line
function onedit(e) {
  e.range.setNote("Edited");
}
Not flagged 1 changed line
function onEdit(e) {
  e.range.setNote("Edited");
}
MF93002 Warning

Simple trigger calls a service that needs authorization

onOpen, onEdit, and onSelectionChange run without authorization. Calls to GmailApp, MailApp, UrlFetchApp, DriveApp, CalendarApp, or ContactsApp, or opening another file by ID or URL, fail when the trigger fires.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function onEdit(e) {
  MailApp.sendEmail("team@example.com", "Sheet edited", e.range.getA1Notation());
}
Not flagged 2 changed lines
// Run from an installable "On edit" trigger.
function notifyOnEdit(e) {
  MailApp.sendEmail("team@example.com", "Sheet edited", e.range.getA1Notation());
}
MF93003 Warning

doGet or doPost does not return a response

A web app must return an HtmlOutput or TextOutput. Without a return value, visitors see "The script completed but did not return anything."

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function doGet(e) {
  HtmlService.createHtmlOutput("<p>Hello</p>");
}
Not flagged 1 changed line
function doGet(e) {
  return HtmlService.createHtmlOutput("<p>Hello</p>");
}
MF93004 Warning

Menu item or trigger names a function that does not exist

Menu.addItem() and ScriptApp.newTrigger() take the function name as text, and Apps Script only looks it up when the item is clicked or the trigger fires. A misspelled name, or one written with parentheses, fails then with "Script function not found". The rule reports names that are close to a function in the project, since a name with no near match may live in a file that is not loaded.

Quick fix: Change the name to the matching function, or drop the parentheses.

Flagged 1 changed line
function onOpen() {
  SpreadsheetApp.getUi().createMenu("Reports").addItem("Build", "buildReprot").addToUi();
}

function buildReport() {}
Not flagged 1 changed line
function onOpen() {
  SpreadsheetApp.getUi().createMenu("Reports").addItem("Build", "buildReport").addToUi();
}

function buildReport() {}
MF93005 Error

everyMinutes() with an unsupported interval

ClockTriggerBuilder.everyMinutes() accepts only 1, 5, 10, 15, or 30. Any other number makes the trigger setup fail.

Quick fix: Use the nearest supported interval.

Flagged 1 changed line
function installTrigger() {
  ScriptApp.newTrigger("syncRows").timeBased().everyMinutes(2).create();
}
Not flagged 1 changed line
function installTrigger() {
  ScriptApp.newTrigger("syncRows").timeBased().everyMinutes(5).create();
}
MF93006 Error

doGet or doPost returns a template that was not evaluated

createTemplateFromFile() returns an HtmlTemplate. A web app needs the HtmlOutput that evaluate() produces, so returning the template itself fails.

Quick fix: Append .evaluate().

Flagged 1 changed line
function doGet(e) {
  return HtmlService.createTemplateFromFile("Index");
}
Not flagged 1 changed line
function doGet(e) {
  return HtmlService.createTemplateFromFile("Index").evaluate();
}
MF93007 Suggestion

JSON text output without a JSON MIME type

ContentService.createTextOutput() serves plain text unless setMimeType() says otherwise, so a JSON response arrives as text/plain.

Quick fix: Append .setMimeType(ContentService.MimeType.JSON).

Flagged 1 changed line
function doGet(e) {
  return ContentService.createTextOutput(JSON.stringify({ ok: true }));
}
Not flagged 1 changed line
function doGet(e) {
  return ContentService.createTextOutput(JSON.stringify({ ok: true })).setMimeType(ContentService.MimeType.JSON);
}
MF93008 Warning

Installable trigger on a simple trigger name

A function named onOpen, onEdit, onSelectionChange, or onInstall already runs as a simple trigger. An installable trigger that points at it makes the function run from both triggers, for example twice on every edit.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
function installEditTrigger() {
  ScriptApp.newTrigger("onEdit").forSpreadsheet(SpreadsheetApp.getActive()).onEdit().create();
}

function onEdit(e) {
  e.range.setNote("Edited");
}
Not flagged 2 changed lines
function installEditTrigger() {
  ScriptApp.newTrigger("onEditInstalled").forSpreadsheet(SpreadsheetApp.getActive()).onEdit().create();
}

function onEditInstalled(e) {
  e.range.setNote("Edited");
}
MF93009 Suggestion

onInstall() does not call onOpen()

When a user installs an editor add-on, only onInstall() runs, so the add-on menu stays missing until the file is reopened. Google's add-on samples call onOpen(e) from onInstall(e).

Quick fix: Add onOpen(e) as the first line of onInstall().

Flagged 1 changed line
function onOpen(e) {
  SpreadsheetApp.getUi().createAddonMenu().addItem("Run report", "runReport").addToUi();
}

function onInstall(e) {
  Logger.log("Installed");
}

function runReport() {}
Not flagged 1 changed line
function onOpen(e) {
  SpreadsheetApp.getUi().createAddonMenu().addItem("Run report", "runReport").addToUi();
}

function onInstall(e) {
  onOpen(e);
}

function runReport() {}
MF93010 Warning

Editor UI from a web app or background trigger

getUi() and Browser dialogs need an open Sheets, Docs, Slides, or Forms editor. When doGet, doPost, a time-driven trigger, or a form-submit trigger reaches them, directly or through other functions, the call throws. Calls inside a try block are skipped.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function doGet() {
  notify("Report ready");
  return HtmlService.createHtmlOutput("Done");
}

function notify(message) {
  SpreadsheetApp.getUi().alert(message);
}
Not flagged 1 changed line
function doGet() {
  notify("Report ready");
  return HtmlService.createHtmlOutput("Done");
}

function notify(message) {
  console.log(message);
}
MF93011 Warning

Visitor email in a web app that runs as you

A web app deployed to execute as you returns an empty string from Session.getActiveUser().getEmail() unless the visitor is in your Google Workspace domain. Deploy it to execute as the user accessing it when you need the visitor's email.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
// appsscript.json: "webapp": { "executeAs": "USER_DEPLOYING" }
function doGet() {
  const email = Session.getActiveUser().getEmail();
  return HtmlService.createHtmlOutput("Hello " + email);
}
Not flagged 1 changed line
// appsscript.json: "webapp": { "executeAs": "USER_ACCESSING" }
function doGet() {
  const email = Session.getActiveUser().getEmail();
  return HtmlService.createHtmlOutput("Hello " + email);
}
MF93012 Warning

Trigger created on every run

Simple triggers such as onOpen run without authorization, so creating a trigger from one fails. From doGet or doPost, each request adds another trigger until the project hits its limit of 20 per user. Create triggers from a menu item or a function you run once, or check getProjectTriggers() first.

Quick fix: None. The message explains the change to make.

Flagged 0 changed lines
function onOpen() {
  ScriptApp.newTrigger("sync").timeBased().everyHours(1).create();
}

function sync() {}
Not flagged 4 changed lines
function onOpen() {
  SpreadsheetApp.getUi().createMenu("Sync").addItem("Turn on hourly sync", "installSync").addToUi();
}

function installSync() {
  ScriptApp.newTrigger("sync").timeBased().everyHours(1).create();
}

function sync() {}

Ranges

MF94001 Error

getRange() with a zero or negative coordinate

Rows and columns start at 1, and a range needs at least one row and one column. Sheet.getRange(0, 1) or a literal 0 for numRows throws "The coordinates or dimensions of the range are invalid."

Quick fix: None. The message explains the change to make.

The coordinates or dimensions of the range are invalid

Flagged 1 changed line
function readHeader() {
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange(0, 1, 1, 5).getValues();
}
Not flagged 1 changed line
function readHeader() {
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange(1, 1, 1, 5).getValues();
}
MF94002 Error

setValues() data does not match the range size

setValues() and the other 2D setters need exactly one row of data per range row and one value per column. When getRange() has literal dimensions or A1 notation and the data is an array literal, a different row count throws "The number of rows in the data does not match the number of rows in the range." A different column count, or rows of uneven length, throws the same error for columns.

Quick fix: None. The message explains the change to make.

The number of columns in the data does not match the number of columns in the range

Flagged 1 changed line
function writeHeader() {
  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.getRange(1, 1, 1, 3).setValues([["Name", "Email"]]);
}
Not flagged 1 changed line
function writeHeader() {
  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.getRange(1, 1, 1, 2).setValues([["Name", "Email"]]);
}
MF94003 Warning

Row count from getLastRow() can be 0

getRange(2, 1, sheet.getLastRow() - 1, n) asks for zero rows on a sheet that holds only a header, and getRange() throws "The number of rows in the range must be at least 1." The rule stays quiet when the function compares the last row before reading. getLastColumn() counts are checked the same way.

Quick fix: Return early when the sheet has nothing past the header, in functions that return no value.

Flagged 0 changed lines
function readRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rows = sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues();
  Logger.log(rows.length);
}
Not flagged 1 changed line
function readRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  if (sheet.getLastRow() <= 1) return;
  const rows = sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues();
  Logger.log(rows.length);
}
MF94004 Warning

getLastRow() passed as a row count

The third argument of getRange() is a number of rows, not the last row. Starting at row 2 with getLastRow() rows reads one row past the data, and the extra rows come back as empty strings.

Quick fix: Subtract the rows above the start row.

Flagged 1 changed line
function readRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange(2, 1, sheet.getLastRow(), 3).getValues();
}
Not flagged 1 changed line
function readRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues();
}
MF94005 Warning

Rows deleted in a loop that counts up

deleteRow() moves every later row up by one. In a loop that counts up, the row after each deleted one is never visited. The rule stays quiet when the loop corrects the index, for example with i--.

Quick fix: Reverse the loop so it counts down.

Flagged 1 changed line
function removeDone() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const values = sheet.getDataRange().getValues();
  for (let i = 1; i < values.length; i++) {
    if (values[i][0] === "Done") sheet.deleteRow(i + 1);
  }
}
Not flagged 1 changed line
function removeDone() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const values = sheet.getDataRange().getValues();
  for (let i = values.length - 1; i >= 1; i--) {
    if (values[i][0] === "Done") sheet.deleteRow(i + 1);
  }
}
MF92004 Warning

for...in over an array

for...in yields each array index as a string, so with rows from getValues(), i + 2 is "02" and a getRange() row computed from it is wrong. It can also visit properties that are not elements. Use a counting for loop, for...of, or forEach().

Quick fix: Rewrite as a counting for loop over the array's length.

Flagged 1 changed line
function logRows() {
  const rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
  for (var i in rows) {
    Logger.log("Row " + (i + 1) + ": " + rows[i][0]);
  }
}
Not flagged 1 changed line
function logRows() {
  const rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
  for (var i = 0; i < rows.length; i++) {
    Logger.log("Row " + (i + 1) + ": " + rows[i][0]);
  }
}

Services and limits

MF95001 Warning

JSON request sent as form fields

When payload is an object, UrlFetchApp encodes it as form fields (a=1&b=2), whatever contentType says. An API expecting JSON then rejects the request.

Quick fix: Wrap the payload in JSON.stringify().

Flagged 1 changed line
function createItem() {
  UrlFetchApp.fetch("https://example.com/items", {
    method: "post",
    contentType: "application/json",
    payload: { name: "New item" },
  });
}
Not flagged 1 changed line
function createItem() {
  UrlFetchApp.fetch("https://example.com/items", {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({ name: "New item" }),
  });
}
MF95002 Warning

formatDate() pattern uses the wrong letters

Utilities.formatDate() patterns follow Java SimpleDateFormat. YYYY is the week year, which is off by one for some dates around January 1. DD is the day of the year, and mm is minutes, so dd/mm/yyyy prints the minutes where the month should be.

Quick fix: Use yyyy, dd, and MM.

Flagged 1 changed line
function stamp() {
  return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd/mm/YYYY");
}
Not flagged 1 changed line
function stamp() {
  return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd/MM/yyyy");
}
MF95003 Error

Utilities.sleep() longer than 5 minutes

Utilities.sleep() accepts at most 300000 milliseconds. A longer wait needs a time-driven trigger.

Quick fix: Use 300000.

Flagged 1 changed line
function waitAndRetry() {
  Utilities.sleep(600000);
}
Not flagged 1 changed line
function waitAndRetry() {
  Utilities.sleep(300000);
}
MF95004 Error

Cache expiration outside 1 to 21600 seconds

Cache entries last at most 6 hours. put() and putAll() reject an expiration above 21600 seconds or below 1.

Quick fix: Use the nearest allowed expiration.

Flagged 1 changed line
function remember(value) {
  CacheService.getScriptCache().put("latest", value, 86400);
}
Not flagged 1 changed line
function remember(value) {
  CacheService.getScriptCache().put("latest", value, 21600);
}
MF95005 Warning

Drive iterator next() without hasNext()

DriveApp.getFilesByName() and similar calls return an iterator. Calling next() when nothing matches throws, so a renamed or missing file stops the script with an error that does not name the file.

Quick fix: Store the iterator and throw a clear error when hasNext() is false.

Flagged 1 changed line
function reportName() {
  return DriveApp.getFilesByName("Report").next().getName();
}
Not flagged 3 changed lines
function reportName() {
  const files = DriveApp.getFilesByName("Report");
  if (!files.hasNext()) throw new Error("No file named Report.");
  return files.next().getName();
}
MF95006 Warning

muteHttpExceptions without a status check

With muteHttpExceptions: true, UrlFetchApp returns 4xx and 5xx responses instead of throwing. Code that reads the body without calling getResponseCode() treats an error page as data.

Quick fix: Throw when the status is 400 or above, with the response body in the message.

Flagged 0 changed lines
function fetchItems(url) {
  const response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
  return JSON.parse(response.getContentText());
}
Not flagged 3 changed lines
function fetchItems(url) {
  const response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
  if (response.getResponseCode() >= 400) {
    throw new Error("Request failed with status " + response.getResponseCode() + ": " + response.getContentText());
  }
  return JSON.parse(response.getContentText());
}
MF95007 Warning

tryLock() result ignored

tryLock() returns false when another run holds the lock past the timeout. If the result is not checked, the code after it runs without the lock it was meant to have.

Quick fix: Use waitLock(), which throws when the lock is not available in time.

Flagged 1 changed line
function addRow(values) {
  const lock = LockService.getScriptLock();
  lock.tryLock(10000);
  try {
    SpreadsheetApp.getActiveSheet().appendRow(values);
  } finally {
    lock.releaseLock();
  }
}
Not flagged 1 changed line
function addRow(values) {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000);
  try {
    SpreadsheetApp.getActiveSheet().appendRow(values);
  } finally {
    lock.releaseLock();
  }
}
MF95008 Suggestion

Lock never released

A lock from waitLock() or tryLock() is held until the script finishes when the function never calls releaseLock(). Other runs wait for that whole time. Release it in a finally block so an error does not keep it held either.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function addRow(values) {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000);
  SpreadsheetApp.getActiveSheet().appendRow(values);
  sendSummary();
}

function sendSummary() {}
Not flagged 5 changed lines
function addRow(values) {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000);
  try {
    SpreadsheetApp.getActiveSheet().appendRow(values);
  } finally {
    lock.releaseLock();
  }
  sendSummary();
}

function sendSummary() {}
MF95009 Warning

Key or token in the code

A string that looks like an API key, access token, or bearer token is readable by anyone who can view or copy the project, and it stays in the version history after you delete it. Keep it in Script Properties and read it with PropertiesService. The message never repeats the value.

Quick fix: Read the value from Script Properties; add the property under Project Settings and rotate the old key.

Flagged 1 changed line
function getOrders() {
  const response = UrlFetchApp.fetch("https://api.example.com/orders", {
    headers: { Authorization: "Bearer 3f9a7c2e1b8d4f6a0c5e9b7d2a4f8c1e" },
  });
  return JSON.parse(response.getContentText());
}
Not flagged 1 changed line
function getOrders() {
  const response = UrlFetchApp.fetch("https://api.example.com/orders", {
    headers: { Authorization: "Bearer " + PropertiesService.getScriptProperties().getProperty("API_TOKEN") },
  });
  return JSON.parse(response.getContentText());
}
MF95010 Suggestion

Query value not URL-encoded

A value placed after ?name= or &name= in a fetched URL goes in as typed. A space, &, #, or accented letter in it breaks the request or changes which parameters the server sees. Wrap the value in encodeURIComponent().

Quick fix: Wrap the value in encodeURIComponent().

Flagged 1 changed line
function search(term) {
  return UrlFetchApp.fetch("https://api.example.com/search?q=" + term);
}
Not flagged 1 changed line
function search(term) {
  return UrlFetchApp.fetch("https://api.example.com/search?q=" + encodeURIComponent(term));
}
MF95011 Suggestion

formatDate() time zone differs from the project

A time zone written into Utilities.formatDate() stays fixed when the project's time zone in appsscript.json changes, and it disagrees with it now. Session.getScriptTimeZone() follows the manifest. UTC and GMT offsets are not flagged.

Quick fix: Use Session.getScriptTimeZone().

Flagged 1 changed line
// appsscript.json: "timeZone": "Europe/London"
function label(date) {
  return Utilities.formatDate(date, "America/New_York", "yyyy-MM-dd");
}
Not flagged 1 changed line
// appsscript.json: "timeZone": "Europe/London"
function label(date) {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), "yyyy-MM-dd");
}

Removed and deprecated services

MF98001 Error

Service that Google shut down

UiApp, DocsList, ScriptDb, FinanceApp, and ContactsApp no longer run. Code that calls them fails, even where the editor still knows their names. Each message names the replacement, such as HtmlService for UiApp and the People advanced service for ContactsApp.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
function showDialog() {
  const app = UiApp.createApplication();
  return app;
}
Not flagged 1 changed line
function showDialog() {
  SpreadsheetApp.getUi().showModalDialog(HtmlService.createHtmlOutput("<p>Hello</p>"), "Hello");
}
MF98002 Warning

Deprecated method with a direct replacement

Session.getTimeZone(), Session.getUser(), ScriptApp.getProjectKey(), Utilities.jsonParse(), and Utilities.jsonStringify() are deprecated. Each has a replacement that takes the same arguments.

Quick fix: Replace the call with its successor.

Flagged 1 changed line
function zone() {
  return Session.getTimeZone();
}
Not flagged 1 changed line
function zone() {
  return Session.getScriptTimeZone();
}

Performance

MF90001 Warning

Range read inside a loop

Calling getValue() or another single-cell getter on each pass of a loop, or in a forEach() or map() callback, makes one service call per cell. Reading the block once with the plural getter is much faster.

Quick fix: Read the range once with the plural getter before the loop, when the loop shape allows it.

Service call inside a loop

Flagged 3 changed lines
function total() {
  const range = SpreadsheetApp.getActiveSheet().getRange(1, 1, 10, 1);
  let sum = 0;
  for (let row = 1; row <= 10; row++) {
    sum += range.getCell(row, 1).getValue();
  }
  return sum;
}
Not flagged 3 changed lines
function total() {
  const values = SpreadsheetApp.getActiveSheet().getRange(1, 1, 10, 1).getValues();
  let sum = 0;
  for (let row = 0; row < values.length; row++) {
    sum += values[row][0];
  }
  return sum;
}
MF90002 Warning

Range write inside a loop

Calling setValue() or another single-cell setter on each pass of a loop, or in a forEach() or map() callback, makes one service call per cell. Collecting a 2D array and writing it once is much faster.

Quick fix: Collect the values and write the range once with the plural setter, when the loop shape allows it.

Flagged 1 changed line
function fill() {
  const range = SpreadsheetApp.getActiveSheet().getRange(1, 1, 10, 1);
  for (let row = 1; row <= 10; row++) {
    range.getCell(row, 1).setValue(row * 2);
  }
}
Not flagged 3 changed lines
function fill() {
  const range = SpreadsheetApp.getActiveSheet().getRange(1, 1, 10, 1);
  const grid = [];
  for (let row = 1; row <= 10; row++) {
    grid.push([row * 2]);
  }
  range.setValues(grid);
}
MF90003 Warning

SpreadsheetApp.flush() inside a loop

flush() forces pending changes to the server. Inside a loop it repeats that round trip on every pass.

Quick fix: Guidance on when flush() can move after the loop.

Flagged 1 changed line
function mark(rows) {
  const sheet = SpreadsheetApp.getActiveSheet();
  for (const row of rows) {
    sheet.getRange(row, 1).setNote("done");
    SpreadsheetApp.flush();
  }
}
Not flagged 1 changed line
function mark(rows) {
  const sheet = SpreadsheetApp.getActiveSheet();
  for (const row of rows) {
    sheet.getRange(row, 1).setNote("done");
  }
  SpreadsheetApp.flush();
}
MF90004 Warning

UrlFetchApp.fetch() inside a loop

Each fetch() waits for the previous request to finish. UrlFetchApp.fetchAll() sends independent requests together.

Quick fix: Guidance on when fetchAll() gives the same result as the loop.

Flagged 5 changed lines
function fetchAll(urls) {
  const bodies = [];
  for (const url of urls) {
    bodies.push(UrlFetchApp.fetch(url).getContentText());
  }
  return bodies;
}
Not flagged 1 changed line
function fetchAll(urls) {
  return UrlFetchApp.fetchAll(urls).map((response) => response.getContentText());
}
MF90008 Warning

Gmail thread search without a limit

GmailApp.search(query), getInboxThreads(), and similar calls without a start and max return every matching thread, and Google notes they fail when the threads are too large to handle. Pass a start and max and page through the results.

Quick fix: Add a start of 0 and a max of 100.

Flagged 1 changed line
function countInvoices() {
  return GmailApp.search("subject:invoice").length;
}
Not flagged 1 changed line
function countInvoices() {
  return GmailApp.search("subject:invoice", 0, 100).length;
}
MF90009 Warning

Same lookup repeated inside a loop

openById(), getSheetByName(), getFileById(), and similar lookups return the same object when their argument does not change. Inside a loop they repeat the service call on every pass.

Quick fix: Move the const above the loop.

Flagged 1 changed line
function stampRows(rows) {
  const ss = SpreadsheetApp.getActive();
  for (const row of rows) {
    const log = ss.getSheetByName("Log");
    log.getRange(row, 2).setValue(new Date());
  }
}
Not flagged 1 changed line
function stampRows(rows) {
  const ss = SpreadsheetApp.getActive();
  const log = ss.getSheetByName("Log");
  for (const row of rows) {
    log.getRange(row, 2).setValue(new Date());
  }
}
MF90010 Warning

appendRow() inside a loop

Each appendRow() is a separate call to Sheets. Collecting the rows and writing them with one setValues() call is much faster for more than a few rows.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function logOrders(orders) {
  const sheet = SpreadsheetApp.getActiveSheet();
  orders.forEach((order) => sheet.appendRow([order.id, order.total]));
}
Not flagged 2 changed lines
function logOrders(orders) {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rows = orders.map((order) => [order.id, order.total]);
  if (rows.length > 0) sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 2).setValues(rows);
}
MF90011 Suggestion

Service call in a loop condition

A for or while condition runs before every pass, so sheet.getLastRow() there asks Sheets again each time. The rule stays quiet when the loop body writes to the sheet or calls project functions, since the value may then change.

Quick fix: Read the value into a const before the loop.

Flagged 1 changed line
function logRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  for (let row = 2; row <= sheet.getLastRow(); row++) {
    Logger.log(row);
  }
}
Not flagged 2 changed lines
function logRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();
  for (let row = 2; row <= lastRow; row++) {
    Logger.log(row);
  }
}
MF90012 Warning

Properties read or written one key at a time in a loop

getProperty() and setProperty() each call the Properties service. In a loop, read every value once with getProperties() and write changes together with setProperties().

Quick fix: None. The message explains the change to make.

Flagged 4 changed lines
function saveTotals(totals) {
  const props = PropertiesService.getScriptProperties();
  for (const name of Object.keys(totals)) {
    props.setProperty(name, String(totals[name]));
  }
}
Not flagged 3 changed lines
function saveTotals(totals) {
  const values = {};
  for (const name of Object.keys(totals)) values[name] = String(totals[name]);
  PropertiesService.getScriptProperties().setProperties(values);
}
MF90013 Warning

Cache read or written one key at a time in a loop

get(), put(), and remove() each call the Cache service. getAll(), putAll(), and removeAll() handle many keys in one call.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
function cachedNames(ids) {
  const cache = CacheService.getScriptCache();
  return ids.map((id) => cache.get("name:" + id));
}
Not flagged 3 changed lines
function cachedNames(ids) {
  const keys = ids.map((id) => "name:" + id);
  const values = CacheService.getScriptCache().getAll(keys);
  return keys.map((key) => values[key]);
}
MF90014 Suggestion

Mail sent in a loop without a quota check

Apps Script allows 100 email recipients a day on a consumer account and 1,500 on Google Workspace. A loop that sends mail without checking MailApp.getRemainingDailyQuota() fails partway through the list once the quota runs out.

Quick fix: None. The message explains the change to make.

Flagged 0 changed lines
function notify(addresses) {
  for (const address of addresses) {
    MailApp.sendEmail(address, "Report ready", "The weekly report is ready.");
  }
}
Not flagged 1 changed line
function notify(addresses) {
  for (const address of addresses) {
    if (MailApp.getRemainingDailyQuota() === 0) throw new Error("Daily email quota used up at " + address);
    MailApp.sendEmail(address, "Report ready", "The weekly report is ready.");
  }
}
MF90015 Suggestion

Open-ended range read

A range such as A2:C or A:A runs to the last row of the sheet, not the last row with data. getValues() then returns a row for every empty row too, often thousands of them, and loops over the result do the same.

Quick fix: End the range at getLastRow().

Flagged 1 changed line
function readOrders() {
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange("A2:C").getValues();
}
Not flagged 1 changed line
function readOrders() {
  const sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange("A2:C" + sheet.getLastRow()).getValues();
}

Custom functions

MF91001 Warning

Custom function calls a service that needs authorization

Functions marked @customfunction run without user authorization. GmailApp, DriveApp, UrlFetchApp, and similar services fail inside a formula.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
/** @customfunction */
function UNREAD() {
  return GmailApp.getInboxUnreadCount();
}
Not flagged 2 changed lines
/** @customfunction */
function DOUBLE(value) {
  return value * 2;
}
MF91002 Warning

Custom function modifies the spreadsheet

A custom function can only return values. Calls such as setValue(), appendRow(), or insertSheet() fail when Sheets evaluates the formula.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
/** @customfunction */
function STAMP() {
  SpreadsheetApp.getActiveSheet().appendRow([new Date()]);
}
Not flagged 2 changed lines
/** @customfunction */
function STAMP(value) {
  return [[value]];
}
MF91003 Warning

Custom function opens a dialog

Formula evaluation has no UI session, so SpreadsheetApp.getUi(), Browser.msgBox(), and Browser.inputBox() fail.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
/** @customfunction */
function ASK() {
  return Browser.inputBox("Value?");
}
Not flagged 2 changed lines
/** @customfunction */
function ASK(value) {
  return value;
}
MF91004 Warning

Range method called on a custom function argument

Sheets passes cell values or 2D arrays to custom functions, not Range objects. Calling getValues() on an argument throws a TypeError.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
/** @customfunction
 * @param {number[][]} input
 */
function SUMALL(input) {
  return input.getValues().flat().reduce((a, b) => a + b, 0);
}
Not flagged 1 changed line
/** @customfunction
 * @param {number[][]} input
 */
function SUMALL(input) {
  return input.flat().reduce((a, b) => a + b, 0);
}
MF91005 Warning

Async custom function or returned Promise

Sheets does not await Promises, so the cell shows #VALUE! or [object Promise].

Quick fix: Remove the async keyword.

Flagged 1 changed line
/** @customfunction */
async function LATER(value) {
  return value;
}
Not flagged 1 changed line
/** @customfunction */
function LATER(value) {
  return value;
}
MF91006 Warning

Custom function name ends with an underscore

Names ending in _ are private in Apps Script, so the formula returns #NAME?.

Quick fix: Remove the trailing underscore.

Flagged 1 changed line
/** @customfunction */
function DOUBLE_(value) {
  return value * 2;
}
Not flagged 1 changed line
/** @customfunction */
function DOUBLE(value) {
  return value * 2;
}
MF91007 Warning

Custom function name collides with a built-in formula or cell reference

A custom function named SUM or A1 is shadowed by the built-in formula or read as a cell reference.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
/** @customfunction */
function SUM(value) {
  return value;
}
Not flagged 1 changed line
/** @customfunction */
function SUMOF(value) {
  return value;
}
MF91008 Suggestion

Custom function depends on the time or a random value

Sheets caches a custom function's result until its arguments change, so Math.random(), Date.now(), and new Date() do not refresh.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
/** @customfunction */
function ROLL() {
  return Math.random();
}
Not flagged 2 changed lines
/** @customfunction */
function ROLL(seed) {
  return (seed * 9301 + 49297) % 233280;
}
MF91009 Suggestion

Custom function JSDoc @param does not match the parameters

Sheets builds formula help from @param tags, so a missing or misnamed tag hides the argument help.

Quick fix: Add a placeholder @param tag for each undocumented parameter.

Flagged 1 changed line
/**
 * @customfunction
 * @param {number} amount
 */
function TAX(value) {
  return value * 0.2;
}
Not flagged 1 changed line
/**
 * @customfunction
 * @param {number} value
 */
function TAX(value) {
  return value * 0.2;
}
MF91010 Warning

Custom function is not a top-level function declaration

Sheets only finds custom functions declared as top-level function statements, not arrow functions or class methods.

Quick fix: Convert the arrow function to a function declaration.

Flagged 1 changed line
/** @customfunction */
const DOUBLE = (value) => value * 2;
Not flagged 3 changed lines
/** @customfunction */
function DOUBLE(value) {
  return value * 2;
}

Files and global scope

MF92001 Error

require() is not available

Apps Script has no CommonJS module system, so require() throws ReferenceError at runtime.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
function parse(text) {
  const yaml = require("yaml");
  return yaml.parse(text);
}
Not flagged 1 changed line
function parse(text) {
  return JSON.parse(text);
}
MF92002 Error

import or export in a .gs file

Every .gs file runs as a script in one shared global scope. import and export statements are syntax errors when the project saves.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
export function greet(name) {
  return "Hello " + name;
}
Not flagged 1 changed line
function greet(name) {
  return "Hello " + name;
}
MF92003 Warning

Function declared more than once

Files share one global scope, so only the last declaration of a function survives. Which one wins depends on file order.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function helper() {
  return 1;
}

function helper() {
  return 2;
}
Not flagged 1 changed line
function helper() {
  return 1;
}

function otherHelper() {
  return 2;
}
MF92005 Warning

Browser global in server code

Apps Script runs .gs files on Google's servers, which have no window, document, localStorage, fetch(), setTimeout(), or alert(). Each one throws a ReferenceError. The message names the Apps Script service to use instead.

Quick fix: Use UrlFetchApp.fetch() for fetch() and getUi().alert() for alert().

Flagged 1 changed line
function getRates() {
  const response = fetch("https://api.example.com/rates");
  return JSON.parse(response.getContentText());
}
Not flagged 1 changed line
function getRates() {
  const response = UrlFetchApp.fetch("https://api.example.com/rates");
  return JSON.parse(response.getContentText());
}
MF92006 Warning

Service call outside a function

Apps Script runs every file's top-level code before each function call, including simple triggers and custom functions, which cannot use every service. A service call there runs on every execution, and when it fails, every function in the project fails with it.

Quick fix: None. The message explains the change to make.

Flagged 2 changed lines
const sheet = SpreadsheetApp.getActiveSheet();

function addRow(values) {
  sheet.appendRow(values);
}
Not flagged 1 changed line
function addRow(values) {
  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.appendRow(values);
}
MF92007 Error

Global from a later file used while loading

Apps Script loads files in the order the editor lists them. A class, let, or const declared in a later file does not exist yet while an earlier file's top-level code runs, so using it there throws "is not defined". Code inside functions runs later and is fine. The check runs once the editor knows the file order.

Quick fix: None. The message explains the change to make.

Flagged 3 changed lines
const ORDERS_SHEET = CONFIG.sheetName;

function readOrders() {
  return SpreadsheetApp.getActive().getSheetByName(ORDERS_SHEET).getDataRange().getValues();
}
Not flagged 1 changed line
function readOrders() {
  return SpreadsheetApp.getActive().getSheetByName(CONFIG.sheetName).getDataRange().getValues();
}

HTML pages and templates

MF97001 Error

HTML file not found

createHtmlOutputFromFile() and createTemplateFromFile() look up the file by its exact name, without .html. A name that matches no HTML file in the project throws when the line runs. The check runs once the editor knows every file in the project.

Quick fix: Change the name to the closest HTML file in the project.

Flagged 1 changed line
function doGet() {
  return HtmlService.createHtmlOutputFromFile("Pages");
}
Not flagged 1 changed line
function doGet() {
  return HtmlService.createHtmlOutputFromFile("Page");
}
MF97002 Warning

Template opened as plain HTML

createHtmlOutputFromFile() serves the file as it is, so <? ?> scriptlets in it show on the page as text. A file with scriptlets needs createTemplateFromFile(...).evaluate().

Quick fix: Switch to createTemplateFromFile(...).evaluate().

Flagged 1 changed line
function doGet() {
  return HtmlService.createHtmlOutputFromFile("Page");
}
Not flagged 1 changed line
function doGet() {
  return HtmlService.createTemplateFromFile("Page").evaluate();
}
MF97003 Warning

Unescaped scriptlet

<?!= prints a value without escaping it. When the value comes from a user, a sheet, or e.parameter, a visitor can put their own HTML or script on the page. <?= escapes the value; keep <?!= for including another file's HTML.

Quick fix: Print the value escaped with <?=.

Flagged 1 changed line
<p>Hello, <?!= name ?></p>
Not flagged 1 changed line
<p>Hello, <?= name ?></p>
MF97004 Warning

Included file printed as text

An include() helper returns another file's HTML. Printing it with <?= escapes that HTML, so the page shows the markup as text instead of the styles or script it holds.

Quick fix: Insert the HTML with <?!=.

Flagged 1 changed line
<head><?= include("Styles") ?></head>
Not flagged 1 changed line
<head><?!= include("Styles") ?></head>
MF97005 Error

google.script.run calls a missing or private function

google.script.run can call only top-level functions of the project whose names do not end in an underscore. A typo or a private function fails in the browser with a 'not a function' error. The check runs once the editor knows every file in the project.

Quick fix: Call the closest server function.

Flagged 1 changed line
<script>
  google.script.run.withSuccessHandler(console.log).getRow();
</script>
Not flagged 1 changed line
<script>
  google.script.run.withSuccessHandler(console.log).getRows();
</script>
MF97006 Warning

Date returned to google.script.run

google.script.run passes numbers, strings, booleans, arrays, and plain objects back to the page. A Date is not one of them, so the call fails and the success handler never runs. Return an ISO string or a formatted date instead.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
function lastRun() {
  return new Date();
}
Not flagged 1 changed line
function lastRun() {
  return new Date().toISOString();
}

Manifest and OAuth scopes

MF96010 Error

Call needs an OAuth scope the manifest does not list

When appsscript.json lists oauthScopes, Apps Script grants only those scopes. A call whose reference page requires a scope missing from the list fails with a permission error at runtime. The check uses the Authorization section of each method's reference page.

Quick fix: Add one of the accepted scopes to oauthScopes in appsscript.json.

Flagged 1 changed line
// appsscript.json: "oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly"]
function copyTotals() {
  const source = SpreadsheetApp.openById("1AbCdEf");
  return source.getRange("A1").getValue();
}
Not flagged 1 changed line
// appsscript.json: "oauthScopes": ["https://www.googleapis.com/auth/spreadsheets"]
function copyTotals() {
  const source = SpreadsheetApp.openById("1AbCdEf");
  return source.getRange("A1").getValue();
}
MF96011 Suggestion

Listed OAuth scope that no call needs

Every scope in oauthScopes appears on the consent screen, and sensitive scopes can require app verification. A built-in service scope that no call in the project accepts is likely left over. Projects with libraries, advanced services, or add-on or Chat configuration are skipped, since those can need scopes the code does not show.

Quick fix: Remove the scope from oauthScopes.

Flagged 2 changed lines
{
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets.currentonly",
    "https://www.googleapis.com/auth/drive"
  ]
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets.currentonly"
  ]
}
MF96012 Warning

Fetched URL is not in urlFetchWhitelist

When appsscript.json lists urlFetchWhitelist, deployed versions can fetch only URLs under those prefixes. A literal URL passed to UrlFetchApp that no prefix covers fails once the script is deployed.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
// appsscript.json: "urlFetchWhitelist": ["https://api.example.com/v1/"]
function loadRates() {
  return UrlFetchApp.fetch("https://api.example.com/v2/rates").getContentText();
}
Not flagged 1 changed line
// appsscript.json: "urlFetchWhitelist": ["https://api.example.com/v2/"]
function loadRates() {
  return UrlFetchApp.fetch("https://api.example.com/v2/rates").getContentText();
}
MF96004 Warning

Project runs on the Rhino runtime

Without runtimeVersion, or with "STABLE" or "DEPRECATED_ES5", Apps Script runs the legacy Rhino runtime, which lacks modern JavaScript such as classes and arrow functions in some contexts. myFunction checks code as V8.

Quick fix: Set runtimeVersion to "V8".

Flagged 0 changed lines
{
  "timeZone": "America/Denver"
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "timeZone": "America/Denver"
}
MF96002 Error

Invalid manifest value

A manifest field has the wrong JSON type or a value Apps Script does not accept, such as a web app access level, an exceptionLogging setting, a time zone, or an oauthScopes entry that is not a scope URL.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
{
  "runtimeVersion": "V8",
  "webapp": { "access": "EVERYONE", "executeAs": "USER_DEPLOYING" }
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "webapp": { "access": "ANYONE", "executeAs": "USER_DEPLOYING" }
}
MF96005 Error

Incomplete library or advanced service entry

Each entry in dependencies.enabledAdvancedServices needs serviceId, userSymbol, and version, and each library needs libraryId, userSymbol, and version. Two entries cannot share a userSymbol.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
{
  "runtimeVersion": "V8",
  "dependencies": {
    "enabledAdvancedServices": [{ "serviceId": "drive", "userSymbol": "Drive" }]
  }
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "dependencies": {
    "enabledAdvancedServices": [{ "serviceId": "drive", "userSymbol": "Drive", "version": "v3" }]
  }
}
MF96006 Error

Invalid urlFetchWhitelist prefix

Each allowlist prefix must use https://, name a full domain, and have a non-empty path such as a trailing slash. A single * can only stand for the leading subdomain.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
{
  "runtimeVersion": "V8",
  "urlFetchWhitelist": ["https://api.example.com"]
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "urlFetchWhitelist": ["https://api.example.com/"]
}
MF96003 Warning

Unknown manifest field

appsscript.json accepts a fixed set of top-level fields. A misspelled field, such as oauthScope for oauthScopes, has no effect.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
{
  "runtimeVersion": "V8",
  "oauthScope": ["https://www.googleapis.com/auth/spreadsheets"]
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "oauthScopes": ["https://www.googleapis.com/auth/spreadsheets"]
}
MF96001 Error

Manifest is not valid JSON

The Apps Script editor will not save a project whose appsscript.json does not parse. Trailing commas and comments are the usual causes.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
{
  "runtimeVersion": "V8",
  "timeZone": "America/Denver",
}
Not flagged 1 changed line
{
  "runtimeVersion": "V8",
  "timeZone": "America/Denver"
}
MF96013 Suggestion

GmailApp used only to send mail

GmailApp needs the scope to read, send, and delete all of a user's mail. When a project only sends mail with GmailApp.sendEmail() and uses no from address, MailApp.sendEmail() takes the same arguments and asks only to send mail as the user.

Quick fix: Send with MailApp.

Flagged 1 changed line
function notify(to) {
  GmailApp.sendEmail(to, "Report ready", "The weekly report is in the shared folder.");
}
Not flagged 1 changed line
function notify(to) {
  MailApp.sendEmail(to, "Report ready", "The weekly report is in the shared folder.");
}
MF96014 Suggestion

Broader OAuth scope than the code needs

When every call that relies on a listed scope also accepts a narrower one, such as drive.readonly instead of drive, listing the narrower scope gives the script less access and shows users a smaller request. Projects with libraries, advanced services, or add-on configuration are skipped.

Quick fix: Replace the scope with the narrower one.

Flagged 1 changed line
{
  "oauthScopes": ["https://www.googleapis.com/auth/drive"]
}
Not flagged 1 changed line
{
  "oauthScopes": ["https://www.googleapis.com/auth/drive.readonly"]
}
MF96015 Error

Anonymous web app that runs as the visitor

A web app that executes as the user accessing it needs a signed-in user to run as, so it cannot allow anonymous access. Deploy it to execute as you, or require visitors to sign in.

Quick fix: Run the web app as you, or require sign-in.

Flagged 1 changed line
{
  "webapp": { "access": "ANYONE_ANONYMOUS", "executeAs": "USER_ACCESSING" }
}
Not flagged 1 changed line
{
  "webapp": { "access": "ANYONE_ANONYMOUS", "executeAs": "USER_DEPLOYING" }
}
MF96016 Warning

Advanced service not turned on

Advanced services such as Drive, Sheets, or Gmail exist only after you add them under Services, which lists them in dependencies.enabledAdvancedServices. Until then, code that uses one throws "is not defined". The check runs when the editor can read appsscript.json.

Quick fix: Add the service to dependencies.enabledAdvancedServices in appsscript.json.

Flagged 1 changed line
// appsscript.json lists no advanced services
function listFiles() {
  return Drive.Files.list({ pageSize: 10 });
}
Not flagged 1 changed line
// appsscript.json: { "userSymbol": "Drive", "version": "v3", "serviceId": "drive" }
function listFiles() {
  return Drive.Files.list({ pageSize: 10 });
}
MF96017 Warning

Library not added

Code that calls OAuth2, OAuth1, Cheerio, FirebaseApp, or BetterLog expects the open-source library by that identifier. Until the library is added under Libraries, the identifier is not defined. The check runs when the editor can read appsscript.json.

Quick fix: None. The message explains the change to make.

Flagged 1 changed line
// appsscript.json lists no libraries
function getService() {
  return OAuth2.createService("github");
}
Not flagged 1 changed line
// appsscript.json lists the OAuth2 library
function getService() {
  return OAuth2.createService("github");
}

Type coverage

MF90005 Suggestion

Uncurated API signature

Some Apps Script methods have no verified parameter list yet. myFunction marks the call so you check the reference before adding arguments.

Quick fix: Guidance to check the Apps Script reference before adding arguments.

Flagged 2 changed lines
function clientAction() {
  return AddOnsResponseService.newGmailClientAction();
}
Not flagged 2 changed lines
function message() {
  return AddOnsResponseService.newChatMessage();
}
MF90006 Suggestion

Value typed as any

A declaration or return value typed as any turns off checking for every use of it. Adding a JSDoc type restores completions and diagnostics.

Quick fix: None. The message explains the change to make.

Flagged 0 changed lines
function readConfig(text) {
  const config = JSON.parse(text);
  return config.name;
}
Not flagged 2 changed lines
/** @param {string} text */
function readConfig(text) {
  /** @type {{ name: string }} */
  const config = JSON.parse(text);
  return config.name;
}
MF90007 Warning

@types/google-apps-script type in JSDoc

JSDoc annotations such as GoogleAppsScript.Events.DoPost or GoogleAppsScript.Spreadsheet.Range come from DefinitelyTyped's @types/google-apps-script package. Replacing them with myFunction's global types (Events.DoPost, SpreadsheetApp.Range) uses the runtime-verified declarations directly.

Quick fix: Replace the GoogleAppsScript.* type with the matching global Apps Script type.

Flagged 1 changed line
/** @param {GoogleAppsScript.Events.DoPost} e */
function doPost(e) {
  return ContentService.createTextOutput(e.postData.contents);
}
Not flagged 1 changed line
/** @param {Events.DoPost} e */
function doPost(e) {
  return ContentService.createTextOutput(e.postData.contents);
}