Say goodbye to low Quality Score with this Google Ads script
Since the dawn of AdWords (now Google Ads), low Quality Scores have been the bane of every account manager’s life. Not only are their inner workings a mystery to us all, they’re also a real eyesore and annoyingly hard to fix.
To help with this, I’ve written a Low Quality Score Keyword Alert script!
If low Quality Score keywords are wasting your money, this script will help you find out where they are in order to tackle the problem head-on.
What is Quality Score?
One of the ways Google measures ad relevance is through Quality Score, a keyword-level metric on a scale from 1 to 10 that illustrates how relevant your ad is to the user.
The components of Quality Score are:
- Historical click-through-rate (CTR).
- The relevance of the keyword to the ad.
- The relevance of the keyword and ad to the search query.
- Landing page quality.
Why is Quality Score so important? Quality Score is key in determining Ad Rank, which decides where your ad appears in the search engine result pages (SERPs), and it is also a factor in how Google determines your cost-per-click (CPC).
To sum up, the higher your Quality Score, the better, as Google penalizes advertisers who bid with low Quality Scores by rarely showing their ads. According to a study by Wordstream, Quality Score can save up to 50 percent or cost up to 400 percent, so it’s incredibly important to improve your keywords’ Quality Scores if they’re low.
If you’re running a Google Grants account, low Quality Scores can put your entire account at risk of suspension, so you’ll want to remedy that as fast as possible.
Finding low Quality Score keywords
With the script below, it becomes much easier to fix low Quality Score keywords. It’ll also save you time you can spend on more interesting activities. All you need to do is set a Quality Score threshold, and the script will email you where the keywords with a score equal or lower to that value are, so you can immediately address the problem.
It can also label the keywords for you to make them even easier to find. If you have a zero-tolerance policy, it can also pause the keywords for you, though you’ll have to make sure your threshold isn’t too high in that case.
After doing a test run to check whether everything is working correctly, I recommend setting up a regular schedule depending on how often you’d like the script to check your Quality Scores for you.
How do you fix a low Quality Score?
Once the script finds your Quality Score keywords, the hard work starts. It’s really essential to either fix or remove the keywords to prevent them from doing more harm to your account.
Fundamentally, Quality Score assesses relevance, which is why CTR is a good indicator of performance; low CTR means that users are likely finding the ad irrelevant to their query.
Here are a few suggestions for dealing with low Quality Scores:
- Improve ad copy to include the keywords. Rewrite ads with a low CTR, and make sure to include relevant and high-volume keywords in your copy. A/B test your ads to find out which ones are the best-performing and learn how to keep improving your ad copy. Also try using ad extensions that increase CTR by increasing the visibility of your ad, like sitelink extensions.
- Improve your landing page. Check whether all your destination URLs are correct and your loading times aren’t too slow, as this is a major pet peeve of Google’s. Have a look at Google’s guidelines on landing page experience to see if there are any areas where you could improve your website.
- Change the ad group to put it with more relevant ads. Maintaining segmented ad groups is also key. Split ad groups according to specific keyword targeting to take advantage of top-performing keywords in the right ads. A low Quality Score keyword might simply belong in another, more relevant ad group.
Keywords need some time to generate enough impressions to get a meaningful Quality Score, so don’t be too hasty about deleting newly added keywords.
How to use the script
In Google Ads, go to Bulk Actions, then choose Scripts to go to the Scripts page. Click on the big “+” button to create a new one, and paste in the script.
Don’t forget to edit the following options
- EMAIL_ADDRESSES is a list of the email addresses that will be alerted of the low Quality Score keywords. These should be a comma-separated list inside square brackets. For example, [“alice@example.com” and “bob@example.co.uk”].
- QS_THRESHOLD is the Quality Score value that the script will consider as “low” as defined by you.
- If you want the low Quality Score keywords to be automatically labeled, then set LABEL_KEYWORDS to true and put the name of the label in LOW_QS_LABEL_NAME
- Set PAUSE_KEYWORDS to true if you want the low Quality Score keywords to be automatically paused.
/** |
* |
* Low Quality Score Alert |
* |
* This script finds the low QS keywords (determined by a user defined threshold) |
* and sends an email listing them. Optionally it also labels and/or pauses the |
* keywords. |
* |
* Version: 1.0 |
* Google AdWords Script maintained on brainlabsdigital.com |
* |
**/ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// |
//Options |
var EMAIL_ADDRESSES = [“alice@example.com“, “bob@example.co.uk“]; |
// The address or addresses that will be emailed a list of low QS keywords |
// eg [“alice@example.com”, “bob@example.co.uk”] or [“eve@example.org”] |
var QS_THRESHOLD = 3; |
// Keywords with quality score less than or equal to this number are |
// considered ‘low QS’ |
var LABEL_KEYWORDS = true; |
// If this is true, low QS keywords will be automatically labelled |
var LOW_QS_LABEL_NAME = “Low QS Keyword“; |
// The name of the label applied to low QS keywords |
var PAUSE_KEYWORDS = false; |
// If this is true, low QS keywords will be automatically paused |
// Set to false if you want them to stay active. |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// |
// Functions |
function main() { |
Logger.log(“Pause Keywords: “ + PAUSE_KEYWORDS); |
Logger.log(“Label Keywords: “ + LABEL_KEYWORDS); |
var keywords = findKeywordsWithQSBelow(QS_THRESHOLD); |
Logger.log(“Found “ + keywords.length + “ keywords with low quality score“); |
if (!labelExists(LOW_QS_LABEL_NAME)) { |
Logger.log(Utilities.formatString(‘Creating label: “%s”‘, LOW_QS_LABEL_NAME)); |
AdWordsApp.createLabel(LOW_QS_LABEL_NAME, ‘Automatically created by QS Alert‘, ‘red‘); |
} |
var mutations = [ |
{ |
enabled: PAUSE_KEYWORDS, |
callback: function (keyword) { |
keyword.pause(); |
} |
}, |
{ |
enabled: LABEL_KEYWORDS, |
callback: function (keyword, currentLabels) { |
if (currentLabels.indexOf(LOW_QS_LABEL_NAME) === –1) { |
keyword.applyLabel(LOW_QS_LABEL_NAME); |
} |
} |
} |
]; |
var chunkSize = 10000; |
var chunkedKeywords = chunkList(keywords, chunkSize); |
Logger.log(“Making changes to keywords..“); |
chunkedKeywords.forEach(function (keywordChunk) { |
mutateKeywords(keywordChunk, mutations); |
}); |
if (keywords.length > 0) { |
sendEmail(keywords); |
Logger.log(“Email sent.“); |
} else { |
Logger.log(“No email to send.“); |
} |
} |
function findKeywordsWithQSBelow(threshold) { |
var query = ‘SELECT Id, AdGroupId, CampaignName, AdGroupName, Criteria, QualityScore, Labels‘ |
+ ‘ FROM KEYWORDS_PERFORMANCE_REPORT WHERE Status = “ENABLED” AND CampaignStatus = “ENABLED” AND AdGroupStatus = “ENABLED”‘ |
+ ‘ AND HasQualityScore = “TRUE” AND QualityScore <= ‘ + threshold; |
var report = AdWordsApp.report(query); |
var rows = report.rows(); |
var lowQSKeywords = []; |
while (rows.hasNext()) { |
var row = rows.next(); |
var lowQSKeyword = { |
campaignName: row[‘CampaignName‘], |
adGroupName: row[‘AdGroupName‘], |
keywordText: row[‘Criteria‘], |
labels: (row[‘Labels‘].trim() === ‘—‘) ? [] : JSON.parse(row[‘Labels‘]), |
uniqueId: [row[‘AdGroupId‘], row[‘Id‘]], |
qualityScore: row[‘QualityScore‘] |
}; |
lowQSKeywords.push(lowQSKeyword); |
} |
return lowQSKeywords; |
} |
function labelExists(labelName) { |
var condition = Utilities.formatString(‘LabelName = “%s”‘, labelName); |
return AdWordsApp.labels().withCondition(condition).get().hasNext(); |
} |
function chunkList(list, chunkSize) { |
var chunks = []; |
for (var i = 0; i < list.length; i += chunkSize) { |
chunks.push(list.slice(i, i + chunkSize)); |
} |
return chunks; |
} |
function mutateKeywords(keywords, mutations) { |
var keywordIds = keywords.map(function (keyword) { |
return keyword[‘uniqueId‘]; |
}); |
var mutationsToApply = getMutationsToApply(mutations); |
var adwordsKeywords = AdWordsApp.keywords().withIds(keywordIds).get(); |
var i = 0; |
while (adwordsKeywords.hasNext()) { |
var currentKeywordLabels = keywords[i][‘labels‘]; |
var adwordsKeyword = adwordsKeywords.next(); |
mutationsToApply.forEach(function(mutate) { |
mutate(adwordsKeyword, currentKeywordLabels); |
}); |
i++; |
} |
} |
function getMutationsToApply(mutations) { |
var enabledMutations = mutations.filter(function (mutation) { |
return mutation[‘enabled‘]; |
}); |
return enabledMutations.map(function (condition) { |
return condition[‘callback‘]; |
}); |
} |
function sendEmail(keywords) { |
var subject = “Low Quality Keywords Paused“; |
var htmlBody = |
“<p>Keywords with a quality score of less than “ + QS_THRESHOLD + “found.<p>“ |
+ “<p>Actions Taken:<p>“ |
+ “<ul>“ |
+ “<li><b>Paused</b>: “ + PAUSE_KEYWORDS + “</li>“ |
+ “<li><b>Labelled</b> with <code>“ + LOW_QS_LABEL_NAME + “</code>: “ + LABEL_KEYWORDS + “</li>“ |
+ “</ul>“ |
+ renderTable(keywords); |
MailApp.sendEmail({ |
to: EMAIL_ADDRESSES.join(“,“), |
subject: subject, |
htmlBody: htmlBody |
}); |
} |
function renderTable(keywords) { |
var header = ‘<table border=”2″ cellspacing=”0″ cellpadding=”6″ rules=”groups” frame=”hsides”>‘ |
+ ‘<thead><tr>‘ |
+ ‘<th>Campaign Name</th>‘ |
+ ‘<th>Ad Group Name</th>‘ |
+ ‘<th>Keyword Text</th>‘ |
+ ‘<th>Quality Score</th>‘ |
+ ‘</tr></thead><tbody>‘; |
var rows = keywords.reduce(function(accumulator, keyword) { |
return accumulator |
+ ‘<tr><td>‘ + [ |
keyword[‘campaignName‘], |
keyword[‘adGroupName‘], |
keyword[‘keywordText‘], |
keyword[‘qualityScore‘] |
].join(‘</td><td>‘) |
+ ‘</td></tr>‘; |
}, ““); |
var footer = ‘</tbody></table>‘; |
var table = header + rows + footer; |
return table; |
} |
Want more info on Paid Search? Check out our comprehensive PPC Guide – Nine chapters covering everything from account setup to automation and bid adjustments!