Integrating Google Analytics with Google Ads
Learn how to effectively integrate Google Analytics with Google Ads for better tracking and optimization of your campaigns.
Published: 2023-08-15
By: Michael Mares
Improve your Google Ads performance with this powerful script that automatically monitors quality scores and provides actionable recommendations for improvement.
Quality Score is one of the most important metrics in Google Ads, directly affecting your ad positions and costs per click. However, monitoring quality scores across a large account and identifying opportunities for improvement can be challenging. This script automates the process by tracking quality scores over time and providing actionable recommendations.
This script analyzes your account’s quality scores, identifies low-performing keywords, tracks changes over time, and provides specific recommendations for improvement based on the component scores.
/**
* Quality Score Monitoring and Improvement Script for Google Ads
*
* This script tracks quality scores over time and provides recommendations
* for improving low-performing keywords.
*/
// Configuration
var CONFIG = {
// Spreadsheet URL for tracking quality scores over time
// Create a blank Google Sheet and paste its URL here
spreadsheetUrl: 'YOUR_SPREADSHEET_URL',
// Quality score thresholds
lowQualityScoreThreshold: 5,
highQualityScoreThreshold: 7,
// Minimum impressions for a keyword to be considered
minImpressions: 100,
// Campaigns to exclude (exact match)
excludeCampaigns: ['Brand Campaign'],
// Only include campaigns with these labels (leave empty for all campaigns)
campaignLabels: [],
// Email notification settings
sendEmail: true,
emailAddresses: 'your.email@example.com'
};
// Component score mappings
var COMPONENT_SCORES = {
'1': 'Below average',
'2': 'Average',
'3': 'Above average'
};
function main() {
// Initialize spreadsheet for tracking
var spreadsheet = initializeSpreadsheet();
// Get current date for tracking
var today = new Date();
var dateString = Utilities.formatDate(today, AdsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd');
// Get quality score data
var qualityScoreData = getQualityScoreData();
// Calculate account-level metrics
var accountMetrics = calculateAccountMetrics(qualityScoreData);
// Record data in spreadsheet
recordQualityScoreData(spreadsheet, dateString, qualityScoreData, accountMetrics);
// Generate recommendations
var recommendations = generateRecommendations(qualityScoreData);
// Send email report if enabled
if (CONFIG.sendEmail) {
sendEmailReport(accountMetrics, recommendations);
}
Logger.log('Script completed. Analyzed ' + Object.keys(qualityScoreData).length + ' keywords.');
Logger.log('Account average quality score: ' + accountMetrics.avgQualityScore.toFixed(2));
}
function initializeSpreadsheet() {
var spreadsheet = SpreadsheetApp.openByUrl(CONFIG.spreadsheetUrl);
// Check if sheets exist, create if not
var sheets = {
'Summary': ['Date', 'Avg QS', 'Low QS %', 'Med QS %', 'High QS %', 'Impressions'],
'Keywords': ['Date', 'Campaign', 'Ad Group', 'Keyword', 'Match Type', 'Quality Score',
'Exp. CTR', 'Ad Relevance', 'Landing Page Exp.', 'Impressions', 'Clicks', 'CTR', 'Avg. CPC'],
'Recommendations': ['Date', 'Campaign', 'Ad Group', 'Keyword', 'Quality Score', 'Issue', 'Recommendation']
};
Object.keys(sheets).forEach(sheetName => {
var sheet = spreadsheet.getSheetByName(sheetName);
if (!sheet) {
sheet = spreadsheet.insertSheet(sheetName);
sheet.appendRow(sheets[sheetName]);
sheet.getRange(1, 1, 1, sheets[sheetName].length).setFontWeight('bold');
}
});
return spreadsheet;
}
function getQualityScoreData() {
var qualityScoreData = {};
// Build report query
var report = AdsApp.report(
'SELECT CampaignName, AdGroupName, Criteria, KeywordMatchType, QualityScore, ' +
'SearchPredictedCtr, CreativeQualityScore, PostClickQualityScore, ' +
'Impressions, Clicks, Ctr, AverageCpc ' +
'FROM KEYWORDS_PERFORMANCE_REPORT ' +
'WHERE Status = ENABLED ' +
'AND Impressions > ' + CONFIG.minImpressions + ' ' +
'DURING LAST_30_DAYS'
);
// Process report data
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var campaignName = row['CampaignName'];
// Skip excluded campaigns
if (CONFIG.excludeCampaigns.indexOf(campaignName) !== -1) {
continue;
}
var keywordText = row['Criteria'];
var adGroupName = row['AdGroupName'];
var matchType = row['KeywordMatchType'];
var qualityScore = parseInt(row['QualityScore'], 10);
var expectedCtr = row['SearchPredictedCtr'];
var adRelevance = row['CreativeQualityScore'];
var landingPageExp = row['PostClickQualityScore'];
var impressions = parseInt(row['Impressions'], 10);
var clicks = parseInt(row['Clicks'], 10);
var ctr = parseFloat(row['Ctr'].replace(/%/g, ''));
var avgCpc = parseFloat(row['AverageCpc'].replace(/,/g, ''));
// Create unique key for the keyword
var key = campaignName + '|' + adGroupName + '|' + keywordText + '|' + matchType;
qualityScoreData[key] = {
campaign: campaignName,
adGroup: adGroupName,
keyword: keywordText,
matchType: matchType,
qualityScore: qualityScore,
expectedCtr: expectedCtr,
adRelevance: adRelevance,
landingPageExp: landingPageExp,
impressions: impressions,
clicks: clicks,
ctr: ctr,
avgCpc: avgCpc
};
}
return qualityScoreData;
}
function calculateAccountMetrics(qualityScoreData) {
var totalKeywords = Object.keys(qualityScoreData).length;
var totalQualityScore = 0;
var totalImpressions = 0;
var lowQSCount = 0;
var medQSCount = 0;
var highQSCount = 0;
Object.values(qualityScoreData).forEach(data => {
totalQualityScore += data.qualityScore;
totalImpressions += data.impressions;
if (data.qualityScore <= CONFIG.lowQualityScoreThreshold) {
lowQSCount++;
} else if (data.qualityScore >= CONFIG.highQualityScoreThreshold) {
highQSCount++;
} else {
medQSCount++;
}
});
return {
totalKeywords: totalKeywords,
avgQualityScore: totalKeywords > 0 ? totalQualityScore / totalKeywords : 0,
totalImpressions: totalImpressions,
lowQSPercent: totalKeywords > 0 ? (lowQSCount / totalKeywords) * 100 : 0,
medQSPercent: totalKeywords > 0 ? (medQSCount / totalKeywords) * 100 : 0,
highQSPercent: totalKeywords > 0 ? (highQSCount / totalKeywords) * 100 : 0
};
}
function recordQualityScoreData(spreadsheet, dateString, qualityScoreData, accountMetrics) {
// Record summary data
var summarySheet = spreadsheet.getSheetByName('Summary');
summarySheet.appendRow([
dateString,
accountMetrics.avgQualityScore.toFixed(2),
accountMetrics.lowQSPercent.toFixed(2) + '%',
accountMetrics.medQSPercent.toFixed(2) + '%',
accountMetrics.highQSPercent.toFixed(2) + '%',
accountMetrics.totalImpressions
]);
// Record keyword data
var keywordsSheet = spreadsheet.getSheetByName('Keywords');
Object.values(qualityScoreData).forEach(data => {
keywordsSheet.appendRow([
dateString,
data.campaign,
data.adGroup,
data.keyword,
data.matchType,
data.qualityScore,
data.expectedCtr,
data.adRelevance,
data.landingPageExp,
data.impressions,
data.clicks,
data.ctr.toFixed(2) + '%',
data.avgCpc.toFixed(2)
]);
});
}
function generateRecommendations(qualityScoreData) {
var recommendations = [];
Object.values(qualityScoreData).forEach(data => {
// Only generate recommendations for low quality score keywords
if (data.qualityScore <= CONFIG.lowQualityScoreThreshold) {
var issues = [];
var recs = [];
// Check expected CTR
if (data.expectedCtr === 'Below average') {
issues.push('Low expected CTR');
recs.push('Improve ad copy relevance, use more compelling calls-to-action, and consider adding the keyword in the headline');
}
// Check ad relevance
if (data.adRelevance === 'Below average') {
issues.push('Low ad relevance');
recs.push('Ensure the keyword appears in your ad text, especially in headlines, and align ad messaging with search intent');
}
// Check landing page experience
if (data.landingPageExp === 'Below average') {
issues.push('Poor landing page experience');
recs.push('Improve page load speed, ensure keyword relevance on landing page, and enhance mobile experience');
}
// If all components are average or above but QS is still low
if (issues.length === 0 && data.qualityScore <= 4) {
issues.push('Generally low quality score despite average components');
recs.push('Consider restructuring this keyword into a more tightly themed ad group or try different match types');
}
// Add recommendations if issues were found
if (issues.length > 0) {
recommendations.push({
campaign: data.campaign,
adGroup: data.adGroup,
keyword: data.keyword,
matchType: data.matchType,
qualityScore: data.qualityScore,
issues: issues.join('; '),
recommendations: recs.join('; '),
impressions: data.impressions
});
}
}
});
// Sort recommendations by impressions (highest first) to prioritize high-impact keywords
recommendations.sort((a, b) => b.impressions - a.impressions);
// Record recommendations in spreadsheet
var spreadsheet = SpreadsheetApp.openByUrl(CONFIG.spreadsheetUrl);
var recsSheet = spreadsheet.getSheetByName('Recommendations');
var today = new Date();
var dateString = Utilities.formatDate(today, AdsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd');
recommendations.forEach(rec => {
recsSheet.appendRow([
dateString,
rec.campaign,
rec.adGroup,
rec.keyword,
rec.qualityScore,
rec.issues,
rec.recommendations
]);
});
return recommendations;
}
function sendEmailReport(accountMetrics, recommendations) {
var accountName = AdsApp.currentAccount().getName();
var subject = 'Google Ads Quality Score Report - ' + accountName;
var body = '<h2>Quality Score Report for ' + accountName + '</h2>';
// Account summary
body += '<h3>Account Summary</h3>';
body += '<table border="1" cellpadding="3" style="border-collapse: collapse;">';
body += '<tr><th>Metric</th><th>Value</th></tr>';
body += '<tr><td>Average Quality Score</td><td>' + accountMetrics.avgQualityScore.toFixed(2) + '</td></tr>';
body += '<tr><td>Low QS Keywords (1-5)</td><td>' + accountMetrics.lowQSPercent.toFixed(2) + '%</td></tr>';
body += '<tr><td>Medium QS Keywords (6-7)</td><td>' + accountMetrics.medQSPercent.toFixed(2) + '%</td></tr>';
body += '<tr><td>High QS Keywords (8-10)</td><td>' + accountMetrics.highQSPercent.toFixed(2) + '%</td></tr>';
body += '<tr><td>Total Keywords Analyzed</td><td>' + accountMetrics.totalKeywords + '</td></tr>';
body += '</table>';
// Top recommendations
var topRecs = recommendations.slice(0, Math.min(10, recommendations.length));
if (topRecs.length > 0) {
body += '<h3>Top ' + topRecs.length + ' Recommendations (by Impression Volume)</h3>';
body += '<table border="1" cellpadding="3" style="border-collapse: collapse;">';
body += '<tr><th>Campaign</th><th>Ad Group</th><th>Keyword</th><th>QS</th><th>Issues</th><th>Recommendations</th></tr>';
topRecs.forEach(rec => {
body += '<tr>';
body += '<td>' + rec.campaign + '</td>';
body += '<td>' + rec.adGroup + '</td>';
body += '<td>' + rec.keyword + ' (' + rec.matchType + ')</td>';
body += '<td>' + rec.qualityScore + '</td>';
body += '<td>' + rec.issues + '</td>';
body += '<td>' + rec.recommendations + '</td>';
body += '</tr>';
});
body += '</table>';
} else {
body += '<p>No recommendations generated. All keywords have quality scores above the threshold.</p>';
}
// Link to spreadsheet
body += '<p>View the full report in the <a href="' + CONFIG.spreadsheetUrl + '">Google Sheet</a>.</p>';
MailApp.sendEmail({
to: CONFIG.emailAddresses,
subject: subject,
htmlBody: body
});
}
The script creates three sheets in your Google Spreadsheet:
The email report highlights your account’s overall quality score health and provides actionable recommendations for your highest-impression, low-quality keywords.
Improving quality scores can have a significant impact on your account:
You can extend this script to:
Have you struggled with quality score issues in your Google Ads account? What strategies have worked best for you? Let me know in the comments!
Learn how to effectively integrate Google Analytics with Google Ads for better tracking and optimization of your campaigns.
Learn how to effectively target and segment your audience in Google Ads to improve campaign performance and ROI.
Explore advanced automation techniques for Google Ads to scale your campaigns and improve efficiency.
Learn the fundamentals of Google Ads automation and how to implement basic automation rules for better campaign management.
Maximize your Google Ads ROI with this automated budget allocation script that dynamically shifts budget from underperforming to high-performing campaigns.
Learn advanced strategies for managing your Google Ads budget effectively to maximize ROI and campaign performance.