// Apps Script

Fix conditional catch clause syntax errors under V8.

Google Apps Script's V8 runtime rejects Mozilla-style conditional catch clauses (catch (e if ...)) with a syntax error. Learn how to convert them to standard instanceof branches with correct rethrow logic.

I migrated my script to the V8 runtime and now it fails immediately with "Unexpected token if" inside a catch block that worked fine before.

The script

copy · paste · trigger
error-handling.gs
Apps Script
// Replace Rhino conditional catch with V8-compatible instanceof branches
function parseSheetRow(row) {
  try {
    var value = JSON.parse(row[0]);
    return processValue(value);
  } catch (e) {
    if (e instanceof SyntaxError) {
      Logger.log('Bad JSON in row: ' + row[0]);
      return null;
    } else if (e instanceof RangeError) {
      Logger.log('Value out of range: ' + e.message);
      return null;
    } else {
      throw e;
    }
  }
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

Why V8 rejects the old syntax

Rhino, the JavaScript engine Apps Script used before 2020, was built on Mozilla's SpiderMonkey lineage and inherited a non-standard extension: conditional catch clauses. The syntax looked like catch (e if e instanceof SyntaxError), and Rhino treated each clause as a separate handler that only matched when its condition was true.

V8 — the engine behind Chrome and Node.js — never implemented this extension. It expects a single identifier after catch (, nothing more. When it hits the if keyword in that position, parsing stops entirely and throws "Unexpected token if" before a single line of your function body runs. The error appears at the catch line, which can be confusing if you expect runtime errors to come from inside the try block.

If your project was created before 2020 and you recently enabled the V8 runtime (or Google migrated it for you), this is the most common immediate breakage. Scripts that ran for years under Rhino hit this on first execution under the new engine.

The conversion pattern — and the rethrow you cannot skip

The mechanical fix is straightforward: collapse all conditional catch clauses into one catch (e) block, then use if/else if chains on instanceof inside it. Each branch handles exactly the error type the old conditional clause matched.

The part people drop is the final else { throw e; }. Under the old Rhino style, any error that didn't match a conditional clause propagated automatically — there was no default handler, so unmatched errors kept bubbling. In the single-catch pattern there is no automatic fallthrough. If you omit the rethrow, every error type that isn't explicitly handled gets silently swallowed: your function returns undefined, no stack trace appears in the logs, and you spend an afternoon wondering why downstream code receives unexpected nulls.

The first time I hit this, I spent two hours chasing a quota-exceeded error from UrlFetchApp that was being eaten by a catch block that only meant to handle JSON parse failures. The rethrow is not optional.

If you use a custom error class (function NetworkError(msg) { this.message = msg; } NetworkError.prototype = Object.create(Error.prototype);), instanceof works the same way in V8, so no changes are needed to your error constructors.

Checking for remaining Rhino-only syntax before deploying

Conditional catch is the most common migration breakage, but a few other Mozilla extensions can appear in older scripts: for each...in loops (for iterating E4X XML, now irrelevant), the __iterator__ protocol, and E4X XML literals. None of these are valid V8 syntax.

A quick way to surface them before going to production: open your script in the Apps Script editor with V8 enabled, then run any function. The parser reports all syntax errors before execution, so you get a full list rather than one failure at a time. Fix each one, save, and run again. Repeat until execution actually starts.

Once the script runs past the parse phase, watch the Executions log (View > Executions) rather than just Logger output. Unhandled throws and quota errors appear there with stack traces, which makes the rethrow-else branch observable in practice.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Does Apps Script still support the Rhino runtime so I can avoid rewriting catch blocks?
Google has not removed Rhino yet, but new projects default to V8 and the Rhino option may disappear at any point — it is already absent from the official documentation as a recommended choice. Rewriting conditional catches is a ten-minute fix per file; deferring it means the migration happens on Google's schedule, not yours.
My custom error type doesn't match instanceof after the migration. Why?
This happens when the constructor does not set up the prototype chain correctly. Under Rhino, some instanceof checks were lenient. Under V8, you need Object.create(Error.prototype) assigned to YourError.prototype, and new.target or explicit prototype assignment in the constructor. If you are using ES6 class syntax with extends Error, V8 handles this correctly and instanceof will work.
Can I match on error message strings instead of instanceof if I don't control the error type?
Yes. If the error comes from a built-in Apps Script service (SpreadsheetApp, UrlFetchApp, etc.) that throws a plain Error object with a descriptive message, checking e.message with indexOf or a regular expression inside the catch block is the practical fallback. Just be specific enough that the match doesn't accidentally catch unrelated errors with similar message text.
The syntax error message says 'Unexpected token if' but points to a line number that seems wrong. How do I find the actual clause?
V8's parser error line numbers in the Apps Script editor are sometimes off by one or two lines when catch clauses span multiple lines. Search the file for the literal string 'catch (' and inspect each one — any catch clause where the identifier is followed by if is the offender. There is no legitimate V8 syntax that puts a keyword directly after the catch variable name.