Skip to content

Broken landing page checker

Requests the final URL of every enabled ad, then logs any 4xx, 5xx or out-of-stock response to a Google Sheet and emails the summary. Can pause the ads it finds.

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

  1. Walks every enabled ad in enabled campaigns and ad groups with AdsApp.ads().withCondition("Status = ENABLED").
  2. 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.
  3. Flags 4xx and 5xx responses and pages whose body matches the out-of-stock pattern.
  4. 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

  1. In Google Ads, open Tools > Bulk actions > Scripts.
  2. Click the plus button to create a script.
  3. Paste the code from this page into the editor.
  4. Create a Google Sheet and put its URL in SPREADSHEET_URL.
  5. Click Authorize to give the script access to Google Ads and Sheets.
  6. 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().

Need one adapted to your account?

We write Google Ads scripts, set up server-side GA4 and build reports that answer a specific question.