Production-ready prompts, scripts, frameworks and AI agents for Google Ads professionals. No payment required.
This agent runs on a schedule and compares current auction insights against historical baselines. When a competitor’s metrics shift beyond your configured thresholds, you get an instant alert.
var AGENT_CONFIG = {
// ═══════════════════════════════════════════════════════════════════════════
// MONITORING TARGETS
// ═══════════════════════════════════════════════════════════════════════════
COMPETITORS: [
// Leave empty to track ALL auction participants
// Or specify: ['competitor1.com', 'competitor2.com']
],
CAMPAIGNS_TO_MONITOR: [
// Leave empty for all campaigns
// Or specify campaign names
],
// ═══════════════════════════════════════════════════════════════════════════
// ALERT THRESHOLDS (percentage point change)
// ═══════════════════════════════════════════════════════════════════════════
THRESHOLDS: {
IMPRESSION_SHARE_CHANGE: 10, // Alert if IS changes by ±10pp
OVERLAP_RATE_CHANGE: 15, // Alert if overlap changes by ±15pp
POSITION_ABOVE_CHANGE: 10, // Alert if position above changes ±10pp
OUTRANKING_SHARE_CHANGE: 15, // Alert if outranking changes ±15pp
NEW_COMPETITOR: true, // Alert on new auction participants
COMPETITOR_LEFT: true, // Alert when competitor disappears
},
// ═══════════════════════════════════════════════════════════════════════════
// SCHEDULE & OUTPUT
// ═══════════════════════════════════════════════════════════════════════════
COMPARISON_PERIOD: 'LAST_7_DAYS',
BASELINE_PERIOD: 'LAST_30_DAYS',
SPREADSHEET_URL: 'CREATE_NEW',
SLACK_WEBHOOK_URL: '',
EMAIL_RECIPIENTS: [],
};
function main() {
var baseline = getAuctionData(AGENT_CONFIG.BASELINE_PERIOD);
var current = getAuctionData(AGENT_CONFIG.COMPARISON_PERIOD);
var alerts = detectChanges(baseline, current);
if (alerts.length > 0) {
logAlerts(alerts);
sendNotifications(alerts);
}
Logger.log('Agent complete. ' + alerts.length + ' alerts generated.');
}
function getAuctionData(dateRange) {
var query =
'SELECT Domain, CampaignName, ' +
'AverageImpressionShare, AverageOverlapRate, ' +
'AveragePositionAboveRate, AverageOutrankingShare ' +
'FROM AUCTION_INSIGHTS_REPORT ' +
'DURING ' + dateRange;
var report = AdsApp.report(query);
var rows = report.rows();
var data = {};
while (rows.hasNext()) {
var row = rows.next();
var domain = row['Domain'];
var campaign = row['CampaignName'];
if (AGENT_CONFIG.COMPETITORS.length > 0 &&
AGENT_CONFIG.COMPETITORS.indexOf(domain) === -1) {
continue;
}
if (AGENT_CONFIG.CAMPAIGNS_TO_MONITOR.length > 0 &&
AGENT_CONFIG.CAMPAIGNS_TO_MONITOR.indexOf(campaign) === -1) {
continue;
}
var key = domain + '|' + campaign;
data[key] = {
domain: domain,
campaign: campaign,
impressionShare: parseFloat(row['AverageImpressionShare']),
overlapRate: parseFloat(row['AverageOverlapRate']),
positionAbove: parseFloat(row['AveragePositionAboveRate']),
outrankingShare: parseFloat(row['AverageOutrankingShare']),
};
}
return data;
}
function detectChanges(baseline, current) {
var alerts = [];
var th = AGENT_CONFIG.THRESHOLDS;
// Check existing competitors for changes
Object.keys(current).forEach(function(key) {
var cur = current[key];
var base = baseline[key];
if (!base) {
if (th.NEW_COMPETITOR) {
alerts.push({
type: 'NEW_COMPETITOR',
domain: cur.domain,
campaign: cur.campaign,
message: cur.domain + ' appeared in auction for "' + cur.campaign + '"' +
' with ' + cur.impressionShare.toFixed(1) + '% impression share',
severity: 'high',
});
}
return;
}
var checks = [
{ metric: 'impressionShare', name: 'Impression Share', threshold: th.IMPRESSION_SHARE_CHANGE },
{ metric: 'overlapRate', name: 'Overlap Rate', threshold: th.OVERLAP_RATE_CHANGE },
{ metric: 'positionAbove', name: 'Position Above Rate', threshold: th.POSITION_ABOVE_CHANGE },
{ metric: 'outrankingShare', name: 'Outranking Share', threshold: th.OUTRANKING_SHARE_CHANGE },
];
checks.forEach(function(check) {
var diff = cur[check.metric] - base[check.metric];
if (Math.abs(diff) >= check.threshold) {
var direction = diff > 0 ? 'increased' : 'decreased';
alerts.push({
type: 'METRIC_CHANGE',
domain: cur.domain,
campaign: cur.campaign,
message: cur.domain + ' ' + check.name + ' ' + direction +
' by ' + Math.abs(diff).toFixed(1) + 'pp in "' + cur.campaign + '"' +
' (' + base[check.metric].toFixed(1) + '% → ' + cur[check.metric].toFixed(1) + '%)',
severity: Math.abs(diff) >= check.threshold * 2 ? 'high' : 'medium',
});
}
});
});
// Check for competitors that disappeared
if (th.COMPETITOR_LEFT) {
Object.keys(baseline).forEach(function(key) {
if (!current[key]) {
var base = baseline[key];
alerts.push({
type: 'COMPETITOR_LEFT',
domain: base.domain,
campaign: base.campaign,
message: base.domain + ' disappeared from auction for "' + base.campaign + '"',
severity: 'medium',
});
}
});
}
return alerts;
}
function sendNotifications(alerts) {
var highAlerts = alerts.filter(function(a) { return a.severity === 'high'; });
var summary = alerts.length + ' competitor changes detected' +
(highAlerts.length > 0 ? ' (' + highAlerts.length + ' high severity)' : '');
// Email
if (AGENT_CONFIG.EMAIL_RECIPIENTS.length > 0) {
var body = summary + '\n\n';
alerts.forEach(function(a) {
body += '[' + a.severity.toUpperCase() + '] ' + a.message + '\n';
});
AGENT_CONFIG.EMAIL_RECIPIENTS.forEach(function(email) {
MailApp.sendEmail(email, 'Competitor Alert: ' + summary, body);
});
}
// Slack
if (AGENT_CONFIG.SLACK_WEBHOOK_URL) {
var blocks = alerts.map(function(a) {
var emoji = a.severity === 'high' ? '🔴' : '🟡';
return emoji + ' ' + a.message;
});
UrlFetchApp.fetch(AGENT_CONFIG.SLACK_WEBHOOK_URL, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({ text: '*' + summary + '*\n\n' + blocks.join('\n') }),
});
}
}
function logAlerts(alerts) {
var ss;
if (AGENT_CONFIG.SPREADSHEET_URL === 'CREATE_NEW') {
ss = SpreadsheetApp.create('Competitor Alerts ' + new Date().toISOString().split('T')[0]);
Logger.log('Log: ' + ss.getUrl());
} else {
ss = SpreadsheetApp.openByUrl(AGENT_CONFIG.SPREADSHEET_URL);
}
var sheet = ss.getActiveSheet();
if (sheet.getLastRow() === 0) {
sheet.appendRow(['Date', 'Severity', 'Type', 'Domain', 'Campaign', 'Details']);
}
var now = new Date().toISOString();
alerts.forEach(function(a) {
sheet.appendRow([now, a.severity, a.type, a.domain, a.campaign, a.message]);
});
}
| Type | Trigger | Default Threshold |
|---|---|---|
IMPRESSION_SHARE_CHANGE | Competitor IS shifts | ±10 percentage points |
OVERLAP_RATE_CHANGE | Auction overlap changes | ±15pp |
POSITION_ABOVE_CHANGE | Above-you rate changes | ±10pp |
OUTRANKING_SHARE_CHANGE | Outranking share shifts | ±15pp |
NEW_COMPETITOR | New domain in auction | Any appearance |
COMPETITOR_LEFT | Domain disappears | Any disappearance |
Run daily for timely alerts. The agent compares the last 7 days against the 30-day baseline to detect meaningful shifts while filtering out daily noise.