Have you ever checked your Google Adwords account, only to find the worst?

No matter how much time and effort you put into your AdWords campaigns, some things just don’t go as planned. Depending on how many other hats you’re wearing for the marketing team or business as a whole, you don’t have time to keep a constant eye on budgets, broad match keywords, quality scores, click-through-rates, budget pacing, etc. etc. etc.

Fortunately, Google AdWords Scripts can help you automate everyday tasks like pausing low-performing ads or alerting you when certain account conditions occur via Slack, email, or text. Before we share our 10 most useful scripts with you, let’s understand scripts a little better so you can use them with confidence in your AdWords account once you’re done reading!

In this post, we’ll cover:

What are Google AdWords Scripts?

According to Google, AdWords Scripts are built using “a JavaScript cloud scripting language that provides easy ways to automate tasks across Google products and third party services and build web applications.”

Basically, Google Scripts are pieces of code that can interact with your Google AdWords Account! If you’re like me and have never touched a line of code in your life, you are probably thinking about ending this article a little early…but here’s a secret:

You do not need to write a single line of code or have any coding experience to utilize Google Scripts in an effective manner.

There are a ton of incredible prewritten scripts, like ones that can adjust your bids based on the weather, or leverage Amazon to find high commercial intent keywords. These scripts can be easily applied directly to your AdWords account, which provides three major benefits:

Automating tedious tasks and making changes to a large number of AdWords campaigns or accounts. Setting safeguards and notifications on your ads that respond to an external trigger. Aggregating and analyzing different campaign metrics.

How to Access the AdWords Script Console

Before we dive into the list, we put together a quick tutorial on how to access Google Scripts. If you are familiar with the process, feel free to skip this section, or watch our video tutorial.

First, enter AdWords manager, and click on “Bulk operations”

Next, click on “Create and manage scripts”

Click on + SCRIPT

Here is the Scripts console, where you can copy and paste your code

When using scripts, make sure you click PREVIEW to make sure everything runs smoothly before saving, as well as Authorize now

Hitting “Run Script Now” will execute the script. However, you are going to want to run most of these scripts daily or even hourly. Rather than manually executing a script, we can tell Google to run the script at a set interval. Save the script, go back to bulk operations, click scripts, and next to the script you just created, click ‘create schedule’, and set the frequency. The script will automatically run and execute its task every period.

10 of the best AdWords Scripts you can run today!

1) Quality Score Tracker by Martin Roettgerding

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Quality Score Tracker v3.0.1 * Written by Martin Roettgerding. * © 2016 Martin Roettgerding, Bloofusion Germany GmbH. * www.ppc-epiphany.com/qstracker/latest */ function main(){ /* * The following preferences can be changed to customize this script. * Most of options can be set by using 1 for yes or 0 for no. * You don't have to change anything here. The script will do fine with the defaults. */ var config = { /* * Which of the following charts should be displayed on the dashboard? * The "per QS" charts are column charts. They show the current state compared to a previous one (see next option). * "Average" and "weighted" charts are line charts, showing changes over time. */ "chartsToDisplay" : { "Keywords per QS" : 0, "Average QS" : 0, "Keywords with Impressions per QS" : 1, "Average QS for Keywords with Impressions" : 0, "Impressions per QS" : 0, "Impression weighted QS" : 1, "Clicks per QS" : 0, "Click weighted QS" : 0, "Conversions per QS" : 0, "Conversion weighted QS" : 0, "Conversion value per QS" : 0, "Conversion value weighted QS" : 0, }, /* * Column charts can show a former date for comparison. Set the number of steps you want to go back for this. * Note that the date you're comparing this to will depend on how often you've run the script in the past. * Example: If the setting is 30 and you ran the script daily, your comparison will be with the values from 30 days before. If you ran it hourly, it will be with values from 30 hours before. * If you haven't run the script often enough, the comparison will go as far back as possible. * Put 0 to disable the comparison. */ "chartsCompareStepsBack" : 30, /* * When stats are taken into account (like impressions per QS, or impression weighted QS), this timeframe is used. * Note that this affects the values to be tracked and stored. Past values that are already stored won't be affected. * Use one of the following: TODAY, YESTERDAY, LAST_7_DAYS, THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK, LAST_14_DAYS, LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH */ "statsTimeframe" : "LAST_30_DAYS", /* * Whether to only look at stats from Google (e.g. for impression weighted QS). * Recommended. Quality Score itself only reflects data from Google, so weighting should only take Google into account and leave out search partners. * Note that this affects the values to be tracked and stored. Past values that are already stored won't be affected. */ "googleOnly" : 1, /* * Whether to only track active keywords. This means that the keyword, the adgroup, and the campaign have to be enabled. * Recommended. Otherwise inactive keywords with meaningless Quality Scores might skew your data. */ "activeKeywordsOnly" : 1, /* * Set to 1 if you want your dates (in charts, table headers, and file names) to contain hours and minutes as well. * Do this if you want to run the script hourly. */ "useHours" : 0, /* * Use this option to not keep track of individual keywords' Quality Scores and only save data to the dashboard file. * This makes sense if you have more than 400,000 keywords. Note that you don't have to change this: The script will notice on its own and log a message otherwise. */ "skipIndividualKeywords" : 0, /* * The name of the file where dashboard and summarized data are stored. */ "summaryFileName" : "Dashboard + Summary", /* * The base folder for all Quality Score Tracker files. */ "baseFolder" : "Quality Score Tracker/", /* * Whether to add a client folder in the base folder (resulting in a folder like "Quality Score Tracker/CLIENT_NAME (123-456-7890)/") * The folder's name is not important, as long as the Adwords client id remains in it. * This is useful if you want to track multiple accounts with this script. */ "useClientFolder" : 1, } trackQS(config); } function trackQS(config){ var version = "3.0"; if(config['useHours']) var dateString = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), "yyyy-MM-dd HH:mm"); else var dateString = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), "yyyy-MM-dd"); var folder = getOrCreateFolder(config['baseFolder']); if(config['useClientFolder']) folder = getOrCreateClientFolder(folder); // Find the latest report file in the folder. var maxFileNumber = 0; var reportFile; var summaryFile; var fileIterator = folder.getFiles(); while(fileIterator.hasNext()){ var file = fileIterator.next(); var matches = new RegExp(' #([0-9]+) ').exec(file.getName()); if(matches && parseInt(matches[1]) > maxFileNumber){ maxFileNumber = parseInt(matches[1]); reportFile = file; }else if(file.getName() == config['summaryFileName']) summaryFile = file; } // No report file found? Add a new one. if(maxFileNumber == 0){ reportFile = addReportFile(folder, "QS Report #1 (" + dateString + ")"); maxFileNumber = 1; } // No summary file found? Add a new one. if(!summaryFile) summaryFile = addSummaryFile(folder, config['summaryFileName']); Logger.log("All files are stored at"); Logger.log(folder.getUrl()); Logger.log("The dashboard is here:"); Logger.log(summaryFile.getUrl()); var spreadsheet = SpreadsheetApp.open(reportFile); var sheet = spreadsheet.getActiveSheet(); var lastRowNumber = sheet.getLastRow(); var sheetLastColumn = sheet.getLastColumn(); var idColumnValues = sheet.getRange(1, 4, lastRowNumber, 1).getValues(); var summarySpreadsheet = SpreadsheetApp.open(summaryFile); updateInfo(summarySpreadsheet, version); var sheetCharts = summarySpreadsheet.getSheetByName("Dashboard"); summarySpreadsheet.setActiveSheet(sheetCharts); summarySpreadsheet.moveActiveSheet(1); // Track an event in Google Analytics. trackInAnalytics(version); // Remember the line number for every keyword. var lineNumbers = {}; for(var i = 1; i < lastRowNumber; i++){ lineNumbers[idColumnValues[i][0]] = i; } // qsValues represents the new column that will go right next to the others. var qsValues = new Array(lastRowNumber); qsValues[0] = [ dateString ]; // Initialize everything with an empty string. var qsValuesLength = qsValues.length; for(var i = 1; i < qsValuesLength; i++) qsValues[i] = [""]; // In case new keywords are found, they'll be added as new rows below the rest (campaign, adgroup, keyword, id string). var newRows = []; // All aggregated data goes in this variable. var qsStats = { "Keywords" : {}, "Keywords with impressions" : {}, "Impressions" : {}, "Clicks" : {}, "Conversions" : {}, "Conversion value" : {} }; // Initialize the arrays so that everything can be added up later. Index 0 is for totals, 1-10 for Quality Scores. for(var key in qsStats){ for(var i = 0; i <= 10; i++){ qsStats[key][i] = 0; } } // Get the data from AdWords. var awql = "SELECT Id, Criteria, KeywordMatchType, CampaignId, CampaignName, AdGroupId, AdGroupName, QualityScore, Impressions, Clicks, Conversions, ConversionValue FROM KEYWORDS_PERFORMANCE_REPORT WHERE Id NOT_IN [3000000, 3000006] AND Status = 'ENABLED' AND AdGroupStatus = 'ENABLED' AND CampaignStatus = 'ENABLED'"; if(config['googleOnly']) awql += " AND AdNetworkType2 = 'SEARCH'"; if(config['activeKeywordsOnly']) awql += " AND CampaignStatus = 'ENABLED' AND AdGroupStatus = 'ENABLED' AND Status = 'ENABLED'"; awql += " DURING " + config['statsTimeframe']; var report = AdWordsApp.report(awql); var reportRows = report.rows(); // Go through the report and count Quality Scores. while(reportRows.hasNext()){ var row = reportRows.next(); // Save the aggregated data. qsStats['Keywords'][row['QualityScore']]++; if(row['Impressions'] > 0) qsStats['Keywords with impressions'][row['QualityScore']]++; qsStats['Impressions'][row['QualityScore']] += parseInt(row['Impressions']); qsStats['Clicks'][row['QualityScore']] += parseInt(row['Clicks']); qsStats['Conversions'][row['QualityScore']] += parseInt(row['Conversions']); qsStats['Conversion value'][row['QualityScore']] += parseInt(row['ConversionValue']); // Save the individual keyword's Quality Score. if(!config['skipIndividualKeywords']){ var id = row['CampaignId']+"_"+row['AdGroupId']+"_"+row['Id']; // Check if there is already a line for this keyword if(lineNumbers[id]) var line_number = lineNumbers[id]; else{ // There is no line for this keyword yet. Create a new one and add the line headers. line_number = qsValues.length; if(row['KeywordMatchType'] == "Exact") var keyword = '[' + row['Criteria'] + ']'; else if(row['KeywordMatchType'] == "Phrase") var keyword = '"' + row['Criteria'] + '"'; else var keyword = row['Criteria']; newRows.push([row['CampaignName'], row['AdGroupName'], keyword, id]); } qsValues[line_number] = [row['QualityScore']]; } } // Check if everything fits. if(!config['skipIndividualKeywords']){ // A spreadsheet can hold up to 2 million cells. Calculate if the new data will fit in with the rest. // With four rows needed for every keyword, plus one for every tracking run, this won't fit if there are more than 400,000 rows (header + 399,999 keywords). if(qsValues.length >= 400000){ Logger.log("There are too many keywords to be tracked (" + qsValues.length + "). This tool can only track up to 399,999 keywords."); Logger.log("A summary will be logged, but individual keyword quality scores cannot be stored."); skipIndividualKeywords = true; }else if((qsValues.length + 1) * (sheet.getLastColumn() + 1) > 2000000){ // This spreadsheet is full, a new one is needed. // Add new file. maxFileNumber++; reportFile = addReportFile(folder, "QS Report #" + maxFileNumber + " (" + dateString + ")"); var newSpreadsheet = SpreadsheetApp.open(reportFile); var newSheet = newSpreadsheet.getActiveSheet(); // Copy the first columns from the old sheet to the new one. newSheet.getRange(1, 1, lastRowNumber, 4).setValues(sheet.getRange(1, 1, lastRowNumber, 4).getValues()); // From now on, work with the new sheet and spreadsheet. spreadsheet = newSpreadsheet; sheet = newSheet; sheetLastColumn = 4; } } // Store the keyword data in the spreadsheet. if(!config['skipIndividualKeywords']){ // If there are new rows, add their line headers beneath the others. if(newRows.length > 0){ sheet.insertRowsAfter(lastRowNumber, newRows.length).getRange(lastRowNumber + 1, 1, newRows.length, 4).setValues(newRows); sheet.autoResizeColumn(1).autoResizeColumn(2).autoResizeColumn(3); } // Add a new column with the tracked data. sheet.insertColumnAfter(sheetLastColumn); sheet.getRange(1, sheetLastColumn + 1, qsValues.length, 1).setValues(qsValues); sheet.autoResizeColumn(sheetLastColumn + 1); // Change file name to reflect the new date. // Find out which dates are currently noted in the file's name. var matches = /\((.*?)( - (.*))?\)/.exec(reportFile.getName()); if(matches && matches[1]){ if(matches[2]){ // There's a start date and an end date. var startDate = matches[1]; var endDate = matches[3]; if(endDate != dateString){ var newFileName = reportFile.getName().replace(endDate, dateString); reportFile.setName(newFileName); } }else{ // There's just a start date. var startDate = matches[1]; if(startDate != dateString){ var newFileName = reportFile.getName().replace(startDate, startDate + " - " + dateString); reportFile.setName(newFileName); } } }else{ Logger.log("Could not recognize dates in file name " + reportFile.getName() +". File name remains unchanged."); } } // Now take care of the summary file. // Get the total numbers. for(var key in qsStats){ for(var i = 1; i <= 10; i++){ qsStats[key][0] += qsStats[key][i]; } } // Prepare a new column for the Percentages data sheet. var newValues = []; var newValuesNumberFormats = []; for(var key in qsStats){ newValues.push([dateString]); newValuesNumberFormats.push(["@STRING@"]); for(var i = 1; i <= 10; i++){ if(qsStats[key][0]) newValues.push([qsStats[key][i] / qsStats[key][0]]); else newValues.push([0]); newValuesNumberFormats.push(["0.00%"]); } newValues.push([qsStats[key][0]]); newValuesNumberFormats.push(["0.##"]); } var sheetPercentages = summarySpreadsheet.getSheetByName("Percentages"); var sheetAverages = summarySpreadsheet.getSheetByName("Averages"); var lastCol = sheetPercentages.getLastColumn() + 1; var lastRow = sheetAverages.getLastRow() + 1; // Add the data to the Percentages sheet. sheetPercentages.insertColumnAfter(lastCol - 1); sheetPercentages.getRange(1, lastCol, 72, 1).setNumberFormats(newValuesNumberFormats).setValues(newValues); sheetPercentages.autoResizeColumn(lastCol); // Add a new row with formulas to the Averages sheet. sheetAverages.appendRow([""]); sheetAverages.getRange(lastRow, 1, 1, 1).setValue(dateString); sheetAverages.getRange(lastRow, 2, 1, 6).setFormulasR1C1([[ "=SUMPRODUCT(Percentages!R2C1:R11C1; Percentages!R2C" + lastCol + ":R11C" + lastCol + ")", "=SUMPRODUCT(Percentages!R14C1:R23C1; Percentages!R14C" + lastCol + ":R23C" + lastCol + ")", "=SUMPRODUCT(Percentages!R26C1:R35C1; Percentages!R26C" + lastCol + ":R35C" + lastCol + ")", "=SUMPRODUCT(Percentages!R38C1:R47C1; Percentages!R38C" + lastCol + ":R47C" + lastCol + ")", "=SUMPRODUCT(Percentages!R50C1:R59C1; Percentages!R50C" + lastCol + ":R59C" + lastCol + ")", "=SUMPRODUCT(Percentages!R62C1:R71C1; Percentages!R62C" + lastCol + ":R71C" + lastCol + ")" ]]); // The properties for the charts. This is not meant to be reconfigured. var chartsProperties = { "Keywords per QS" : { "type" : "column", "vCol" : 2, }, "Average QS" : { "type" : "line", "vCol" : 2, }, "Keywords with Impressions per QS" : { "type" : "column", "vCol" : 3, }, "Average QS for Keywords with Impressions" : { "type" : "line", "vCol" : 3, }, "Impressions per QS" : { "type" : "column", "vCol" : 4, }, "Impression weighted QS" : { "type" : "line", "vCol" : 4, }, "Clicks per QS" : { "type" : "column", "vCol" : 5, }, "Click weighted QS" : { "type" : "line", "vCol" : 5, }, "Conversions per QS" : { "type" : "column", "vCol" : 6, }, "Conversion weighted QS" : { "type" : "line", "vCol" : 6, }, "Conversion value per QS" : { "type" : "column", "vCol" : 7, }, "Conversion value weighted QS" : { "type" : "line", "vCol" : 7, }, }; var row = 1; var col = 1; var summarySheets = { "dataH": sheetPercentages, "dataV": sheetAverages, "charts": sheetCharts, } // Add charts to the dashboard. for(var chartName in config['chartsToDisplay']){ // Skip all charts that are not set to be displayed. if(!config['chartsToDisplay'][chartName]) continue; addChartToDashboard(chartName, chartsProperties[chartName]['type'], summarySheets, row, col, lastRow, lastCol, chartsProperties[chartName]['vCol'], config['chartsCompareStepsBack']); // Add the "Average QS" cells. sheetCharts.setRowHeight(row, 60).setRowHeight(row + 1, 20).setRowHeight(row + 2, 270); sheetCharts.getRange(row, 2).setValue("Average QS").setFontWeight("bold").setFontSize(24).setBorder(true, true, false, true, null, null); sheetCharts.getRange(row + 2, 2).setFontWeight("bold").setFontSize(24).setNumberFormat("0.00").setBorder(false, true, false, true, null, null); sheetCharts.getRange(row + 1, 2, 2, 1).setFormulasR1C1( [ ["=LOWER(Averages!R1C" + chartsProperties[chartName]['vCol'] + ")"], ["=Averages!R" + lastRow + "C" + chartsProperties[chartName]['vCol']] ]).setBorder(false, true, true, true, null, null); sheetCharts.autoResizeColumn(2); row += 3; } } /* * Checks if there is a folder with the given name in the Google Drive root folder. If not, the folder is created. * The folderName can be in the form of a complete path with subfolders, like "QS Reports/123/whatever". * Returns the folder. */ function getOrCreateFolder(folderName){ return getOrCreateFolderFromArray(folderName.toString().split("/"), DriveApp.getRootFolder()); } /* * Does the actual work for getOrCreateFolder. Recursive function, based on an array of folder names (to handle paths with subfolders). */ function getOrCreateFolderFromArray(folderNameArray, currentFolder){ var folderName = ""; // Skip empty folders (multiple slashes or a slash at the end). do folderName = folderNameArray.shift(); while(folderName == "" && folderNameArray.length > 0); if(folderName == "") return currentFolder; // See if the folder is already there. var folderIterator = currentFolder.getFoldersByName(folderName); if(folderIterator.hasNext()){ var folder = folderIterator.next(); }else{ // Create folder. Logger.log("Creating folder '" + folderName + "'"); var folder = currentFolder.createFolder(folderName); } if(folderNameArray.length > 0) return getOrCreateFolderFromArray(folderNameArray, folder); return folder; } /* * Checks if there is a folder for the current client account in the base folder. If not, the folder is created. * Existing client folders are recognized by the client id in parentheses. This way, folders can be found again, even if an account has been renamed. */ function getOrCreateClientFolder(baseFolder){ var folderIterator = baseFolder.getFolders(); var regExp = new RegExp(AdWordsApp.currentAccount().getCustomerId()); while(folderIterator.hasNext()){ var folder = folderIterator.next(); if(folder.getName().match(regExp)) return folder; } // Since no folder has been found: Create one. var newFolderName = AdWordsApp.currentAccount().getName() + " (" + AdWordsApp.currentAccount().getCustomerId() + ")"; Logger.log("Creating folder '" + newFolderName + "'"); return baseFolder.createFolder(newFolderName); } /* * Creates a spreadsheet for QS tracking. * Adds headers to the spreadsheet. * Returns the file. */ function addReportFile(folder, name){ var spreadsheet = SpreadsheetApp.create(name, 1, 4); var sheet = spreadsheet.getActiveSheet(); sheet.setName("QS history"); // Put in the table headings sheet.getRange(1, 1, 1, 4).setValues([["Campaign", "AdGroup", "Keyword", "ID string"]]); //sheet.getRange(1, 1, 1, 4).setFontWeight("bold"); sheet.setColumnWidth(4, 1); var file = DriveApp.getFileById(spreadsheet.getId()); folder.addFile(file); var parentFolder = file.getParents().next(); parentFolder.removeFile(file); return folder.getFilesByName(name).next(); } /* * Creates a spreadsheet for the summary and stores it in the folder. * Creates sheets for the Percentages and Averages. * Populates header rows and columns. */ function addSummaryFile(folder, name){ var spreadsheet = SpreadsheetApp.create(name); var sheetH = spreadsheet.getActiveSheet(); sheetH.setName("Percentages"); // Add the first column for the horizontal data table. sheetH.getRange(1, 1, 72, 1).setValues( [["All keywords"], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], ['Total'], ["Keywords with impressions"], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], ['Total'], ["Impression weighted"], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], ['Total'], ["Click weighted"], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], ['Total'], ["Conversion weighted"], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], ['Total'], ["Conversion value weighted"], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], ['Total'] ] ); sheetH.getRange("A:A").setNumberFormat('@STRING@'); sheetH.autoResizeColumn(1); var sheetV = spreadsheet.insertSheet("Averages"); // Add the first rows for the vertical data table. sheetV.getRange(1, 1, 4, 7).setValues([ ["Date", "Average", "Average for keywords with impressions", "Impression weighted", "Click weighted", "Conversion weighted", "Value weighted"], ["Highest", "", "", "", "", "", ""], ["Lowest", "", "", "", "", "", ""], ["Average", "", "", "", "", "", ""] ]); // Add some formulas for maximums, minimums, and averages. sheetV.getRange(2, 2, 3, 6).setFormulas([ ["=MAX(B$5:B)", "=MAX(C$5:C)", "=MAX(D$5:D)", "=MAX(E$5:E)", "=MAX(F$5:F)", "=MAX(G$5:G)"], ["=MIN(B$5:B)", "=MIN(C$5:C)", "=MIN(D$5:D)", "=MIN(E$5:E)", "=MIN(F$5:F)", "=MIN(G$5:G)"], ["=AVERAGE(B$5:B)", "=AVERAGE(C$5:C)", "=AVERAGE(D$5:D)", "=AVERAGE(E$5:E)", "=AVERAGE(F$5:F)", "=AVERAGE(G$5:G)"] ]); sheetV.getRange(1, 1, 1, 7).setFontWeight("bold").setNumberFormat('@STRING@'); sheetV.autoResizeColumn(1); sheetV.autoResizeColumn(2); sheetV.autoResizeColumn(3); sheetV.autoResizeColumn(4); sheetV.autoResizeColumn(5); sheetV.autoResizeColumn(6); sheetV.autoResizeColumn(7); // Store the spreadsheet. var file = DriveApp.getFileById(spreadsheet.getId()); folder.addFile(file); var parentFolder = file.getParents().next(); parentFolder.removeFile(file); return folder.getFilesByName(name).next(); } /* * Replaces the About sheet in the summary spreadsheet with a fresh one from the master sheet. This way, the sheet (including the FAQ) stays up to date. * Also replaces the Dashboard with a fresh copy (resulting in an empty sheet with the correct conditional formatting). * If there's a new version, a sheet "New Version Available!" is added. */ function updateInfo(summarySpreadsheet, version){ var templateSpreadsheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1qnTYdpBCgHP_5u5eQcXmc5gP0NrOrBK51JnTCTlc0_g/"); var oldSheet = summarySpreadsheet.getSheetByName("Dashboard"); if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet); templateSpreadsheet.getSheetByName("Dashboard v" + version).copyTo(summarySpreadsheet).setName("Dashboard"); var oldSheet = summarySpreadsheet.getSheetByName("About + FAQ"); if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet); templateSpreadsheet.getSheetByName("About v" + version).copyTo(summarySpreadsheet).setName("About + FAQ"); var oldSheet = summarySpreadsheet.getSheetByName("New Version Available!"); if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet); // Check if there is a newer version. var versionHistory = templateSpreadsheet.getSheetByName("Version History").getDataRange().getValues(); if(versionHistory[0][0] != version){ // There's a new version available (at least one). // Look for the row which has the info about the current (old) version. var oldVersionRow = 1; while(oldVersionRow < versionHistory.length && versionHistory[oldVersionRow][0] != version){ oldVersionRow++; } // Copy the entire version history. var newVersionSheet = templateSpreadsheet.getSheetByName("Version History").copyTo(summarySpreadsheet).setName("New Version Available!"); // Remove everything about the old version. newVersionSheet.deleteRows(oldVersionRow + 1, versionHistory.length - oldVersionRow); // Add new Rows at the beginning. newVersionSheet.insertRows(1, 6); newVersionSheet.getRange(1, 1, 6, 2).setValues([["Latest version:", versionHistory[0][0]], ["Your version:", version], ["", ""], ["Get the latest version at", "https://www.ppc-epiphany.com/qstracker/latest"], ["", ""], ["Newer Versions", ""]]); newVersionSheet.getRange(1, 1, 1, 2).setFontWeight("bold"); newVersionSheet.getRange(6, 1, 1, 1).setFontWeight("bold"); newVersionSheet.autoResizeColumn(1); newVersionSheet.autoResizeColumn(2); } } /* * Inserts a line or column chart into the dashboard sheet. * The chart is based on data from the Percentages or Averages sheet. */ function addChartToDashboard(name, type, sheets, row, col, lastRow, lastCol, vCol, compareStepsBack){ var chartBuilder = sheets['charts'].newChart(); chartBuilder .setOption('title', name) .setOption('width', 800) .setOption('height', 349) .setOption('colors', ['#fa9d1c','#00507d']) .setPosition(row, col, 0, 0); switch(type){ case "column": var statsRow = (vCol - 2) * 12 + 1; // First range for a column chart is always the same column with QS from 1 to 10. var dataRanges = [sheets['dataH'].getRange(1, 1, 11, 1)]; if(compareStepsBack && lastCol > 2){ // The column for comparison is either the specified number of columns behind lastCol, or 2 (the first column with data). dataRanges.push(sheets['dataH'].getRange(statsRow, Math.max(2, lastCol - compareStepsBack), 11, 1)); } dataRanges.push(sheets['dataH'].getRange(statsRow, lastCol, 11, 1)); chartBuilder = chartBuilder.asColumnChart(); break; case "line": var dataRanges = [sheets['dataV'].getRange(5, 1, lastRow - 2, 1), sheets['dataV'].getRange(5, vCol, lastRow - 2, 1)]; chartBuilder = chartBuilder.asLineChart(); chartBuilder.setOption("vAxis.maxValue", 10); chartBuilder.setOption("vAxis.ticks", [0,2,4,6,8,10]); chartBuilder.setLegendPosition(Charts.Position.NONE); break; } for(var i in dataRanges) chartBuilder.addRange(dataRanges[i]); sheets['charts'].insertChart(chartBuilder.build()); } /* * Tracks the execution of the script as an event in Google Analytics. * Sends the version number and a random UUID (basically just a random number, required by Analytics). * Basically tells that somewhere someone ran the script with a certain version. * Credit for the idea goes to Russel Savage, who posted his version at https://www.freeadwordsscripts.com/2013/11/track-adwords-script-runs-with-google.html. */ function trackInAnalytics(version){ // Create the random UUID from 30 random hex numbers gets them into the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (with y being 8, 9, a, or b). var uuid = ""; for(var i = 0; i < 30; i++){ uuid += parseInt(Math.random()*16).toString(16); } uuid = uuid.substr(0, 8) + "-" + uuid.substr(8, 4) + "-4" + uuid.substr(12, 3) + "-" + parseInt(Math.random() * 4 + 8).toString(16) + uuid.substr(15, 3) + "-" + uuid.substr(18, 12); var url = "https://www.google-analytics.com/collect?v=1&t=event&tid=UA-74705456-1&cid=" + uuid + "&ds=adwordsscript&an=qstracker&av=" + version + "&ec=AdWords%20Scripts&ea=Script%20Execution&el=QS%20Tracker%20v" + version; UrlFetchApp.fetch(url); }

The Quality Score Tracker is a script that will document all of an account’s quality scores into a Google Sheet. In case you don’t know, quality score is Google’s rating of the quality and relevance of your keywords, website and ad copy. The higher your quality score, the less Google will charge you to achieve the same ad position. As your score decreases, Google will charge you more and more to run your seemingly irrelevant ad.

The script will also generate a graph that represents an aggregated average of all your quality scores over time. Once you authorize and run the script, the report should be in your Google Drive. PPC-Epiphany recommends running this script daily.

2) Account Summary Report by Google

// Copyright 2015, Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License./** * @name Account Summary Report * * @overview The Account Summary Report script generates an at-a-glance report * showing the performance of an entire AdWords account. See * https://developers.google.com/adwords/scripts/docs/solutions/account-summary * for more details. * * @author AdWords Scripts Team [adwords-scripts@googlegroups.com] * * @version 1.0.4 * * @changelog * - version 1.0.4 * - Improved code readability and comments. * - version 1.0.3 * - Added validation for external spreadsheet setup. * - version 1.0.2 * - Fixes date formatting bug in certain timezones. * - version 1.0.1 * - Improvements to time zone handling. * - version 1.0 * - Released initial version. */var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';/** * Configuration to be used for running reports. */ var REPORTING_OPTIONS = { // Comment out the following line to default to the latest reporting version. apiVersion: 'v201705' };function main() { Logger.log('Using spreadsheet - %s.', SPREADSHEET_URL); var spreadsheet = validateAndGetSpreadsheet(); spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone()); spreadsheet.getRangeByName('account_id_report').setValue( AdWordsApp.currentAccount().getCustomerId());var yesterday = getYesterday(); var date = getFirstDayToCheck(spreadsheet, yesterday);var rows = [];while (date.getTime() <= yesterday.getTime()) { var row = getReportRowForDate(date); rows.push([new Date(date), row['Cost'], row['AverageCpc'], row['Ctr'], row['AveragePosition'], row['Impressions'], row['Clicks']]); spreadsheet.getRangeByName('last_check').setValue(date); date.setDate(date.getDate() + 1); } if (rows.length > 0) { writeToSpreadsheet(rows);var email = spreadsheet.getRangeByName('email').getValue(); if (email) { sendEmail(email); } } }/** * Append the data rows to the spreadsheet. * * @param {Array<Array>} rows The data rows. */ function writeToSpreadsheet(rows) { var access = new SpreadsheetAccess(SPREADSHEET_URL, 'Report'); var emptyRow = access.findEmptyRow(6, 2); if (emptyRow < 0) { access.addRows(rows.length); emptyRow = access.findEmptyRow(6, 2); } access.writeRows(rows, emptyRow, 2); }function sendEmail(email) { var day = getYesterday(); var yesterdayRow = getReportRowForDate(day); day.setDate(day.getDate() - 1); var twoDaysAgoRow = getReportRowForDate(day); day.setDate(day.getDate() - 5); var weekAgoRow = getReportRowForDate(day);var html = []; html.push( '', '', '', '', '', '', "", '', '', '', ' ', " " + 'Powered by AdWords Scripts ', ' ', " Account Summary report ", ' ', " ", AdWordsApp.currentAccount().getCustomerId(), '', ' ', ' ', "", '', "", "", "", '', emailRow('Cost', 'Cost', yesterdayRow, twoDaysAgoRow, weekAgoRow), emailRow('Average Cpc', 'AverageCpc', yesterdayRow, twoDaysAgoRow, weekAgoRow), emailRow('Ctr', 'Ctr', yesterdayRow, twoDaysAgoRow, weekAgoRow), emailRow('Average Position', 'AveragePosition', yesterdayRow, twoDaysAgoRow, weekAgoRow), emailRow('Impressions', 'Impressions', yesterdayRow, twoDaysAgoRow, weekAgoRow), emailRow('Clicks', 'Clicks', yesterdayRow, twoDaysAgoRow, weekAgoRow), ' Yesterday Two Days Ago A week ago ', '', ''); MailApp.sendEmail(email, 'AdWords Account ' + AdWordsApp.currentAccount().getCustomerId() + ' Summary Report', '', {htmlBody: html.join('

')}); } function emailRow(title, column, yesterdayRow, twoDaysAgoRow, weekAgoRow) { var html = []; html.push(' ', "" + title + ' ', " " + yesterdayRow[column] + ' ', " " + twoDaysAgoRow[column] + formatChangeString(yesterdayRow[column], twoDaysAgoRow[column]) + ' ', " " + weekAgoRow[column] + formatChangeString(yesterdayRow[column], weekAgoRow[column]) + ' ', ' '); return html.join('

'); } function getReportRowForDate(date) { var timeZone = AdWordsApp.currentAccount().getTimeZone(); var dateString = Utilities.formatDate(date, timeZone, 'yyyyMMdd'); return getReportRowForDuring(dateString + ',' + dateString); } function getReportRowForDuring(during) { var report = AdWordsApp.report( 'SELECT Cost, AverageCpc, Ctr, AveragePosition, Impressions, Clicks ' + 'FROM ACCOUNT_PERFORMANCE_REPORT ' + 'DURING ' + during, REPORTING_OPTIONS); return report.rows().next(); } function formatChangeString(newValue, oldValue) { var x = newValue.indexOf('%'); if (x != -1) { newValue = newValue.substring(0, x); var y = oldValue.indexOf('%'); oldValue = oldValue.substring(0, y); } var change = parseFloat(newValue - oldValue).toFixed(2); var changeString = change; if (x != -1) { changeString = change + '%'; } if (change >= 0) { return " (+" + changeString + ')'; } else { return " (" + changeString + ')'; } } function SpreadsheetAccess(spreadsheetUrl, sheetName) { this.spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl); this.sheet = this.spreadsheet.getSheetByName(sheetName); // what column should we be looking at to check whether the row is empty? this.findEmptyRow = function(minRow, column) { var values = this.sheet.getRange(minRow, column, this.sheet.getMaxRows(), 1).getValues(); for (var i = 0; i < values.length; i++) { if (!values[i][0]) { return i + minRow; } } return -1; }; this.addRows = function(howMany) { this.sheet.insertRowsAfter(this.sheet.getMaxRows(), howMany); }; this.writeRows = function(rows, startRow, startColumn) { this.sheet.getRange(startRow, startColumn, rows.length, rows[0].length). setValues(rows); }; } /** * Gets a date object that is noon yesterday. * * @return {Date} A date object that is equivalent to noon yesterday in the * account's time zone. */ function getYesterday() { var yesterday = new Date(new Date().getTime() - 24 * 3600 * 1000); return new Date(getDateStringInTimeZone('MMM dd, yyyy 12:00:00 Z', yesterday)); } /** * Returned the last checked date + 1 day, or yesterday if there isn't * a specified last checked date. * * @param {Spreadsheet} spreadsheet The export spreadsheet. * @param {Date} yesterday The yesterday date. * * @return {Date} The date corresponding to the first day to check. */ function getFirstDayToCheck(spreadsheet, yesterday) { var last_check = spreadsheet.getRangeByName('last_check').getValue(); var date; if (last_check.length == 0) { date = new Date(yesterday); } else { date = new Date(last_check); date.setDate(date.getDate() + 1); } return date; } /** * Produces a formatted string representing a given date in a given time zone. * * @param {string} format A format specifier for the string to be produced. * @param {date} date A date object. Defaults to the current date. * @param {string} timeZone A time zone. Defaults to the account's time zone. * @return {string} A formatted string of the given date in the given time zone. */ function getDateStringInTimeZone(format, date, timeZone) { date = date || new Date(); timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone(); return Utilities.formatDate(date, timeZone, format); } /** * Validates the provided spreadsheet URL to make sure that it's set up * properly. Throws a descriptive error message if validation fails. * * @return {Spreadsheet} The spreadsheet object itself, fetched from the URL. */ function validateAndGetSpreadsheet() { if ('YOUR_SPREADSHEET_URL' == SPREADSHEET_URL) { throw new Error('Please specify a valid Spreadsheet URL. You can find' + ' a link to a template in the associated guide for this script.'); } var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL); var email = spreadsheet.getRangeByName('email').getValue(); if ('foo@example.com' == email) { throw new Error('Please either set a custom email address in the' + ' spreadsheet, or set the email field in the spreadsheet to blank' + ' to send no email.'); } return spreadsheet; }

This script creates a Google Sheet that provides a full report of your entire AdWords account, which will include metrics such as date, CPC, CTR, impressions, and average position. Setting the script’s frequency to daily will send detailed daily reports straight to your email address.

Set up is fairly straight forward. Create a copy of this spreadsheet, and enter the spreadsheets URL into the script.

You can also enter your email address into the spreadsheet to get the report emailed to you daily, as well as change the “Last check” date in the spreadsheet to aggregate data starting from a specific time. Google recommends you schedule this script to run “Daily, 3 am or later to guarantee accuracy.”

3) Slack Notification CTR Script by AdHawk

//AdHawk Slack Script //replace "WEBHOOK_URL" with your given slack url var SLACK_URL = 'WEBHOOK_URL'; //channel you would like to receive notification in (either #specified_channel or @person) var channel= '#CHANNEL' //the CTR you would like to be notified if any active campaign falls below var clickThroughRate=.05 /** * Sends a message to Slack * @param {string} text The message to send in slack formatting. See: * https://api.slack.com/docs/message-formatting. * @param {string=} opt_channel An optional channel, which can be channel e.g. * '#adwords' or a direct message e.g. '@sundar'. Defaults to '#general'. */ function sendSlackMessage(text, opt_channel) { var slackMessage = { text: text, icon_url: 'https://www.gstatic.com/images/icons/material/product/1x/adwords_64dp.png', username: 'AdWords Scripts', channel: channel }; var options = { method: 'POST', contentType: 'application/json', payload: JSON.stringify(slackMessage) }; UrlFetchApp.fetch(SLACK_URL, options); } function main() { getAllCampaigns() } function getAllCampaigns() { // AdWordsApp.campaigns() will return all campaigns that are not removed by // default. var campaignIterator = AdWordsApp.campaigns().get(); while (campaignIterator.hasNext()) { var campaign = campaignIterator.next(); var name=campaign.getName(); var active=campaign.isEnabled(); var stats = campaign.getStatsFor('TODAY'); var ctr = stats.getCtr(); if(active===true&&ctr<clickThroughRate) { sendSlackMessage(name+' '+'has a ctr of'+' '+ ctr); } } }

If you are a Slack fanatic like us, this script will take your experience to the next level. This script will send a notification via Slack when any of your active campaigns fall below a set CTR. Set up is 3 quick steps: you will need a Slack Webhook URL. First, go to this page, scroll down and click Set up an incoming webhook

Click Add Configuration and select which channel you would like the notification to go to. Copy the Webhook URL given (https://hooks.slack.com/services/….)

Now in the script, replace “WebHook URL” with your given URL, “CHANNEL” with the channel you would like to receive the notification, and “CTR” with the level you would like the notification to trigger. (Keep the URL and channel in quotations, and leave the CTR number out of quotations.)

It’s up to you how often you would like the script to run. I like to set the frequency to “daily”, and I receive notifications when any of my campaigns fall below a CTR of .05.

/*************************************************************************************** * AdWords Account Audit -- Check Ads for disapprovals -- text if there are open issues * Version 1.0 * Created By: Derek Martin * DerekMartinLA.com ****************************************************************************************/ // This script was heavily inspired by Russell Savage so all credit where its due! // Sign up at Twilio.com and get an API key (sid) and Auth code and place here // Url: https://www.twilio.com/ var sid = 'ACxxxxxxx'; // looks like this: var auth = 'xxxxxxxx'; var to_number = '+1234567890'; // put your phone number here var from_number = '+1234567890'; // this is the phone number given to you by Twilio var email_address = 'youremail@yourdomain.com'; // put the email you want to send to here function main() { var campIter = AdWordsApp.campaigns().withCondition('Status = ENABLED').get(); var disapprovedList = []; while (campIter.hasNext()) { var camp = campIter.next(); adsIter = camp.ads().withCondition('Status = ENABLED').withCondition('ApprovalStatus = DISAPPROVED').get(); info('Checking ' + camp.getName()); numDisapproved = adsIter.totalNumEntities(); if (numDisapproved > 0) { // there are disapproved ads while (adsIter.hasNext()) { var ad = adsIter.next(); warn('Ad ' + ad.getId() + ' : Headline : ' + ad.getHeadline() + ' is currently DISAPPROVED.'); disapproved = new disapprovedAd(camp.getName(), ad.getAdGroup().getName(), ad.getHeadline(), ad.getDescription1(), ad.getDescription2(), ad.getDisplayUrl(), ad.getDestinationUrl(), ad.getDisapprovalReasons()); // info(camp.getName() + ' ' + ad.getAdGroup().getName() + ' ' + ad.getHeadline() + ' ' + ad.getDescription1() + ' ' + ad.getDescription2() + ' ' + ad.getDisapprovalReasons()); disapprovedList.push(disapproved); } // dont do anything if disapproved ads are zero // info('hitting the else statement'); // ads are collected, text message // prepare spreadsheet and email and then text msg info('Collected all disapproved ads, expect an email with report results momentarily'); var fileUrl = createSpreadsheet(disapprovedList); info(' '); info('Or you can find it here: ' + fileUrl); var clientName = AdWordsApp.currentAccount().getName().split("-"); sendAnEmail(clientName[0], disapprovedList.toString(), fileUrl); // The third parameter is what you want the text or voice message to say var client = new Twilio(sid,auth); var clientName = AdWordsApp.currentAccount().getName().split(); var warningMsg = 'ALERT: ' + clientName[0] + ' has ' + numDisapproved + ' disapproved ads in campaign: ' + camp.getName() + '

' + '

'; warningMsg += 'Click here: ' + fileUrl + ' to review the disapproved list.'; client.sendMessage(to_number,from_number,warningMsg); } // end of campaign iteration } } function createSpreadsheet(results) { var newSS = SpreadsheetApp.create('disapprovedAds', results.length, 26); var sheet = newSS.getActiveSheet(); var resultList = results; var columnNames = ["Campaign", "Ad Group","Headline", "Description1","Description2","Display URL","Destination URL","Disapproval Reason"]; var headersRange = sheet.getRange(1, 1, 1, columnNames.length); headersRange.setValues([columnNames]); var i = 0; for each (headline in resultList) { var campaign, adgroup, headline, description1, description2, displayUrl, destinationUrl, disapprovalReasons; campaign = resultList[i].campaign; adgroup = resultList[i].adgroup; headline = resultList[i].headline; description1 = resultList[i].d1; description2 = resultList[i].d2; displayUrl = resultList[i].displayUrl; destinationUrl = resultList[i].destinationUrl; disapprovalReasons = resultList[i].disapprovalReasons; sheet.appendRow([campaign, adgroup, headline, description1, description2, displayUrl, destinationUrl, disapprovalReasons]); // Sets the first column to a width which fits the text sheet.setColumnWidth(1, 300); sheet.setColumnWidth(2, 300); sheet.setColumnWidth(3, 300); sheet.setColumnWidth(4, 300); sheet.setColumnWidth(5, 300); sheet.setColumnWidth(6, 300); sheet.setColumnWidth(7, 300); sheet.setColumnWidth(8, 300); i++; } return newSS.getUrl(); } function sendAnEmail (clientName, results, fileUrl) { var data = Utilities.parseCsv(results, '\t'); var clientName = AdWordsApp.currentAccount().getName().split(); var today = new Date(); today = today.getMonth() + today.getDate() + today.getFullYear(); var filename = clientName[0] + 'disapproved-ads' + today; // Send an email with Search list attachment var blob = Utilities.newBlob(results, 'text/html', ''); MailApp.sendEmail(email_address, clientName + ' Disapproved Ad Results ', 'You can find the list of disapproved ads at the following URL:' + fileUrl, { name: ' Client Disapproved Ads' }); } function disapprovedAd(campaign, adgroup, headline, d1, d2, displayUrl, destinationURL, reason) { this.campaign = campaign; this.adgroup = adgroup; this.headline = headline; this.d1 = d1; this.d2 = d2; this.displayUrl = displayUrl; this.destinationUrl = destinationURL; this.reason = reason; } // HELPER FUNCTIONS function warn(msg) { Logger.log('WARNING: '+msg); } function info(msg) { Logger.log(msg); } // TWILIO ACCESS function Twilio(e,t){function n(e){return{Authorization:"Basic "+Utilities.base64Encode(e.ACCOUNT_SID+":"+e.AUTH_TOKEN)}}this.ACCOUNT_SID=e;this.AUTH_TOKEN=t;this.MESSAGES_ENDPOINT="https://api.twilio.com/2010-04-01/Accounts/"+this.ACCOUNT_SID+"/Messages.json";this.CALLS_ENDPOINT="https://api.twilio.com/2010-04-01/Accounts/"+this.ACCOUNT_SID+"/Calls.json";this.sendMessage=function(e,t,r){var i={method:"POST",payload:{To:e,From:t,Body:r},headers:n(this)};var s=UrlFetchApp.fetch(this.MESSAGES_ENDPOINT,i).getContentText();return JSON.parse(s)["sid"]};this.makeCall=function(e,t,r){var i="https://proj.rjsavage.com/savageautomation/twilio_script/dynamicSay.php?alert="+encodeURIComponent(r);var s={method:"POST",payload:{To:e,From:t,Url:i},headers:n(this)};var o=UrlFetchApp.fetch(this.CALLS_ENDPOINT,s).getContentText();return JSON.parse(o)["sid"]}}

This Script allows you to receive a text message and email when one of your ads is disapproved. You will need to sign up for a free Twilio account, and place your API Key (Sid) and Token in the code, along with your Twilio phone number, your personal phone number, and your email.

Find your credentials here:

Then, create your Twilio phone number:

If you are using the free trail, you will need to add your number as a verified number:

After these steps, enter these credentials into the script, and you are good to go!

Set the frequency to hourly to get the most up to date notifications.

//----------------------------------- // Pause Ads with Low CTR // Created By: Russ Savage // FreeAdWordsScripts.com //----------------------------------- function main() { // Let's start by getting all of the adGroups that are active var ag_iter = AdWordsApp.adGroups() .withCondition("Status = ENABLED") .get(); // Then we will go through each one while (ag_iter.hasNext()) { var ag = ag_iter.next(); var ad_iter = ag.ads() .withCondition("Status = ENABLED") .forDateRange("ALL_TIME") .orderBy("Ctr DESC") .get(); var ad_array = new Array(); while(ad_iter.hasNext()) { ad_array.push(ad_iter.next()); } if(ad_array.length > 1) { for(var i = 1; i < ad_array.length; i++) { ad_array[i].pause(); //or .remove(); to delete them } } } }

Have you ever realized that your ad just isn’t working, but already spent money on the failed campaign before you could pull the plug? With this script, you can automatically pause the worst performing ads in an ad group. (you can also run the same process for impressions with this script.)

/* * * Advanced ad scheduling * * This script will apply ad schedules to campaigns or shopping campaigns and set * the ad schedule bid modifier and mobile bid modifier at each hour according to * multiplier timetables in a Google sheet. * * This version creates schedules with modifiers for 4 hours, then fills the rest * of the day and the other days of the week with schedules with no modifier as a * fail safe. * * Version: 3.0 * Updated to allow -100% bids, change mobile adjustments and create fail safes. * brainlabsdigital.com * */function main() { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// //Options//The Google sheet to use //The default value is the example sheet var spreadsheetUrl = "https://docs.google.com/a/brainlabsdigital.com/spreadsheets/d/1JDGBPs2qyGdHd94BRZw9lE9JFtoTaB2AmlL7xcmLx2g/edit#gid=0";//Shopping or regular campaigns //Use true if you want to run script on shopping campaigns (not regular campaigns). //Use false for regular campaigns. var shoppingCampaigns = false;//Use true if you want to set mobile bid adjustments as well as ad schedules. //Use false to just set ad schedules. var runMobileBids = false;//Optional parameters for filtering campaign names. The matching is case insensitive. //Select which campaigns to exclude e.g ["foo", "bar"] will ignore all campaigns //whose name contains 'foo' or 'bar'. Leave blank [] to not exclude any campaigns. var excludeCampaignNameContains = [];//Select which campaigns to include e.g ["foo", "bar"] will include only campaigns //whose name contains 'foo' or 'bar'. Leave blank [] to include all campaigns. var includeCampaignNameContains = [];//When you want to stop running the ad scheduling for good, set the lastRun //variable to true to remove all ad schedules. var lastRun = false;//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~////Initialise for use later. var weekDays = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]; var adScheduleCodes = []; var campaignIds = [];//Retrieving up hourly data var scheduleRange = "B2:H25"; var accountName = AdWordsApp.currentAccount().getName(); var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl); var sheets = spreadsheet.getSheets();var timeZone = AdWordsApp.currentAccount().getTimeZone(); var date = new Date(); var dayOfWeek = parseInt(Utilities.formatDate(date, timeZone, "uu"), 10) - 1; var hour = parseInt(Utilities.formatDate(date, timeZone, "HH"), 10);var sheet = sheets[0]; var data = sheet.getRange(scheduleRange).getValues();//This hour's bid multiplier. var thisHourMultiplier = data[hour][dayOfWeek]; var lastHourCell = "I2"; sheet.getRange(lastHourCell).setValue(thisHourMultiplier);//The next few hours' multipliers var timesAndModifiers = []; var otherDays = weekDays.slice(0); for (var h=0; h<5; h++) { var newHour = (hour + h)%24; if (hour + h > 23) { var newDay = (dayOfWeek + 1)%7; } else { var newDay = dayOfWeek; } otherDays[newDay] = "-";if (h<4) { // Use the specified bids for the next 4 hours var bidModifier = data[newHour][newDay]; if (isNaN(bidModifier) || (bidModifier < -0.9 && bidModifier > -1) || bidModifier > 9) { Logger.log("Bid modifier '" + bidModifier + "' for " + weekDays[newDay] + " " + newHour + " is not valid."); timesAndModifiers.push([newHour, newHour+1, weekDays[newDay], 0]); } else if (bidModifier != -1 && bidModifier.length != 0) { timesAndModifiers.push([newHour, newHour+1, weekDays[newDay], bidModifier]); } } else { // Fill in the rest of the day with no adjustment (as a back-up incase the script breaks) timesAndModifiers.push([newHour, 24, weekDays[newDay], 0]); } }if (hour>0) { timesAndModifiers.push([0, hour, weekDays[dayOfWeek], 0]); }for (var d=0; d<otherDays.length; d++) { if (otherDays[d] != "-") { timesAndModifiers.push([0, 24, otherDays[d], 0]); } }//Pull a list of all relevant campaign IDs in the account. var campaignSelector = ConstructIterator(shoppingCampaigns); for(var i = 0; i < excludeCampaignNameContains.length; i++){ campaignSelector = campaignSelector.withCondition('Name DOES_NOT_CONTAIN_IGNORE_CASE "' + excludeCampaignNameContains[i] + '"'); } campaignSelector = campaignSelector.withCondition("Status IN [ENABLED,PAUSED]"); var campaignIterator = campaignSelector.get(); while(campaignIterator.hasNext()){ var campaign = campaignIterator.next(); var campaignName = campaign.getName(); var includeCampaign = false; if(includeCampaignNameContains.length === 0){ includeCampaign = true; } for(var i = 0; i < includeCampaignNameContains.length; i++){ var index = campaignName.toLowerCase().indexOf(includeCampaignNameContains[i].toLowerCase()); if(index !== -1){ includeCampaign = true; break; } } if(includeCampaign){ var campaignId = campaign.getId(); campaignIds.push(campaignId); } }//Return if there are no campaigns. if(campaignIds.length === 0){ Logger.log("There are no campaigns matching your criteria."); return; }//Remove all ad scheduling for the last run. if(lastRun){ checkAndRemoveAdSchedules(campaignIds, []); return; }// Change the mobile bid adjustment if(runMobileBids){ if (sheets.length < 2) { Logger.log("Mobile ad schedule sheet was not found in the Google spreadsheet."); } else { var sheet = sheets[1]; var data = sheet.getRange(scheduleRange).getValues(); var thisHourMultiplier_Mobile = data[hour][dayOfWeek];if (thisHourMultiplier_Mobile.length === 0) { thisHourMultiplier_Mobile = -1; }if (isNaN(thisHourMultiplier_Mobile) || (thisHourMultiplier_Mobile < -0.9 && thisHourMultiplier_Mobile > -1) || thisHourMultiplier_Mobile > 3) { Logger.log("Mobile bid modifier '" + thisHourMultiplier_Mobile + "' for " + weekDays[dayOfWeek] + " " + hour + " is not valid."); thisHourMultiplier_Mobile = 0; }var totalMultiplier = ((1+thisHourMultiplier_Mobile)*(1+thisHourMultiplier))-1; sheet.getRange("I2").setValue(thisHourMultiplier_Mobile); sheet.getRange("T2").setValue(totalMultiplier); ModifyMobileBidAdjustment(campaignIds, thisHourMultiplier_Mobile); } }// Check the existing ad schedules, removing those no longer necessary var existingSchedules = checkAndRemoveAdSchedules(campaignIds, timesAndModifiers);// Add in the new ad schedules AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns);}/** * Function to add ad schedules for the campaigns with the given IDs, unless the schedules are * referenced in the existingSchedules array. The scheduling will be added as a hour long periods * as specified in the passed parameter array and will be given the specified bid modifier. * * @param array campaignIds array of campaign IDs to add ad schedules to * @param array timesAndModifiers the array of [hour, day, bid modifier] for which to add ad scheduling * @param array existingSchedules array of strings identifying already existing schedules. * @param bool shoppingCampaigns using shopping campaigns? * @return void */ function AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns){ // times = [[hour,day],[hour,day]] var campaignIterator = ConstructIterator(shoppingCampaigns) .withIds(campaignIds) .get(); while(campaignIterator.hasNext()){ var campaign = campaignIterator.next(); for(var i = 0; i < timesAndModifiers.length; i++){ if (existingSchedules.indexOf( timesAndModifiers[i][0] + "|" + (timesAndModifiers[i][1]) + "|" + timesAndModifiers[i][2] + "|" + Utilities.formatString("%.2f",(timesAndModifiers[i][3]+1)) + "|" + campaign.getId()) > -1) {continue; }campaign.addAdSchedule({ dayOfWeek: timesAndModifiers[i][2], startHour: timesAndModifiers[i][0], startMinute: 0, endHour: timesAndModifiers[i][1], endMinute: 0, bidModifier: Math.round(100*(1+timesAndModifiers[i][3]))/100 }); } } }/** * Function to remove ad schedules from all campaigns referenced in the passed array * which do not correspond to schedules specified in the passed timesAndModifiers array. * * @param array campaignIds array of campaign IDs to remove ad scheduling from * @param array timesAndModifiers array of [hour, day, bid modifier] of the wanted schedules * @return array existingWantedSchedules array of strings identifying the existing undeleted schedules */ function checkAndRemoveAdSchedules(campaignIds, timesAndModifiers) {var adScheduleIds = [];var report = AdWordsApp.report( 'SELECT CampaignId, Id ' + 'FROM CAMPAIGN_AD_SCHEDULE_TARGET_REPORT ' + 'WHERE CampaignId IN ["' + campaignIds.join('","') + '"]' );var rows = report.rows(); while(rows.hasNext()){ var row = rows.next(); var adScheduleId = row['Id']; var campaignId = row['CampaignId']; adScheduleIds.push([campaignId,adScheduleId]); }var chunkedArray = []; var chunkSize = 10000;for(var i = 0; i < adScheduleIds.length; i += chunkSize){ chunkedArray.push(adScheduleIds.slice(i, i + chunkSize)); }var wantedSchedules = []; var existingWantedSchedules = [];for (var j=0; j<timesAndModifiers.length; j++) { wantedSchedules.push(timesAndModifiers[j][0] + "|" + (timesAndModifiers[j][1]) + "|" + timesAndModifiers[j][2] + "|" + Utilities.formatString("%.2f",timesAndModifiers[j][3]+1)); }for(var i = 0; i < chunkedArray.length; i++){ var unwantedSchedules = []; var adScheduleIterator = AdWordsApp.targeting() .adSchedules() .withIds(chunkedArray[i]) .get(); while (adScheduleIterator.hasNext()) { var adSchedule = adScheduleIterator.next(); var key = adSchedule.getStartHour() + "|" + adSchedule.getEndHour() + "|" + adSchedule.getDayOfWeek() + "|" + Utilities.formatString("%.2f",adSchedule.getBidModifier()); if (wantedSchedules.indexOf(key) > -1) { existingWantedSchedules.push(key + "|" + adSchedule.getCampaign().getId()); } else { unwantedSchedules.push(adSchedule); } }for(var j = 0; j < unwantedSchedules.length; j++){ unwantedSchedules[j].remove(); } }return existingWantedSchedules; }/** * Function to construct an iterator for shopping campaigns or regular campaigns. * * @param bool shoppingCampaigns Using shopping campaigns? * @return AdWords iterator Returns the corresponding AdWords iterator */ function ConstructIterator(shoppingCampaigns){ if(shoppingCampaigns === true){ return AdWordsApp.shoppingCampaigns(); } else{ return AdWordsApp.campaigns(); } }/** * Function to set a mobile bid modifier for a set of campaigns * * @param array campaignIds An array of the campaign IDs to be affected * @param Float bidModifier The multiplicative mobile bid modifier * @return void */ function ModifyMobileBidAdjustment(campaignIds, bidModifier){var platformIds = []; var newBidModifier = Math.round(100*(1+bidModifier))/100;for(var i = 0; i < campaignIds.length; i++){ platformIds.push([campaignIds[i],30001]); }var platformIterator = AdWordsApp.targeting() .platforms() .withIds(platformIds) .get(); while (platformIterator.hasNext()) { var platform = platformIterator.next(); platform.setBidModifier(newBidModifier); } }

Google Ad Scheduling only allows for six bidding windows in one day. If you are the average marketer, this is more than enough time; however, the data-driven marketer would like as many bidding opportunities as possible. Luckily, with the magic power of scripts, the dream becomes a reality. This tool by Brainlabs allows you to enable bid adjustments for every hour of the day. More time = more money.

var monthly_budget =1000; function main() { var spend=AdWordsApp.currentAccount().getStatsFor("THIS_MONTH").getCost(); if(spend>monthly_budget){ pauseAllCampaigns() } } function pauseAllCampaigns() { Logger.log('Monthly budget has been exceeded…pausing') var campaignIterator = AdWordsApp.campaigns().get(); while (campaignIterator.hasNext()) { var campaign = campaignIterator.next(); campaign.pause() } }

Google Adwords allows you to automatically pause a campaign when the campaign’s budget is met. With this script, you can pause all campaigns when the total account level cost meets your budget. Simply set “monthly_budget” equal to your budget, and schedule the script to run hourly. The script will pause all active campaigns in an account if the month’s budget is exceeded.

// Copyright 2015, Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License./** * @name Bid Testing * * @overview The Bid Testing script allows you to try different levels of * bidding for keywords in your advertiser account to determine what bids * work best to achieve your goals. * See https://developers.google.com/adwords/scripts/docs/solutions/bid-testing * for more details. * * @author AdWords Scripts Team [adwords-scripts@googlegroups.com] * * @version 1.0.3 * * @changelog * - version 1.0.3 * - Replaced deprecated keyword.setMaxCpc() and keyword.getMaxCpc(). * - version 1.0.2 * - Added validation for user settings. * - version 1.0.1 * - Improvements to time zone handling. * - version 1.0 * - Released initial version. */var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL'; var CAMPAIGN_NAME = 'YOUR_CAMPAIGN_NAME';function main() { validateCampaignName(); Logger.log('Using spreadsheet - %s.', SPREADSHEET_URL); var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL); spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());var multipliersSheet = spreadsheet.getSheetByName('Multipliers');var multipliers = multipliersSheet.getDataRange().getValues(); // Find if we have a multiplier left to apply. var multiplierRow = 1; for (; multiplierRow < multipliers.length; multiplierRow++) { // if we haven't marked a multiplier as applied, use it. if (!multipliers[multiplierRow][1]) { break; } } var today = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), 'yyyyMMdd'); var shouldReport = multiplierRow > 1; var shouldIncreaseBids = multiplierRow < multipliers.length; var finishedReporting = multipliersSheet.getSheetProtection().isProtected(); if (shouldReport && !finishedReporting) { // If we have at least one multiplier marked as applied, // let's record performance since the last time we ran. var lastRun = multipliers[multiplierRow - 1][1]; if (lastRun == today) { Logger.log('Already ran today, skipping'); return; } outputReport(spreadsheet, lastRun, today); if (!shouldIncreaseBids) { // We've reported one iteration after we finished bids, so mark the sheet // protected. var permissions = multipliersSheet.getSheetProtection(); permissions.setProtected(true); multipliersSheet.setSheetProtection(permissions); Logger.log('View bid testing results here: ' + SPREADSHEET_URL); } } if (shouldIncreaseBids) { // If we have a multiplier left to apply, let's do so. updateBids(spreadsheet, multipliers[multiplierRow][0]); multipliers[multiplierRow][1] = today; // Mark multiplier as applied. multipliersSheet.getDataRange().setValues(multipliers); } if (finishedReporting) { Logger.log('Script complete, all bid modifiers tested and reporting. ' + 'Please remove this script\'s schedule.'); } } function updateBids(spreadsheet, multiplier) { Logger.log('Applying bid multiplier of ' + multiplier); var startingBids = getStartingBids(spreadsheet); if (!startingBids) { startingBids = recordStartingBids(spreadsheet); } var campaign = getCampaign(); var keywordIter = campaign.keywords().get(); while (keywordIter.hasNext()) { var keyword = keywordIter.next(); var oldBid = startingBids[keyword.getText()]; if (!oldBid) { // If we don't have a starting bid, keyword has been added since we // started testing. oldBid = keyword.bidding().getCpc() || keyword.getAdGroup().bidding().getCpc(); startingBids[keyword.getText()] = oldBid; } var newBid = oldBid * multiplier; keyword.bidding().setCpc(newBid); } saveStartingBids(spreadsheet, startingBids); } function outputReport(spreadsheet, start, end) { Logger.log('Reporting on ' + start + ' -> ' + end);// Create a new sheet to output keywords to. var reportSheet = spreadsheet.insertSheet(start + ' - ' + end); var campaign = getCampaign();var rows = [['Keyword', 'Max CPC', 'Clicks', 'Impressions', 'Ctr']]; var keywordIter = campaign.keywords().get(); while (keywordIter.hasNext()) { var keyword = keywordIter.next(); var stats = keyword.getStatsFor(start, end); rows.push([keyword.getText(), keyword.bidding().getCpc(), stats.getClicks(), stats.getImpressions(), stats.getCtr()]); }reportSheet.getRange(1, 1, rows.length, 5).setValues(rows); }function recordStartingBids(spreadsheet) { var startingBids = {}; var keywords = getCampaign().keywords().get(); while (keywords.hasNext()) { var keyword = keywords.next(); var bid = keyword.bidding().getCpc() || keyword.getAdGroup().bidding().getCpc(); startingBids[keyword.getText()] = bid; } saveStartingBids(spreadsheet, startingBids); return startingBids; }function getStartingBids(spreadsheet) { var sheet = spreadsheet.getSheetByName('Starting Bids'); if (!sheet) { return; } var rawData = sheet.getDataRange().getValues(); var startingBids = {}; for (var i = 0; i < rawData.length; i++) { startingBids[rawData[i][0]] = rawData[i][1]; } return startingBids; }function saveStartingBids(spreadsheet, startingBids) { var sheet = spreadsheet.getSheetByName('Starting Bids'); if (!sheet) { sheet = spreadsheet.insertSheet('Starting Bids'); } var rows = []; for (var keyword in startingBids) { rows.push([keyword, startingBids[keyword]]); } sheet.getRange(1, 1, rows.length, 2).setValues(rows); }function dateToString(date) { return date.getFullYear() + zeroPad(date.getMonth() + 1) + zeroPad(date.getDate()); }function zeroPad(n) { if (n < 10) { return '0' + n; } else { return '' + n; } }function getCampaign() { return AdWordsApp.campaigns().withCondition("Name = '" + CAMPAIGN_NAME + "'").get().next(); }/** * Validates the provided campaign name and throws a descriptive error * if the user has not changed the email from the default fake name. * * @throws {Error} If the name is the default fake name. */ function validateCampaignName(){ if (CAMPAIGN_NAME == "YOUR_CAMPAIGN_NAME") { throw new Error('Please use a valid campaign name.'); } }/** * Validates the provided spreadsheet URL * to make sure that it's set up properly. Throws a descriptive error message * if validation fails. * * @param {string} spreadsheeturl The URL of the spreadsheet to open. * @return {Spreadsheet} The spreadsheet object itself, fetched from the URL. * @throws {Error} If the spreadsheet URL hasn't been set */ function validateAndGetSpreadsheet(spreadsheeturl) { if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') { throw new Error('Please specify a valid Spreadsheet URL. You can find' + ' a link to a template in the associated guide for this script.'); } return SpreadsheetApp.openByUrl(spreadsheeturl); }

Google already provides you with some powerful testing tools like image ad rotation and bulk uploading ready-to-test expanded text ads. This script automatically applies different bids to your ads, and records the results in a Google spreadsheet. Depending on the scheduled interval you set the script for, a new bid will be applied based on the previous period’s bid.

What to look for: You can quickly view performance depending on bids, which can reveal serious money-saving opportunities. What if bidding into position #1 is twice the cost, but only generating 25% more conversions than a lower bid in position 2 or 3? This script can tell you just that.

// Copyright 2015, Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License./** * @name Sales Countdown * * @overview The sales countdown script demonstrates how to update your ad * parameters hourly to count down until a sale you'd like to promote. See * https://developers.google.com/adwords/scripts/docs/solutions/sale-countdown * for more details. * * @author AdWords Scripts Team [adwords-scripts@googlegroups.com] * * @version 1.0 * * @changelog * - version 1.0 * - Released initial version. */// Date and time for the end of the sale. Be sure to include a time and timezone // so that the sale ends exactly when you intend. var END_DATE = new Date('February 17, 2016 13:00:00 -0500'); // Change this to the Ad Group you set up with text ads with AdParams. var AD_GROUP_NAME = 'Widget Sale';var DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;function main() { var timeLeft = calculateTimeLeftUntil(END_DATE); var adGroup = getAdGroup(AD_GROUP_NAME); var keywords = adGroup.keywords().get(); while (keywords.hasNext()) { var keyword = keywords.next(); // We want to update {param1} to use our calculated days and {param2} // for hours. keyword.setAdParam(1, timeLeft['days']); keyword.setAdParam(2, timeLeft['hours']); } }function calculateTimeLeftUntil(end) { var current = new Date(); var timeLeft = {}; var daysFloat = (end - current) / (DAY_IN_MILLISECONDS); timeLeft['days'] = Math.floor(daysFloat); timeLeft['hours'] = Math.floor(24 * (daysFloat - timeLeft['days']));// Prevent countdown to negative time period. if (timeLeft['days'] < 0) { timeLeft['days'] = 0; } if (timeLeft['hours'] < 0) { timeLeft['hours'] = 0; }return timeLeft; }function getAdGroup(name) { var adGroupIterator = AdWordsApp.adGroups() .withCondition('Name = "' + name + '"') .withLimit(1) .get(); if (adGroupIterator.hasNext()) { return adGroupIterator.next(); } }

Every marketer knows that urgency is a powerful sales tactic that can dramatically increase the effectiveness of an ad. This script allows you to display up-to-date values in ad text. This kind of automation is perfect if you are running a promotion or sale; the script calculates how many hours and days until a promotion is over, which is incorporated into the ad text.

10) AdHawk Slides Script by AdHawk

//AdHawk Slides Script var id function main() { var presentation = Slides.Presentations.create({"title": "MyNewPresentation"}); id= presentation.presentationId; Logger.log('created presentation') getAllCampaigns() } function createSlide(presentationId,stats,n) { var name="Campaign:"+" "+n var impressions="Impressions:"+" "+stats.getImpressions() var clicks="clicks:"+" "+stats.getClicks() var ctr="ctr:"+" "+stats.getCtr() var cost="cost:"+" "+stats.getCost() var cpc="cpc:"+" "+(stats.getCost()/stats.getClicks()) // You can specify the ID to use for the slide, as long as it's unique. var pageId = Utilities.getUuid(); var requests = [{ "createSlide": { "objectId": pageId, "insertionIndex": 1, "slideLayoutReference": { "predefinedLayout": "TITLE_AND_TWO_COLUMNS" } } }]; var slide = Slides.Presentations.batchUpdate({'requests': requests}, presentationId); Logger.log("Created Slide with ID: " + slide.replies[0].createSlide.objectId); addTextBox(presentationId,pageId,name,10) addTextBox(presentationId,pageId,impressions,50) addTextBox(presentationId,pageId,clicks,100) addTextBox(presentationId,pageId,ctr,150) addTextBox(presentationId,pageId,cost,200) addTextBox(presentationId,pageId,cpc,250) } function addTextBox(presentationId, pageId,param,num) { Logger.log(param) // You can specify the ID to use for elements you create, // as long as the ID is unique. var pageElementId = Utilities.getUuid(); var requests = [{ "createShape": { "objectId": pageElementId, "shapeType": "TEXT_BOX", "elementProperties": { "pageObjectId": pageId, "size": { "width": { "magnitude": 150, "unit": "PT" }, "height": { "magnitude": 50, "unit": "PT" } }, "transform": { "scaleX": 1, "scaleY": 1, "translateX": 200, "translateY": num, "unit": "PT" } } } }, { "insertText": { "objectId": pageElementId, "text": " "+param, "insertionIndex": 0 } }]; var response = Slides.Presentations.batchUpdate({'requests': requests}, presentationId); Logger.log("Created Textbox with ID: " + response.replies[0].createShape.objectId); } function getAllCampaigns() { // AdWordsApp.campaigns() will return all campaigns that are not removed by // default. var campaignIterator = AdWordsApp.campaigns().get(); Logger.log('Total campaigns found : ' + campaignIterator.totalNumEntities()); while (campaignIterator.hasNext()) { var campaign = campaignIterator.next(); Logger.log(campaign.getName()); var name=campaign.getName() if(campaign.isEnabled()) { var stats = campaign.getStatsFor('LAST_MONTH') createSlide(id,stats,name) } } }

This script takes advantage of a new API that allows AdWords Scripts to interact with Google Slides.

If you ever need to create presentations on ad performance, this is the perfect script to quickly automate the process. The script will create a Google Slide into your Google Drive, with each slide containing information on each active campaign, displaying key performance metrics from the month including impressions, cost, and CPC.

Setting up permissions for the Slides API is a two step process- you need to click on Advanced APIs in the scripts console, and check off “Slides”.

After that, click on the Google Developers Console link, and search for “slides” in the Google API manager dashboard. Click “Enable”

And that’s it! Copy and paste the script and watch in awe as the magic of AdWords Scripts automates your reports into a Google Slide presentation.

What is your favorite script?

Thanks for reading! Leave a comment if you have any trouble implementing a script, or if you want to share how you are implementing your scripts!