Google Ads script
/**
* Goodlabs Google Ads script: broken landing page checker
* Author: Goodlabs (https://goodlabs.kz)
* Schedule: Daily at 06:00
*/
var CONFIG = {
SPREADSHEET_URL: "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
NOTIFICATION_EMAIL: "analytics@yourdomain.com",
PAUSE_BROKEN_ADS: false,
OUT_OF_STOCK_REGEX: "Out of stock|Товар распродан|Нет в наличии",
TIMEOUT_MS: 10000
};
function main() {
var brokenCount = 0;
var sheet = getOrCreateSheet(CONFIG.SPREADSHEET_URL);
var checkedUrls = {};
Logger.log("Starting Goodlabs Broken URL Audit...");
var adIterator = AdsApp.ads()
.withCondition("Status = ENABLED")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.get();
while (adIterator.hasNext()) {
var ad = adIterator.next();
var url = ad.urls().getFinalUrl();
if (!url) continue;
var result = checkedUrls[url] || checkUrl(url);
checkedUrls[url] = result;
if (!result.ok) {
brokenCount++;
sheet.appendRow([
new Date(),
ad.getCampaign().getName(),
ad.getAdGroup().getName(),
ad.getId(),
url,
result.statusCode,
result.error
]);
if (CONFIG.PAUSE_BROKEN_ADS) {
ad.pause();
Logger.log("PAUSED broken ad: " + ad.getId() + " (" + url + ")");
}
}
}
Logger.log("Audit finished. Broken URLs detected: " + brokenCount);
if (brokenCount > 0 && CONFIG.NOTIFICATION_EMAIL) {
MailApp.sendEmail(
CONFIG.NOTIFICATION_EMAIL,
"⚠️ Google Ads Alert: " + brokenCount + " Broken URLs Found",
"The broken landing page check found " + brokenCount + " failing landing pages in your Google Ads account.\n\n" +
"Review the full report here: " + CONFIG.SPREADSHEET_URL
);
}
}
function checkUrl(url) {
try {
var response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
followRedirects: true,
validateHttpsCertificates: true,
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
});
var code = response.getResponseCode();
if (code >= 400) {
return { ok: false, statusCode: code, error: "HTTP Error " + code };
}
var text = response.getContentText();
var oosPattern = new RegExp(CONFIG.OUT_OF_STOCK_REGEX, "i");
if (CONFIG.OUT_OF_STOCK_REGEX && oosPattern.test(text)) {
return { ok: false, statusCode: code, error: "Out of Stock Detected" };
}
return { ok: true, statusCode: code, error: "" };
} catch (err) {
return { ok: false, statusCode: 0, error: err.toString() };
}
}
function getOrCreateSheet(url) {
try {
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getActiveSheet();
if (sheet.getLastRow() === 0) {
sheet.appendRow(["Timestamp", "Campaign", "Ad Group", "Ad ID", "Final URL", "HTTP Status", "Error Details"]);
sheet.getRange(1, 1, 1, 7).setFontWeight("bold").setBackground("#f3f4f6");
}
return sheet;
} catch(e) {
Logger.log("Spreadsheet error: " + e);
return { appendRow: function() {} };
}
}How it works
- Walks every enabled ad in enabled campaigns and ad groups with
AdsApp.ads().withCondition("Status = ENABLED"). - Requests each final URL with
UrlFetchApp.fetch()under a normal Chrome User-Agent. Apps Script fetches are synchronous, so run time grows with the number of distinct URLs, which is why checked URLs are cached. - Flags 4xx and 5xx responses and pages whose body matches the out-of-stock pattern.
- Writes the time, campaign, ad group, URL and error to the Google Sheet, pauses the ad if you turned that on, and emails a summary.
Installation
- In Google Ads, open Tools > Bulk actions > Scripts.
- Click the plus button to create a script.
- Paste the code from this page into the editor.
- Create a Google Sheet and put its URL in SPREADSHEET_URL.
- Click Authorize to give the script access to Google Ads and Sheets.
- Run Preview, then save and schedule it daily, for example at 06:00.
Questions
Will this slow down my ads or my site?
It runs on Google's side, outside ad serving, so ads are unaffected. It requests each unique URL on your site once per run, which is why checked URLs are cached.
Does it check keywords and sitelinks?
Ads only. Keyword final URLs and sitelinks need one more loop each over AdsApp.keywords() and the sitelink selector, passed through the same checkUrl().