The Problem
Google Ads is one of the most popular platforms for online advertising, allowing businesses of all sizes to reach their target audience and increase their online visibility. However, creating and managing successful campaigns can be a time-consuming and complex process, especially if you have a large number of keywords to manage.
Fortunately, Google Ads provides various automated tools and scripts to help optimize your campaigns and save you time. In this blog post, we will explore a script that can help you generate and add new keywords to your campaigns automatically, based on performance metrics and campaign settings.
Understanding the Script
Let's take a closer look at the script provided at the end of this post, which is designed to generate and add new keywords based on campaign and ad group names. The script works by running a series of reports for different match types (broad, phrase, and exact), looking for search queries that meet the minimum threshold of conversions and impressions, are not currently being targeted in the account, and are in enabled ad groups and campaigns.
The script also includes a set of default conditions to exclude specific campaigns and ad groups, such as those with the same names as the match types being targeted. Additionally, the script can send an email with a keyword report to your chosen email address once it has added new keywords.
Implementing the Script
To implement this script in your own Google Ads account, follow these steps:
Open the Google Ads account you want to add the script to.
Click on "Tools & Settings" and select "Scripts" from the dropdown menu.
Click on the "plus" icon to create a new script, and give it a descriptive name.
Copy and paste the script provided below into the editor window.
Adjust the values of the variables at the beginning of the script to match your campaign settings and performance metrics.
Save and run the script.
Tips for Optimizing Your Campaigns:
While the automated keyword generation script can be a helpful tool for saving time and improving your campaigns' performance, there are a few best practices you should keep in mind to get the most out of it:
Set realistic performance metrics: Make sure your performance metrics are set at a reasonable level based on your industry and business goals. Setting them too high can lead to a lack of data to work with, while setting them too low can result in poor quality leads.
Review and refine the generated keywords: The script generates keywords automatically, but it's still important to review and refine them to ensure they are relevant and align with your business goals.
Continuously monitor and optimize your campaigns: Automated tools and scripts can help optimize your campaigns, but they are not a substitute for ongoing monitoring and optimization. Continuously review your campaigns and make adjustments as needed to ensure the best results.
Conclusion
Automated keyword addition can be a powerful tool for optimizing your Google Ads campaigns, saving you time, and improving your performance. By implementing the script below and following the best practices outlined, you can save time and achieve better results from your campaigns.
The Google Ads Script
minImpressions--; minConversions -= 0.01; minROAS -= 0.01; var fullCleanLog = ""; // initialise fullCleanLog var keywordCount = 0; // intialise keyword count function main() { labelMaker(); // runs the label maker if needed cleanLog("---- ADDING NEW KEYWORDS BASED ON CAMPAIGN AND AD GROUP NAMES ----"); cleanLog(" "); // Campaign Broad var broadCampaignReport = gimmeMyReport("Campaign", "Broad"); cpaCheck(broadCampaignReport, "broad"); cleanLog(" "); var bmmCampaignReport = gimmeMyReport("Campaign", "BMM"); cpaCheck(bmmCampaignReport, "broad"); cleanLog(" "); // Ad Group Broad var broadAdGroupReport = gimmeMyReport("Ad Group", "Broad"); cpaCheck(broadAdGroupReport, "broad"); cleanLog(" "); var bmmAdGroupReport = gimmeMyReport("Ad Group", "BMM"); cpaCheck(bmmAdGroupReport, "broad"); cleanLog(" "); // Campaign Phrase var phraseCampaignReport = gimmeMyReport("Campaign", "Phrase"); cpaCheck(phraseCampaignReport, "phrase"); cleanLog(" "); // Ad Group Phrase var phraseAdGroupReport = gimmeMyReport("Ad Group", "Phrase"); cpaCheck(phraseAdGroupReport, "phrase"); cleanLog(" "); // Campaign Exact var exactCampaignReport = gimmeMyReport("Campaign", "Exact"); cpaCheck(exactCampaignReport, "exact"); cleanLog(" "); // Ad Group Exact var exactAdGroupReport = gimmeMyReport("Ad Group", "Exact"); cpaCheck(exactAdGroupReport, "exact"); cleanLog(" "); // DEFAULTS cleanLog("---- ADDING DEFAULT MATCH TYPES ----"); cleanLog(" "); var report = AdWordsApp.report( "SELECT Query, Conversions, Cost, AdGroupId, QueryTargetingStatus," + " CampaignStatus, AdGroupStatus, CampaignName, AdGroupName, Impressions," + " Clicks, Ctr, ConversionRate, CostPerConversion, ConversionValue," + " QueryMatchTypeWithVariant, KeywordTextMatchingQuery, AverageCpc" + " FROM SEARCH_QUERY_PERFORMANCE_REPORT" + " WHERE Conversions > " + minConversions + " AND QueryTargetingStatus = 'NONE' AND" + " CampaignStatus = 'ENABLED' AND AdGroupStatus = 'ENABLED'" + " AND Impressions > " + minImpressions + " AND KeywordTextMatchingQuery DOES_NOT_CONTAIN 'URL=='" + " AND KeywordTextMatchingQuery DOES_NOT_CONTAIN 'CATEGORY=='" + " AND KeywordTextMatchingQuery != '*'" + " AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE 'broad'" + " AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE 'bmm'" + " AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE 'phrase'" + " AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE 'exact'" + " AND AdGroupName DOES_NOT_CONTAIN_IGNORE_CASE 'broad'" + " AND AdGroupName DOES_NOT_CONTAIN_IGNORE_CASE 'bmm'" + " AND AdGroupName DOES_NOT_CONTAIN_IGNORE_CASE 'phrase'" + " AND AdGroupName DOES_NOT_CONTAIN_IGNORE_CASE 'exact'" ); // If broad keywords are flagged, add broad keywords if (addBroadKeywordsByDefault) { cpaCheck(report, "broad"); cleanLog(" "); } // If phrase keywords are flagged, add broad keywords if (addPhraseKeywordsByDefault) { cpaCheck(report, "phrase"); cleanLog(" "); } // If exact keywords are flagged, add broad keywords if (addExactKeywordsByDefault) { cpaCheck(report, "exact"); cleanLog(" "); } if (keywordCount > 0) { try { var subject = "[YOURNAME] Keyword Report for " + AdsApp.currentAccount().getName(); var body = fullCleanLog; MailApp.sendEmail(yourEmail, subject, body); } catch (e) { cleanLog("Unable to send keyword report email. Please check the email " + "addresses provided are valid."); } } Logger.log("Added " + keywordCount + " keyword(s)."); } // Gets the report at either campaign or ad group level of search queries which: // - have at least the minimum threshold of conversions // - are not currently being targeted in the account // - are in enabled ad groups and campaigns // - have at least the minimum threshold of impressions function gimmeMyReport(level, matchType) { if (level.toLowerCase() == "campaign") { reportLevel = "CampaignName"; } else if (level.toLowerCase() == "ad group") { reportLevel = "AdGroupName"; } cleanLog("Level: " + level + ". Match Type: " + matchType); var report = AdWordsApp.report( "SELECT Query, Conversions, Cost, AdGroupId, QueryTargetingStatus," + " CampaignStatus, AdGroupStatus, CampaignName, AdGroupName, Impressions," + " Clicks, Ctr, ConversionRate, CostPerConversion, ConversionValue," + " QueryMatchTypeWithVariant, KeywordTextMatchingQuery, AverageCpc" + " FROM SEARCH_QUERY_PERFORMANCE_REPORT" + " WHERE Conversions > " + minConversions + " AND QueryTargetingStatus = 'NONE' AND" + " CampaignStatus = 'ENABLED' AND AdGroupStatus = 'ENABLED'" + " AND Impressions > " + minImpressions + " AND KeywordTextMatchingQuery DOES_NOT_CONTAIN 'URL=='" + " AND KeywordTextMatchingQuery DOES_NOT_CONTAIN 'CATEGORY=='" + " AND KeywordTextMatchingQuery != '*'" + " AND " + reportLevel + " CONTAINS_IGNORE_CASE '" + matchType + "'" ); return report; } // Checks whether CPA is below target. If so, it checks if checks whether query // is eligible to be added as a keyword and then adds it if so. function cpaCheck(report, criterionType) { var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var adGroupId = row["AdGroupId"]; var searchQuery = row["Query"]; var campaignName = row["CampaignName"]; var adGroupName = row["AdGroupName"]; var impr = row["Impressions"]; var clicks = row["Clicks"]; var ctr = row["Ctr"]; var avgCpc = row["AverageCpc"]; var cost = row["Cost"]; var conv = row["Conversions"]; var convRate = row["ConversionRate"]; var CPA = row["CostPerConversion"]; // can this use CostPerConversion ? var revenue = row["ConversionValue"]; var roas = revenue / cost; var kwText = row["KeywordTextMatchingQuery"]; var stats = ["Search Query: " + searchQuery, "Campaign: " + campaignName, "Ad Group: " + adGroupName, "Keyword: " + kwText, "Impressions: " + impr, "Clicks: " + clicks, "CTR: " + ctr, "Avg. CPC: " + avgCpc, "Cost: " + cost, "Revenue: " + revenue, "ROAS: " + roas.toFixed(2), "Conversions: " + conv, "Conv. rate: " + convRate, "CPA: " + CPA]; var adGroupSelector = AdsApp.adGroups().withIds([adGroupId]); var adGroupIterator = adGroupSelector.get(); while (adGroupIterator.hasNext()) { var adGroup = adGroupIterator.next(); if (CPA <= maxCPA) { if (roas >= minROAS) { try { if (canWeEvenDealWithThisTing(searchQuery)) { addThatTing(searchQuery, adGroup, criterionType, CPA, avgCpc, conv, stats); keywordCount++; } } catch (e) { cleanLog("Unable to add " + searchQuery + " as keyword."); } } } } } } // Adds the search term as a targeted keyword with either a broad match // modifier, phrase or exact match type function addThatTing(thatQuery, thatAdGroup, thatCriterionType, thatCPA, bid, thatConv, thatStats) { if (thatCriterionType == "broad") { //var addedQuery = "+" + thatQuery.replace(/ /g," +"); //for bmm var addedQuery = thatQuery; } else if (thatCriterionType == "phrase") { var addedQuery = '"' + thatQuery + '"'; } else { var addedQuery = "[" + thatQuery + "]"; } var keywordOperation = thatAdGroup.newKeywordBuilder() .withText(addedQuery) .withCpc(bid) .build(); // Gets the newly built keyword and applies the keyword label var keyword = keywordOperation.getResult(); keyword.applyLabel(keywordLabel); cleanLog(" "); // Logs statistics and details of the search query to the Logger thatStats.forEach(function(stat) { cleanLog("- " + stat); }); } // Logs a message and returns false if query is too long (more than 10 words or // more than 80 characters) to be added as a targeted keyword function canWeEvenDealWithThisTing(longQuery) { if ((longQuery.match(/\s/gi) || []).length > 10) { cleanLog(" "); cleanLog(">> '" + longQuery + "' is more than 10 words so can't be added."); return false; } else if (longQuery.length > 80) { cleanLog(" "); cleanLog(">> '" + longQuery + "' is longer than 80 characters so can't be " + "added."); return false; } else { return true; } } function cleanLog(input) { Logger.log(input); fullCleanLog += "\n" + input; } function labelMaker() { try { // checks to see if keywordLabel exists var labelSelector = AdsApp.labels(); var labelIterator = labelSelector.get(); var createKeywordLabel = true; while (labelIterator.hasNext()) { var label = labelIterator.next(); var labelName = label.getName(); if (labelName == keywordLabel) { createKeywordLabel = false; } } if (createKeywordLabel) { // if keywordLabel wasn't found amongst labels AdsApp.createLabel(keywordLabel); // creates keywordLabel Logger.log("Creating keyword label '" + keywordLabel + "'."); Logger.log(""); } } catch (e) { Logger.log("There was an error creating the keyword label. It might " + "already be on this account."); Logger.log(""); // error catching } }