RESOURCES

Google Script Enhancements

Google offers several free AdWords Scripts, but sometimes scripts need to be modified to meet specific account needs.  Below are a few freely offered Google AdWords Scripts along with our changes.  Enjoy!
Check out Google’s Unmodified Free Scripts Here »

Adding Conversions to The Keyword Performance Report Tutorial Download Download_button
  • What it Does:This script breaks down all keywords in an account by average position and quality score. The metrics are summed across these segments, letting you see how your performance varies across position and quality score.

    What Changed:This script takes conversions and conversion rate and adds them to the report. This allows you to see how your conversions and conversion rate vary by position and quality score. By optimizing for quality score, are you getting a worthwhile return? Similarly, the higher positions get many more clicks, are these clicks actually converting at a reasonable rate?

// SpreadSheet Url. Starts with https://docs.google.com...
var SPREADSHEET_URL = 'spreadsheet URL';

/**
* This script computes a keyword performance report
* and outputs it to a Google spreadsheet
*/
function main() {
 var spreadsheet = getSpreadsheet(SPREADSHEET_URL);
 var sheet = spreadsheet.getSheetByName('Sheet1');
 outputQualityScoreData(sheet);
 outputPositionData(sheet);
 Logger.log('Keyword performance report - ' + SPREADSHEET_URL);
}

/**
* Retrieves the spreadsheet identified by the URL.
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @return {SpreadSheet} The spreadsheet.
*/
function getSpreadsheet(spreadsheetUrl) {
 var matches = new RegExp('key=([^&#]*)').exec(spreadsheetUrl);
 if (!matches || !matches[1]) {
   throw 'Invalid spreadsheet URL: ' + spreadsheetUrl;
 }
 var spreadsheetId = matches[1];
 return SpreadsheetApp.openById(spreadsheetId);
}

/**
* Outputs Quality score related data to the spreadsheet
* @param {Sheet} sheet The sheet to output to.
*/
function outputQualityScoreData(sheet) {
 // Output header row
 var header = [
   'Quality Score',
   'Num Keywords',
   'Impressions',
   'Clicks',
   'CTR (%)',
   'Cost',
   'Conversions',
   'Conversion Rate'
 ];
 sheet.getRange(1, 1, 1, 8).setValues([header]);

 // Initialize
 var qualityScoreMap = [];
 for (i = 1; i <= 10; i++) {
   qualityScoreMap[i] = {
     numKeywords: 0,
     totalImpressions: 0,
     totalClicks: 0,
     totalCost: 0.0,
     totalConversions: 0,
     conversionRate: 0,
   };
 }

 // Compute data
 var keywordIterator = AdWordsApp.keywords()
     .forDateRange('LAST_WEEK')
     .withCondition('Impressions > 0')
     .get();
 while (keywordIterator.hasNext()) {
   var keyword = keywordIterator.next();
   var stats = keyword.getStatsFor('LAST_WEEK');
   var data = qualityScoreMap[keyword.getQualityScore()];
   if (data) {
     data.numKeywords++;
     data.totalImpressions += stats.getImpressions();
     data.totalClicks += stats.getClicks();
     data.totalCost += stats.getCost();
     data.totalConversions += stats.getConversions();
     data.conversionRate += stats.getConversionRate();
   }
 }

 // Output data to spreadsheet
 var rows = [];
 for (var key in qualityScoreMap) {
   var ctr = 0;
   var cost = 0.0;
   var conversionRate= 0;
   if (qualityScoreMap[key].numKeywords > 0) {
     ctr = (qualityScoreMap[key].totalClicks /
       qualityScoreMap[key].totalImpressions) * 100;
     conversionRate= (qualityScoreMap[key].totalConversions / qualityScoreMap[key].totalClicks) * 100;
   }
   var row = [
     key,
     qualityScoreMap[key].numKeywords,
     qualityScoreMap[key].totalImpressions,
     qualityScoreMap[key].totalClicks,
     ctr.toFixed(2),
     qualityScoreMap[key].totalCost,
     qualityScoreMap[key].totalConversions,
     conversionRate.toFixed(2)
     
   ];
   rows.push(row);
 }
 sheet.getRange(2, 1, rows.length, 8).setValues(rows);
}

/**
* Outputs average position related data to the spreadsheet.
* @param {Sheet} sheet The sheet to output to.
*/
function outputPositionData(sheet) {
 // Output header row
 headerRow = [];
 var header = [
   'Avg Position',
   'Num Keywords',
   'Impressions',
   'Clicks',
   'CTR (%)',
   'Cost',
   'Conversions',
   'Conversion Rate'
 ];
 headerRow.push(header);
 sheet.getRange(14, 1, 1, 8).setValues(headerRow);

 // Initialize
 var positionMap = [];
 for (i = 1; i <= 12; i++) {
   positionMap[i] = {
     numKeywords: 0,
     totalImpressions: 0,
     totalClicks: 0,
     totalCost: 0.0,
     totalConversions: 0,
     conversionRate: 0

   };
 }

 // Compute data
 var keywordIterator = AdWordsApp.keywords()
     .forDateRange('LAST_WEEK')
     .withCondition('Impressions > 0')
     .get();
 while (keywordIterator.hasNext()) {
   var keyword = keywordIterator.next();
   var stats = keyword.getStatsFor('LAST_WEEK');
   
   if (stats.getAveragePosition() <= 11) {
     var data = positionMap[Math.ceil(stats.getAveragePosition())];
   } else {
     // All positions greater than 11
     var data = positionMap[12];
   }
   data.numKeywords++;
   data.totalImpressions += stats.getImpressions();
   data.totalClicks += stats.getClicks();
   data.totalCost += stats.getCost();
   data.totalConversions += stats.getConversions();
   data.conversionRate += stats.getConversionRate();

 }

// Output data to spreadsheet
var rows = [];
for (var key in positionMap) {
  var ctr = 0;
  var cost = 0.0;
  var conversionRate = 0;
  if (positionMap[key].numKeywords > 0) {
    ctr = (positionMap[key].totalClicks /
      positionMap[key].totalImpressions) * 100;
    conversionRate = (positionMap[key].totalConversions / positionMap[key].totalClicks) * 100;

  }
  var row = [
    key <= 11 ? key - 1 + ' to ' + key : '>11',
    positionMap[key].numKeywords,
    positionMap[key].totalImpressions,
    positionMap[key].totalClicks,
    ctr.toFixed(2),
    positionMap[key].totalCost,
    positionMap[key].totalConversions,
    conversionRate.toFixed(2)
  ];
  rows.push(row);
}
sheet.getRange(15, 1, rows.length, 8).setValues(rows);
}

Pause Ad Groups with Declining CTR Tutorial Download Download_button
  • What it does: This script takes week over week performance from the last three weeks. If the ad group is below a certain CTR threshold and continues to decline, the script pauses those ad groups.

    What Changed:This is useful for cleaning out the non-valuable keywords, but what about other metrics? For this script we took it back to the number of conversions.. Rather than only looking at traffic, is this traffic actually driving leads or revenue for the account.

function main() {

 AdWordsApp.createLabel("PausedCTR", "ad group paused due to declining CTR", "red");
 AdWordsApp.createLabel("PausedCPA", "ad groups paused due to declining Conversions", "yellow");
 //set your max CPA value. You don't want to pause an ad group with a rising CPA unless that CPA goes over your limit.
 var cpaMax = 0;

 var adGroupsIterator = AdWordsApp.adGroups()
   .withCondition("Clicks > 25")
   .forDateRange("LAST_14_DAYS")
   .get();

 var today = getDateInThePast(0);
 var oneWeekAgo = getDateInThePast(7);
 var twoWeeksAgo = getDateInThePast(14);
 var threeWeeksAgo = getDateInThePast(21);

 while (adGroupsIterator.hasNext()) {
   var adGroup = adGroupsIterator.next();
   // Let's look at the trend of the ad group's CTR.
   var ctr1 = adGroup.getStatsFor(threeWeeksAgo, twoWeeksAgo).getCtr();
   var ctr2 = adGroup.getStatsFor(twoWeeksAgo, oneWeekAgo).getCtr();
   var ctr3 = adGroup.getStatsFor(oneWeekAgo, today).getCtr();
   var cost1 = adGroup.getStatsFor(threeWeeksAgo, twoWeeksAgo).getCost();
   var cost2 = adGroup.getStatsFor(twoWeeksAgo, oneWeekAgo).getCost();
   var cost3 = adGroup.getStatsFor(oneWeekAgo, today).getCost();
   var conversions1 = adGroup.getStatsFor(threeWeeksAgo, twoWeeksAgo).getCost();
   var conversions2 = adGroup.getStatsFor(twoWeeksAgo, oneWeekAgo).getCost();
   var conversions3 = adGroup.getStatsFor(oneWeekAgo, today).getCost();
   var cpa1 = cpa(cost1,conversions1);
   var cpa2 = cpa(cost2, conversions2);
   var cpa3 = cpa(cost3, conversions3);

   // Week over week, the ad group is degrading - pause it!
   if (ctr1 > ctr2 && ctr2 > ctr3) {
     adGroup.pause();
     adGroup.applyLabel('PausedCTR');
    }

   if ((cpa3 > cpaMax) && (cpa1 < cpa2 && cpa2 < cpa3)) {
     adGroup.pause();
     adGroup.applyLabel('PausedCPA');
   }
 }

}

// Returns YYYYMMDD-formatted date.
function getDateInThePast(numDays) {
 var today = new Date();
 today.setDate(today.getDate() - numDays);
 return Utilities.formatDate(today, "PST", "yyyyMMdd");

}

//calculates cost per conversion
function cpa(cost, conversions){
 var costConv = cost/conversions;
 return costConv.toFixed(2);
}

Create a New Text Ad Tutorial Download Download_button
  • What it does: Automatically creates a text ad in the selected ad group. This is another quick solution that can speed up your workflow, allowing you to specify the contents of the ad and create them in bulk.

    What Changed: We’ve added an iterator that selects a group of ad groups and applies the ad to each of those ad groups. Rather than selecting ad groups one by one, a single click applies the ad to each ad group. This can be done at the campaign level. Alternatively, if you are feeling adventurous you can use labels, similar to the other scripts, to control the additions.

function main() {
 // Replace with a name of an campaign that exists in the account
 var campaignName = "Campaign";

 var adGroupsIterator = AdWordsApp.Campaigns.withCondition("Name = '" + campaignName + "'").adGroups()
   .withCondition("Name = '" + adGroupName + "'")
   .get();

 if (!adGroupsIterator.hasNext()) {
   Logger.log("Ad group '" + adGroupName + "' not found.");
 } else {
   var adGroup = adGroupsIterator.next();

   
adGroup.createTextAd("Ad headline",
     "First Line of description",
     "Second line of description",
     "www.yoursite.com", // display url
     "http://www.yoursite.com" // destination url
   );
 }
}

Modified Campaign Copier Tutorial Download Download_button
  • What it does:This script lets you select a campaign in your account and copy its contents, keywords, ad groups, negatives, etc.

    What Changed: : This script takes advantage of labels to define what needs to be copied. This script will quickly copy an a campaign with all keywords, negatives, ads, etc. intact.

    By tagging the ad groups we want to move, you can quickly move the parts you need, and only those, with minimal effort. The script also removes the labels after copying to contents, allow you to restart from where it left off in the case you hit the time limit.

var ARGUMENTS = {
 sourceCampaign: "Campaign Name",
 targetCampaign: "New Campaign Name",
};

function main() {
 createLabel();
 verifyArguments();
 var sourceCampaign = AdWordsApp.campaigns()
   .withCondition("Name='" + escape(ARGUMENTS.sourceCampaign) + "'")
   .get().next();
 var targetCampaign = AdWordsApp.campaigns()
   .withCondition("Name='" + escape(ARGUMENTS.targetCampaign) + "'")
   .get().next();

 copyAdGroups(sourceCampaign, targetCampaign);
 copyNegativeKeywords(sourceCampaign, targetCampaign);
}

function copyAdGroups(sourceCampaign, targetCampaign) {
 var sourceAdGroups = sourceCampaign.adGroups().withCondition("LabelNames CONTAINS_ANY ['Copy']").get();

 while (sourceAdGroups.hasNext()) {
   var sourceAdGroup = sourceAdGroups.next();
 var status = "ENABLED";
 var targetAdGroup = targetCampaign.newAdGroupBuilder()
   .withName(sourceAdGroup.getName() + "")
  &nsp;.withStatus(status)
   .withKeywordMaxCpc(sourceAdGroup.getKeywordMaxCpc())
   .create();
 copyKeywords(sourceAdGroup, targetAdGroup);
 copyTextAds(sourceAdGroup, targetAdGroup);
 copyNegativeKeywords(sourceAdGroup, targetAdGroup);
 sourceAdGroup.removeLabel('Copy');
 sourceAdGroup.applyLabel('Copied');
 }
}

function createAdGroup(sourceAdGroup) {
 var targetCampaign = AdWordsApp.campaigns()
   .withCondition("CampaignName = '" + escape(ARGUMENTS.targetCampaign) + "'")
   .get().next();
 var status = ARGUMENTS.createPaused ? "PAUSED" : "ENABLED";
 return targetCampaign.newAdGroupBuilder()
   .withName(ARGUMENTS.targetAdGroup)
   .withKeywordMaxCpc(sourceAdGroup.getKeywordMaxCpc())
   .withStatus(status)
   .create();
}

function copyKeywords(sourceAdGroup, targetAdGroup) {
 var keywordsIterator = sourceAdGroup.keywords().get();

 while (keywordsIterator.hasNext()) {
   var sourceKeyword = keywordsIterator.next();
   if (sourceKeyword.getMaxCpc() == sourceAdGroup.getKeywordMaxCpc()) {
     targetAdGroup.createKeyword(sourceKeyword.getText(), null, sourceKeyword.getDestinationUrl());
   } else {
     targetAdGroup.createKeyword(sourceKeyword.getText(), sourceKeyword.getMaxCpc(), sourceKeyword.getDestinationUrl());
   }
 }
}

function copyTextAds(sourceAdGroup, targetAdGroup) {
   var adsIterator = sourceAdGroup.ads().withCondition("Type = 'TEXT_AD'").get();

   while (adsIterator.hasNext()) {
   var sourceAd = adsIterator.next();
   if (sourceAd.isMobilePreferred()) {
     targetAdGroup.createTextAd(
       sourceAd.getHeadline(),
       sourceAd.getDescription1(),
       sourceAd.getDescription2(),
       sourceAd.getDisplayUrl(),
       sourceAd.getDestinationUrl(),
       {isMobilePreferred: true});
   } else {
     targetAdGroup.createTextAd(
       sourceAd.getHeadline(),
       sourceAd.getDescription1(),
       sourceAd.getDescription2(),
       sourceAd.getDisplayUrl(),
       sourceAd.getDestinationUrl());
   }
 }
}

function copyNegativeKeywords(source, target) {
 var negativesIterator = source.negativeKeywords().get();

 while (negativesIterator.hasNext()) {
   var sourceNegative = negativesIterator.next();
   target.createNegativeKeyword(sourceNegative.getText());
 }
}

function verifyArguments() {
 var errors = [];
 if (verifyRequiredString("sourceCampaign", errors)) {
   if (!AdWordsApp.campaigns()
     .withCondition("Name = '" + escape(ARGUMENTS.sourceCampaign) + "'")
     .get().hasNext()) {
     errors.push("Source campaign " + ARGUMENTS.sourceCampaign + " doesn't exist");
   }
 }
 if (verifyRequiredString("targetCampaign", errors)) {
   if (!AdWordsApp.campaigns().withCondition("Name = '" + escape(ARGUMENTS.targetCampaign) + "'").get().hasNext()) {
     errors.push("Target campaign " + ARGUMENTS.targetCampaign + " doesn't exist");
   }
 }
 if (errors.length > 0) {
   Logger.log("" + errors.join("
"));
   throw "Cannot copy the ad group: ARGUMENTS are malformed. Please fix the arguments and try again.";
 }
}

function verifyRequiredString(str, errors) {
 if (!ARGUMENTS[str] || ARGUMENTS[str].length == 0) {
   errors.push("ARGUMENTS." + str + " must be specified");
   return false;
 }
 return true;
}

function escape(name) {
 return name.replace(/'/g,"\'");
}

function createLabel(){
 AdWordsApp.createLabel('Copy', 'Select Ad Groups to Copy', 'green');
 AdWordsApp.createLabel('Copied', 'Marks all ad groups that have been copied', 'blue');
}

If you like our modifications to these scripts, check
out our complete Scripts Library here!

WHITEPAPERS

Remarketingtoolkit

Remarketing Toolkit

Listen to presentations, read exclusive guides and download valuable tools for your remarketing campaigns

Enhancedctoolkit

Enhanced Campaigns Toolkit

As the switch to AdWords Enhanced Campaigns approaches, read everything you need to know about when, how, and why to switch your accounts.

Soaicltoolkit_comingsoon

Social Media Toolkit

Coming Soon

"Ad Guardian detects when your site goes down and automatically pauses your campaigns until it goes back up.  Brilliant.  Simply Brilliant."

Mvw-head

Matt Van Wagner

Grey_divider

“I love using PPCHero; it is a great tool that I show people just getting into PPC too.  Great Tips!”

Derek_oslter

Derek Ostler

Grey_divider

"Been reading for a few years back.  Great for beginners & veterans as it covers a range of topics."

Mark_kennedy_pic

Mark Kennedy

Grey_divider

"PPC Hero has been essential in the development of our PPC simulation game.  Nobody knows QS like you do!"

Louis_simbound

Simbound.com

Grey_divider