Google Ads Script – Check for Redirects

Change happens.

A couple of months ago I went out to visit my favorite steak restaurant. Turned out it moved to the other side of the city.

Bummer…

Websites are no exception.

Your SEO colleagues or web developers might have decided it was time to change the URL structure of the website, redirecting visitors all over the place.
Your client might just have shifted from HTTP to HTTPS.
All sorts of things can result in your Final URLs not working properly anymore.

Luckily for us, there is the broken link checker script to prevent us from paying for clicks to non-functioning web pages.
Sadly though this script does NOT check for redirects.

And guess what!
Redirects hurt your account’s performance.

There’s evidence straight from the horse’s mouth: Google Ads Product Management Lead Jon Diorio shared this tweet:

 

 

Let me repeat that:
“using 301/302 redirects slows things down which can impact your QS”.

Does that get your brain tickling already?

Lower QS implicates lower ad rank, which might lead to fewer impressions, lower avg position, fewer clicks and/or higher CPC’s.
Also, did you know, redirecting from HTTP to HTTPS might hurt your analytics data?

So, I wondered: with many of my clients redirecting HTTP traffic to HTTPS, was I taking a beating now?
I decided to run some tests.

Three different accounts, three campaign experiments on exact match SKAG campaigns, with Final URLs in the EXP being set to HTTPS.
Run for 30 days.

Here’s the result:

Image

(Note: Avg QS and Avg Pos are click weighted averages)

What do these numbers tell us?

I don’t know. I’m still trying to get my head around it. Loads of factors come into play, and I probably need a better setup for my experiments.
What I do see though is that QS improved, or hardly changed at all by changing the Final URL’s redirecting URL to its destination.

This got me thinking overnight and convinced me to get up at 4 AM to write a script that would alert me when I have redirecting URLs in my account.

4 AM!?! Really?
No, haha, no way! It was 6 AM. Still felt the same for me though…

The next steps were easy. Here’s what I did:

  • Step 1: Fire up my Domobar
  • Step 2: Create a new Google sheet
  • Step 3: Decide upon an email address to receive email alerts 
  • Step 4: Write a Google Ads script to monitor Final URLs of all enabled ads, keywords, and sitelinks.
    Let’s have the script report alerts in the Google sheet and send me an ALERT email if there is a redirect.
  • Step 5: Schedule the script to run weekly

Here’s the Redirect Alert script:

➥  ACTION: Set the alarm for tomorrow morning 6 AM and install the script.

Oh, and when you are on it, here’s another benefit of using scripts:
Share the list of redirects with your SEO colleagues so they can make sure no link juice gets lost. You might impress them.

(Don’t worry if you have never run a script before. You do not need any coding skills. It is as simple as copy-paste.)

INSTRUCTIONS:

  1. See the script code below. Install the script in your account.
    Don’t worry if you have never done this before. You do not need any coding skills. It is as simple as copy-paste. Simply follow these instructions on how to set up and schedule google ads scripts.
  2. Create a new Google Sheet
    (tip for chrome users: simply type ‘sheets.new’ in the address bar)
  3. Add the complete URL of the spreadsheet to the script (line 14)
  4. Add your email address to the script (line 15)
  5. Preview
  6. Schedule to run weekly.

/**
*
* Report Redirects
*
* Creates a report indicating which ads, sitelinks and keywords have urls that are redirects
*
* @author: Nils Rooijmans
*
* contact nils@nilsrooijmans.com for questions and a High Performance MCC version of the script	
*/

// CHANGE SETTINGS HERE

var SPREADSHEET_URL = "YOUR_SHEET_URL_GOES_HERE" ;  //insert a new blank spreadsheet url
var EMAIL = "YOUR_EMAIL_ADDRESS_GOES_HERE"; //insert your email


// NO CHANGES NEEDED BELOW THIS LINE

var HTTP_OPTIONS = {'followRedirects':false };

var REDIRECT_CODES = [301,302, 303, 307, 308];
var ERRORCODE = "ERROR TESTING URL";

var urlResponseCodes = []; // store http response code objects for urls in the account
var totalNumberOfRedirects = 0; // how many redirect(s) did we detect in total?

var sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL).getActiveSheet();

function main() {
  
  // prepare the sheet
  sheet.setName("Redirect Report");
  sheet.clearContents();
  if (sheet.getRange('A1').isBlank()) {
    sheet.appendRow(["campaign name", "ad group name", "ad id",  "keyword", "sitelink", "url", "response code", "clicks"]);
  }
  
  // check ad urls
  var adSelector = AdWordsApp.ads().withCondition("Status = 'ENABLED'").withCondition("AdGroupStatus = 'ENABLED'").withCondition("CampaignStatus = 'ENABLED'");
  var adIterator = adSelector.get();
  
	while (adIterator.hasNext()) {
  	var ad = adIterator.next();
	  checkAdUrls(ad);
  }
  
  // check keyword urls
  var keywordSelector = AdWordsApp.keywords().withCondition("Status = 'ENABLED'").withCondition("AdGroupStatus = 'ENABLED'").withCondition("CampaignStatus = 'ENABLED'");
  var keywordIterator = keywordSelector.get();
  
	while (keywordIterator.hasNext()) {
  	var keyword = keywordIterator.next();
	  checkKeywordUrls(keyword);
  }  
  
  // check sitelink urls
  var adGroupSelector = AdWordsApp.adGroups().withCondition("Status = 'ENABLED'").withCondition("CampaignStatus = 'ENABLED'");
  var adGroupIterator = adGroupSelector.get();
  
	while (adGroupIterator.hasNext()) {
  	var adGroup = adGroupIterator.next();
	  checkSitelinkUrls(adGroup);
  }  
  
  // if we have redirects, send email alert
  if (totalNumberOfRedirects > 0) {
    // prepare the email
    var html = [];
    html.push(
      "<html>",
      "<body>", 
      "<p>New Google Ads Redirect Report Available at: "+SPREADSHEET_URL+"</p>",
      "<P>Number of Redirects found: "+totalNumberOfRedirects+"</p>",
      "</body>",
      "</html>"
    );
    
    MailApp.sendEmail(EMAIL, "New Redirect Report Available", "", {htmlBody: html.join("\n")});
  }
  
  Logger.log("Report available at " + SPREADSHEET_URL);
  
}

function checkAdUrls(ad) {

  var responseCode;
   
  var finalUrl = ad.urls().getFinalUrl();
  if (finalUrl != null) {
    responseCode = getResponseCode(finalUrl);
    if (isInArray(responseCode, REDIRECT_CODES) || responseCode == ERRORCODE) { // we've got a redirect or error
      Logger.log("Redirect on: " + finalUrl + " --> " + responseCode );
      sheet.appendRow([ad.getCampaign().getName(), ad.getAdGroup().getName(), ad.getId(), "", "", finalUrl, responseCode, ad.getStatsFor("LAST_30_DAYS").getClicks()]);
    }
  } 

  var mobileFinalUrl = ad.urls().getMobileFinalUrl();
  if (mobileFinalUrl != null) {
    responseCode = getResponseCode(mobileFinalUrl);
    if (isInArray(responseCode, REDIRECT_CODES) || responseCode == ERRORCODE) { // we've got a redirect or error
      Logger.log("Redirect on: " + mobileFinalUrl + " --> " + responseCode);
      totalNumberOfRedirects++;
      sheet.appendRow([ad.getCampaign().getName(), ad.getAdGroup().getName(), ad.getId(), "", "", mobileFinalUrl, responseCode, ad.getStatsFor("LAST_30_DAYS").getClicks()]);
    }
  }   
}


function checkKeywordUrls(keyword) {

  Logger.log("Checking keyword: "+keyword.getText());

  var responseCode;
   
  var finalUrl = keyword.urls().getFinalUrl();
  if (finalUrl != null) {
    responseCode = getResponseCode(finalUrl);
    if (isInArray(responseCode, REDIRECT_CODES) || responseCode == ERRORCODE) { // we've got a redirect or error
      Logger.log("Redirect on: " + finalUrl + " --> " + responseCode );
      totalNumberOfRedirects++;
      sheet.appendRow([keyword.getCampaign().getName(), keyword.getAdGroup().getName(), "", " "+keyword.getText(), "", finalUrl, responseCode, keyword.getStatsFor("LAST_30_DAYS").getClicks()]);
    }
  } 

  var mobileFinalUrl = keyword.urls().getMobileFinalUrl();
  if (mobileFinalUrl != null) {
    responseCode = getResponseCode(mobileFinalUrl);
    if (isInArray(responseCode, REDIRECT_CODES) || responseCode == ERRORCODE) { // we've got a redirect or error
      Logger.log("Redirect on: " + mobileFinalUrl + " --> " + responseCode);
      totalNumberOfRedirects++;
      sheet.appendRow([keyword.getCampaign().getName(), keyword.getAdGroup().getName(), "", " "+keyword.getText(), "", mobileFinalUrl, responseCode, keyword.getStatsFor("LAST_30_DAYS").getClicks()]);
    }
  }   
}


function checkSitelinkUrls(adgroup) {

  Logger.log("Checking sitelinks for ad group: "+adgroup.getName());

  var responseCode;

  var adGroupSitelinkIterator = adgroup.extensions().sitelinks().get();
  if (adGroupSitelinkIterator.totalNumEntities() < 1) { // no sitelinks at ad group level, so let's check the campaign level
    adGroupSitelinkIterator = adgroup.getCampaign().extensions().sitelinks().get();
  }
  
  /* NO SOLUTION FOR ACCOUNT LEVEL YET  
  if (adGroupSitelinkIterator.totalNumEntities() < 1) { // no sitelinks at ad group level and no sitelinks at campaign level, so let's check the account level
    adGroupSitelinkIterator = AdWordsApp.currentAccount().extensions().sitelinks().get();
  }
*/
  
  while (adGroupSitelinkIterator.hasNext()) {
  
    var sitelink = adGroupSitelinkIterator.next();
    Logger.log("Checking sitelink: "+sitelink.getLinkText());
    
    var finalUrl = sitelink.urls().getFinalUrl();
    if (finalUrl != null) {
      responseCode = getResponseCode(finalUrl);
      if (isInArray(responseCode, REDIRECT_CODES) || responseCode == ERRORCODE) { // we've got a redirect or error
        Logger.log("Redirect on: " + finalUrl + " --> " + responseCode );
        totalNumberOfRedirects++;
        sheet.appendRow([adgroup.getCampaign().getName(), adgroup.getName(), "","", sitelink.getLinkText(), finalUrl, responseCode, "UNKNOWN"]);
      }
    }  

    var mobileFinalUrl = sitelink.urls().getMobileFinalUrl();
    if (mobileFinalUrl != null) {
      responseCode = getResponseCode(mobileFinalUrl);
      if (isInArray(responseCode, REDIRECT_CODES) || responseCode == ERRORCODE) { // we've got a redirect or error
        Logger.log("Redirect on: " + mobileFinalUrl + " --> " + responseCode );
        totalNumberOfRedirects++;
        sheet.appendRow([adgroup.getCampaign().getName(), adgroup.getName(), "","", sitelink.getLinkText(), mobileFinalUrl, responseCode, "UNKNOWN"]);
      }
    }  
  }
}



function getResponseCode(url) {
  var responseObject = findObjectInArray(url, 'url', urlResponseCodes);
  if (responseObject != false) { // url response already tested
    //Logger.log("URL already tested: " + url + " --> " + responseObject.responsecode);
    return responseObject.responsecode;  
  } else {
    try {
      var response;
      Logger.log("Testing url: "+url);
      response = UrlFetchApp.fetch(url, HTTP_OPTIONS).getResponseCode();
      Logger.log("URL response code: " + response);
      urlResponseCodes.push({url : url, responsecode : response});
      return response;
    } catch(e) {
      Logger.log(" #### Error Testing url: "+url+" ERROR: "+e);
      return ERRORCODE;
    }
  }
} 

function isInArray(value, array) {
  return array.indexOf(value) > -1;
}

function findObjectInArray(value, prop, array){
  for (var i=0; i < array.length; i++) {
      if (array[i][prop] === value) {
          return array[i];
      }
  }
  return false;
}


 

I simply need to give you a shout out now – have followed you for years, and you are simply the king! Amazing content and the scripts are another level. Making my PPC life easier, so really appreciate your content!

Ulrik Haack Pedersen, COO & Head of Digital Marketing - Hojbjerg, Denmark

Nils Rooijmans
2023-08-02T07:22:06+00:00

Ulrik Haack Pedersen, COO & Head of Digital Marketing - Hojbjerg, Denmark

I simply need to give you a shout out now – have followed you for years, and you are simply the king! Amazing content and the scripts are another level. Making my PPC life easier, so really appreciate your content!
0
0
Nils Rooijmans

Join thousands of PPC geeks who already have access:

If the button above isn’t working for you, you can sign up here to get access.