If you’re not using Adpulse Close Variant automation but are still looking to automatically block irrelevant traffic in Google Ads by adding negative keywords to your search, shopping or DSA campaigns, we have a solution for you.
This script compliments the free Close Variant Spreadsheet (we built the spreadsheet to help you identify which campaigns and ad groups have bad close variant performance, allowing you to prioritize your time by focusing on those first). Once you know which campaigns and ad groups need search term sculpting, you can use this Google Ads script to actually do the work for you.
This script will add exact match negative keywords (at the ad group level) where those search terms do not match either your keywords or any allowed phrases you nominate.
An allowed phrase is defined as a word or phrase that you are happy for your search terms to contain.
For example, if you were bidding on a keyword “emergency dentist in Colorado” and you had the word “denture” as an allowed phrase, the script would see the search term “emergency denture clinic near me” as being appropriate for the ad group (even though the search term does not match the keyword) because it contains the allowed phrase “denture”. In this case, no negative keyword would be added.
Use this Google Ads script anytime you want to lock down an ad group’s search terms to truly match either your keywords or specific allowed phrases.
Some use case examples:
We recommend running this script daily.
In Google Ads, head to Tools > Bulk Actions > Scripts and click the blue + button to add a new script:
Name your Google Ads script, then copy/paste the script (see bottom of this page).
Configure your campaign and ad groups. This Google Ads script needs to be configured ad group by ad group (starting at row 8). For each ad group you wish to configure, you’ll need to nominate the campaign and the ad group combination. For each ad group, you can add (optional) allowed phrases. There are examples in the script (row 8).
Authorize and preview the script. This should highlight any errors but should also show you what changes would be made if you actually ran the script now. You may find some examples of negatives that you don’t want added – in this case just add new keywords or allowed phrases to cover those, then preview the script again to make sure it’s adding only the negatives you need.
When you are comfortable with the preview results, you can Run and/or set up a Schedule.
We recommend that you check either the change history or the Google Ads script logs periodically to ensure it’s adding the correct negatives for you. We would also check the ad group level negatives to check for negatives that should actually be allowed (either through adding keywords or updating your allowed list in the script).
And that’s it!
We’ve found that the easiest way to ease into search term automation is to start with Brand campaigns (for search) or low/medium volume ad groups for other campaign types. Once you are comfortable with the way it all works, you can look at rolling out on a larger scale.
function main() {
// Script provided by www.adpulse.app
// The purpose of this script is to add exact match negative keywords to the ad group for all search terms that do not match your keywords or the allowed phrases in that ad group. It works in a similar fashion to the www.adpulse.app “close variant manager” – see more here: https://support.adpulse.app/en/articles/9364164-close-variants-manager-explained
// User-defined inputs: Each campaign + ad group combo (and their respective allowed phrases) needs to be defined below.
var campaignsAndAdGroups = [
{
campaignName: “Campaign 1”,
adGroupName: “Ad Group 1”,
allowedPhrases: [“allowed phrase 1”, “allowed phrase 2”] // Custom allowed phrases for this ad group.
},
{
campaignName: “Campaign 2”,
adGroupName: “Ad Group 2”,
allowedPhrases: [“allowed phrase 3”, “allowed phrase 4”] // Custom allowed phrases for this ad group
}
];
campaignsAndAdGroups.forEach(function(entry) {
var campaignName = entry.campaignName;
var adGroupName = entry.adGroupName;
var allowedPhrases = entry.allowedPhrases;
Logger.log(“Processing Campaign: ” + campaignName + ” Ad Group: ” + adGroupName);
var adGroupIterator = AdsApp.adGroups()
.withCondition(‘CampaignName = “‘ + campaignName + ‘”‘)
.withCondition(‘AdGroupName = “‘ + adGroupName + ‘”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
Logger.log(“Found Ad Group: ” + adGroup.getName());
processAdGroup(adGroup, allowedPhrases);
} else {
Logger.log(“Campaign ‘” + campaignName + “‘ or Ad Group ‘” + adGroupName + “‘ not found.”);
}
});
}
function processAdGroup(adGroup, allowedPhrases) {
var adGroupId = adGroup.getId();
var reportQuery = “SELECT Query, QueryMatchTypeWithVariant, KeywordTextMatchingQuery ” +
“FROM SEARCH_QUERY_PERFORMANCE_REPORT ” +
“WHERE AdGroupId = ” + adGroupId +
” DURING LAST_30_DAYS”;
Logger.log(“Querying Search Term Report for Ad Group ID: ” + adGroupId);
var searchTermReport = AdsApp.report(reportQuery);
var keywordsIterator = adGroup.keywords()
.withCondition(“Status = ENABLED”)
.get();
Logger.log(“Processing keywords for Ad Group: ” + adGroup.getName());
var keywords = [];
while (keywordsIterator.hasNext()) {
var keyword = keywordsIterator.next();
var keywordMatchType = keyword.getMatchType();
Logger.log(“Found Keyword: ” + keyword.getText() + ” (Match Type: ” + keywordMatchType + “)”);
keywords.push({
keywordText: keyword.getText().toLowerCase(),
matchType: keywordMatchType
});
}
if (keywords.length === 0) {
Logger.log(“No enabled keywords found for Ad Group: ” + adGroup.getName());
return;
}
var rows = searchTermReport.rows();
var foundTerms = false;
while (rows.hasNext()) {
foundTerms = true;
var row = rows.next();
var searchTerm = row[“Query”].toLowerCase();
Logger.log(“Processing Search Term: ” + searchTerm);
if (isSearchTermAllowed(searchTerm, allowedPhrases)) {
Logger.log(“Search term ‘” + searchTerm + “‘ contains an allowed phrase. Skipping.”);
continue;
}
if (isSearchTermSafe(searchTerm, keywords)) {
Logger.log(“Search term ‘” + searchTerm + “‘ matches an enabled keyword. Skipping.”);
continue;
}
adGroup.createNegativeKeyword(searchTerm);
Logger.log(“Added negative keyword: ” + searchTerm + ” in Ad Group: ” + adGroup.getName());
}
if (!foundTerms) {
Logger.log(“No search terms found for Ad Group: ” + adGroup.getName());
}
}
function isSearchTermAllowed(searchTerm, allowedPhrases) {
for (var i = 0; i < allowedPhrases.length; i++) {
if (searchTerm.includes(allowedPhrases[i].toLowerCase())) {
return true;
}
}
return false;
}
function isSearchTermSafe(searchTerm, keywords) {
var keywordTypesToCheck = [“EXACT”, “PHRASE”, “BROAD”];
for (var i = 0; i < keywords.length; i++) {
var keyword = keywords[i];
var keywordText = keyword.keywordText;
Logger.log(“Checking keyword ‘” + keywordText + “‘ with match type: ” + keyword.matchType);
if (!keywordTypesToCheck.includes(keyword.matchType)) {
Logger.log(“Skipping keyword ‘” + keywordText + “‘ as it is of type: ” + keyword.matchType);
continue;
}
// Exact match logic
if (keyword.matchType === “EXACT”) {
if (searchTerm === keywordText) {
Logger.log(“Exact match found for search term: ” + searchTerm + ” with keyword: ” + keywordText);
return true;
}
We love talking PPC and spend most of our time chatting with agencies, so if you have a question please do drop us a line!
Also, Adpulse comes with a 14-day free trial, and right now the first month is only $19.99/mth, so it’s a low-risk way to try it for yourself. Sign up now!