Google Ads Script – Change History Alerts

Be in the know when someone, or something makes changes in your Google Ads account.

Change happens.

Some changes are good. Other changes…not so much.

Occasionally, some of the most terrible changes pop up in your change history.

And the worst thing…they do so without you noticing!

Remember that last client of yours? That stubborn, overly self-confident ego-driven maniac?
He decided he knows best.
Made some “minor” changes to your carefully optimized account.
All by himself.
Changing ad copy, reducing test budgets, and adding non-brand keywords to your BRAND campaign…the horror.

Or maybe that overly ambitious intern that joined your team last week…Negating keywords all over the place!

And let’s not forget the worst of the worse; your colleague accidentally hitting the ‘Apply’ button next to one of Google’s Recommendations.

Oh dear.

Here’s the thing:

It is your job to manage and optmize your account.
Therfore you want be in the know when someone else makes any unanticipated change.
And you want to know immediately!

Thankfully, there’s a script for that!

Today I am sharing a stripped-down version of one of my favorite scripts in my private stack:


Monitoring the change history is, to say the least, a tedious repetitive burden. Not one PPC Pro I know does this on a regular basis.
It will result in Obsessive-Compulsive Disorder if you don’t automate the process.

Now imagine the comfort of being 100% sure that you will be notified immediately when anybody or anything outside of your team makes a change.
After the alert, you can simply jump in and check if the change is in line with your strategy. Or if it will actually hurt your account so you can repair the damage.

That’s exactly what my latest script does.

You can configure a list of users that are anticipated to make changes. This way your sheet and inbox won’t be flooded with alerts for changes that don’t need your review.

The output will look something like this:

(click image to enlarge)

google ads script - change history alerts

 

 

➥  ACTION: Schedule this script to run  daily.

(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 18)
    4. Add your email address to the script (line 19)
    5. Add the users for which you do not want to receive alerts (Line 21)
      NOTE: If you do not want to receive alerts when Google Ads’ Automated Rules make changes to your account -> simply add the user ‘Bulk Actions’ to the IGNORE_USERS array 
    6. Preview
    7. Schedule to run daily

I’m using this script in many of the accounts I manage

I'm using this script in many of the accounts I manage for a while now. Thank you so much for sharing it!

Stefanie Drucker, PPC Specialist - Gols, Austria

ᴎils rooijmans
2024-06-02T12:40:09+00:00

Stefanie Drucker, PPC Specialist - Gols, Austria

I'm using this script in many of the accounts I manage for a while now. Thank you so much for sharing it!
0
ᴎils rooijmans

Always the first script I implement as soon as I take over an account

Always the first script I implement as soon as I take over an account. Thank you for this masterpiece

Wolkan Yagar, Google Ads Lead Gen Manager - Vienna, Austria

ᴎils rooijmans
2024-06-02T12:45:04+00:00

Wolkan Yagar, Google Ads Lead Gen Manager - Vienna, Austria

Always the first script I implement as soon as I take over an account. Thank you for this masterpiece
0
ᴎils rooijmans

The script even flagged automated changes

I just implemented the script and it works like a charm! The script even flagged automated changes coming from “Internal Tool” with an user “low activity system bulk change”. Those are the changes that Google announced in the beginning of the year for low activity campaigns and adgroups. Thanks a ton and have a great week.

Cézar Keller Colturato, Online Advertising Manager at Ingersoll Rand - Suisse

ᴎils rooijmans
2024-10-05T10:18:10+00:00

Cézar Keller Colturato, Online Advertising Manager at Ingersoll Rand - Suisse

I just implemented the script and it works like a charm! The script even flagged automated changes coming from “Internal Tool” with an user “low activity system bulk change”. Those are the changes that Google announced in the beginning of the year for low activity campaigns and adgroups. Thanks a ton and have a great week.
0
ᴎils rooijmans

 

/**
*
* Change History Alerts - stripped down version
*
* The script checks all the entries in the Google Ads change history of your account, 
* and if there is a change by a user outside of your list of 'recognized' users, you will get an alert via email.
* The alert mail conatins the number of changes as well as a link to the Google Sheet that lists all changes by unrecognized users.
*
* Version 1.1
*
* @author: Nils Rooijmans
*
* For the more advanced version that runs on both single and manager MCC accounts and allows for Bulk upload and API changes exlusions contact nils@nilsrooijmans.com 
*/
 
// CHANGE SETTINGS HERE

var SPREADSHEET_URL = ""; //insert a new blank spreadsheet url between the double quotes 
var EMAIL_ADDRESSES = ""; //alert email adresses, insert your email adresses between the double quotes. You can add multiple email addresses between the double quotes, just separate them via a comma, ie: "john@doe.com, jane@doe.com"

var IGNORE_USERS = [
      'nils@watercoolertopics.com'
    ];
// NOTE: if you want to add multiple users, add them between the square brackets, one per line, with a comma seperating the lines. Ie:     
//var IGNORE_USERS = [
//      'john@doe.com',
//      'jane@doe.com',
//      'jill@johns.com'
//    ];

var SEND_EMAIL = true;
var EMAIL_SUBJECT = "[GAds Script] - WARNING - Change by person outside of organisation";
var EMAIL_BODY = 
    "\n"+
    "***\n"+
    "\n"+
    "This script checks changes in the 'Change history' :\n"+
    "\n"+
    "For all changes during "+PERIOD+" \n"+
    "		check if there is a change being made by users other than "+IGNORE_USERS+" \n"+
    "		if so, alerts are logged in Google Sheet: "+SPREADSHEET_URL+" \n"+
    "\n"+
    "If there is an alert an email is sent to:\n"+ EMAIL_ADDRESSES +"\n";

var PERIOD = "LAST_7_DAYS";


function main() { 

  Logger.log("Processing account: "+AdsApp.currentAccount().getName());
  
  var changeAlerts = getChangeAlerts();

  if (changeAlerts.length > 0 ) {
    reportResults(changeAlerts);
    sendEmail(changeAlerts.length);
  } else {
  Logger.log("Could not find any changes in change history that were made by users other than "+IGNORE_USERS+" \n");
  }
}


// look into change history and return changes to add to alert report
function getChangeAlerts() {
  
  var accountName = AdsApp.currentAccount().getName();
  
  var changes = [];
    
  var query = "SELECT " + 
              "campaign.name, " +
              "ad_group.name, " +
              "change_event.change_date_time, " +
              "change_event.change_resource_type, " +
              "change_event.changed_fields, " +
              "change_event.client_type, " +
              "change_event.feed, " +
              "change_event.feed_item, " +
              "change_event.new_resource, " +
              "change_event.old_resource, " +
              "change_event.resource_change_operation, " +
              "change_event.resource_name, " +
              "change_event.user_email " +
              "FROM change_event " +
              "WHERE change_event.change_date_time DURING "+PERIOD+" " + 
              "AND change_event.user_email NOT IN ('"+IGNORE_USERS.join("', '")+"') "+
              "ORDER BY change_event.change_date_time DESC "+ 
              "LIMIT 9999 "; // max of 10k changes reported per request
              
  Logger.log("query: " + query);
  
  try {
    var result = AdsApp.search(query);
  } catch (e) {
    alert("Issue retrieving results from search API: "+e);
  } 
  
  while (result.hasNext()) {
    var row = result.next();
 
    var campaignName = "";
    var adGroupName = "";
    
    // hack to prevent undefined variable from stopping script execution
    try {
      campaignName = row.campaign.name;
      adGroupName = row.adGroup.name;
    } catch(e) {
    }
    
    try {    
      var change = [
        row.changeEvent.changeDateTime,
        accountName,
        row.changeEvent.userEmail,
        row.changeEvent.clientType,
        campaignName,
        adGroupName,
        row.changeEvent.changeResourceType,
        row.changeEvent.changedFields,
        row.changeEvent.feed,
        row.changeEvent.feedItem,
        row.changeEvent.newResource,
        row.changeEvent.oldResource,
        row.changeEvent.resourceChangeOperation
      ];
      
      changes.push(change);
      
    } catch(e) {
      Logger.log("Issue with parsing results from search API: "+e);
    }

  }
 
  return changes;
}


function addHeaderToOutputSheet(sheet) {

  try {
    var headerSheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1HnHwdCTeHGbedV-2gwed36U9YfUB9Bd45NFCFV5R4q4/").getSheetByName('header_sheet');
  } catch(e) {
    console.log(`### There was an issue opening the header sheet. Please download the latest version of this script at https://nilsrooijmans.com\n${e}`);
    throw `### There was an issue opening the header sheet. Please download the latest version of this script at https://nilsrooijmans.com\n${e}`;
  }
  
  var headerRange = headerSheet.getRange(1, 1, 2, headerSheet.getLastColumn());
  var headerData = headerRange.getValues();
  
  console.log("Adding header to the output sheet"); 

  var range=sheet.getRange(1,1,2,headerData[1].length);
  range.clear();
  range.clearFormat();
  range.setValues(headerData)
  range.setFontWeight("bold");
  range = sheet.getRange(1,1,1,headerData[1].length);
  range.setFontColor('#007BFF')
  sheet.setFrozenRows(2);
  
}


function reportResults(changes) {
  
  var sheet = prepareOutputSheet();
  
  addOutputToSheet(changes, sheet);  
}


function prepareOutputSheet() {
  
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  if (!spreadsheet) {
    alert("Cannot open new reporting spreadsheet") ;
    return ;
  }

  var sheet = spreadsheet.getActiveSheet();
  if (!sheet) {
    alert("Cannot open new reporting sheet") ;
    return ;
  }  
  
  var numberOfRows=sheet.getLastRow() ;

  // set width of columns
  sheet.setColumnWidth(1, 150);
  sheet.setColumnWidth(2, 200);
  sheet.setColumnWidth(3, 300);
  sheet.setColumnWidth(4, 300);
  sheet.setColumnWidth(5, 300);
  sheet.setColumnWidth(6, 300);
  sheet.setColumnWidth(7, 100);
  sheet.setColumnWidth(8, 100);  
  sheet.setColumnWidth(9, 100);
  sheet.setColumnWidth(10, 100);
  sheet.setColumnWidth(11, 100);    
  sheet.setColumnWidth(12, 100);
  sheet.setColumnWidth(13, 100);
  sheet.setColumnWidth(14, 100);  
  
  addHeaderToOutputSheet(sheet);
  
  return sheet;
}


function addOutputToSheet(output, sheet) {
  
  sheet.insertRowsBefore(3, output.length); // add empty rows below header row

  var startRow = 3;
  
  var range=sheet.getRange(startRow, 1, output.length, output[0].length) ;
  range.setValues(output) ; 

  Logger.log("\nNumber of rows added to output sheet: "+output.length+"\n\n");
  
}



function sendEmail(numberOfalerts) {

  var accountName = AdsApp.currentAccount().getName();
  
  if (SEND_EMAIL) {
    // send the email
    var emailBody = 
        "Number of changes: " + numberOfalerts + "\n" + 
        "See details: "+ SPREADSHEET_URL+ "\n" + EMAIL_BODY;

    MailApp.sendEmail(EMAIL_ADDRESSES, EMAIL_SUBJECT+" | "+accountName, emailBody);
    Logger.log("Sending alert mail");
  }  
}

function alert(string) {
  Logger.log("### "+string);
}

 

Love this script!

Thanks Nils!! I love this script and find it very useful

Javier Ruíz-Calderón, PPC Manager at Selecta - Madrid, Spain

ᴎils rooijmans
2024-06-02T12:21:41+00:00

Javier Ruíz-Calderón, PPC Manager at Selecta - Madrid, Spain

Thanks Nils!! I love this script and find it very useful
0
ᴎils rooijmans

All the scripts you publish save my life

"All the scripts you publish save my life, SO MANY THANKS , great job!!!!!!"

Marta Sabaté Méndez, PPC/SEM Manager & Digital Analyst - Barcelona, Spain

ᴎils rooijmans
2023-05-04T07:40:54+00:00

Marta Sabaté Méndez, PPC/SEM Manager & Digital Analyst - Barcelona, Spain

"All the scripts you publish save my life, SO MANY THANKS , great job!!!!!!"
0
ᴎils 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.