Error Handling Patterns for Google Ads Scripts
Ship a dry-run flag on anything that writes, wrap each loop iteration so one bad entity cannot stop the run, treat an unexpectedly empty result set as a failure rather than a clean run, and stop yourself before the thirty minute cancellation leaves changes half applied.
A script that fails loudly costs you five minutes. A script that fails quietly costs you a month, because everything downstream keeps behaving as if the report arrived. Most of the discipline below is about making silence impossible.
The dry-run flag
Every script that writes should compute the full change set first, log it, and apply nothing until one variable says otherwise. It costs about six lines:
const DRY_RUN = true; // set false only after reading the log
function apply(entity, change, label) {
if (DRY_RUN) {
Logger.log('WOULD ' + label + ': ' + entity);
return false;
}
change();
Logger.log('DID ' + label + ': ' + entity);
return true;
}
The log is the same shape in both modes, so what you read during the preview is what will happen. That equivalence is the point — a dry run that produces different output from the real run is not a dry run.
Isolate the loop body
One bad entity should not stop the other four hundred. Wrap each iteration, collect the failures, and report them at the end:
const errors = [];
for (const item of items) {
try {
process(item);
} catch (e) {
errors.push(item.name + ': ' + String(e).substring(0, 200));
}
}
if (errors.length) {
Logger.log(errors.length + ' failures:n' + errors.join('n'));
}
This matters most in manager scripts, where a single account with a missing conversion action can otherwise take down the whole run.
Guard against the empty result
The most dangerous return value in scripts is an empty set, because it looks like success. A query that returns nothing produces a clean run, an empty email and a green tick.
Assert what you expect. If a report over thirty days returns zero rows in an account you know is spending, that is a failure and should be logged as one, not passed through as “nothing to report”.
If your script only emails when something is wrong, then no email means either a healthy account or a script that has not run since March. Either send a one-line all-clear on a low frequency, or make a habit of checking the run history. Silent monitoring degrades without any visible signal.
Handle the time limit before you hit it
Scripts are cancelled at thirty minutes, and changes made before the cancellation are kept — which is how a writing script leaves an account half modified. Track elapsed time and stop yourself:
const start = Date.now();
const BUDGET_MS = 20 * 60 * 1000; // stop well before the wall
if (Date.now() - start > BUDGET_MS) {
Logger.log('Time budget reached, stopping cleanly.');
break;
}
Stopping yourself at twenty minutes gives you a complete log and a clean stopping point. Being stopped at thirty gives you neither.
Fetching URLs without failing the run
Any script that calls UrlFetchApp is depending on servers you do not control. Always pass muteHttpExceptions: true, because otherwise a single 500 throws and ends the script. Catch the exception separately from the status code: a timeout and a 404 are different findings and should be reported differently.
Expect false positives from bot protection. A server that blocks a request without a browser-like user agent will report a page as broken when it is fine, which is the first thing to check when a URL check flags something you know works.
A short checklist
- Configuration at the top, in named constants, never buried in the logic.
- Dry-run flag on anything that writes.
- Try block inside every loop that touches an external entity.
- An explicit failure when a result set is unexpectedly empty.
- A self-imposed time budget below the platform limit.
- Every write logged with enough detail to reverse it by hand.
Questions
- What is a dry run in a Google Ads script?
- A mode where the script computes every change and logs what it would do without applying anything. The log should be identical in shape to a real run, so what you read is what will happen.
- Why is an empty result set dangerous?
- Because it looks like success. A broken query produces a clean run, an empty email and no error. Assert what you expect and log a failure when the result is implausible.
- What happens if a script is cancelled at thirty minutes?
- Every change made before the cancellation is kept, which can leave a writing script half applied. Track elapsed time and stop cleanly well before the limit.
- Why does my URL checker report working pages as broken?
- Many servers block requests that do not look like a browser. Set a browser-like user agent and use muteHttpExceptions so a single failed request does not end the script.