Google Ads Scripts That Actually Protect Your Budget
A client came to me in June after discovering their top campaign had been sending traffic to a 404 page for eleven days. The ad was active, the budget was spending, and the landing page had been quietly removed during a site migration. By the time they noticed, roughly EUR 4,200 was gone with zero conversions to show for it.
A single Google Ads script -- five minutes to set up -- would have caught it on day one.
Google Ads scripts are JavaScript-based automations that run directly inside your account. They can read campaign data, check URLs, send email alerts, and make changes on a schedule. Google provides the Apps Script environment built into every account. You do not need a developer. You need the right scripts and ten minutes.
What follows are five scripts I set up on nearly every account I touch. Each one connects to a tracking problem I see repeatedly in audits -- quiet failures that erode data quality without anyone noticing. If you are not sure whether your tracking is solid, start with an audit and then layer these on top.
1. Budget Overspend Guard
Google can spend up to twice your daily budget on any given day, as long as the monthly average stays within your limit. For accounts with strict daily caps -- pilot campaigns, limited budgets, seasonal promotions -- a surprise 2x day can blow through the allocation before you wake up.
This script checks spend against a threshold and sends an email alert when it crosses.
function main() {
var DAILY_LIMIT = 500; // EUR
var EMAIL = 'you@example.com';
var today = Utilities.formatDate(new Date(), 'Europe/Berlin', 'yyyyMMdd');
var report = AdsApp.report(
'SELECT metrics.cost_micros FROM customer WHERE segments.date = "' + today + '"'
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var cost = row['metrics.cost_micros'] / 1e6;
if (cost > DAILY_LIMIT) {
MailApp.sendEmail(EMAIL,
'Google Ads overspend alert',
'Spend today: ' + cost.toFixed(2) + ' EUR. Limit: ' + DAILY_LIMIT + ' EUR.');
}
}
}
Schedule it hourly. For MCC-level monitoring across multiple accounts, Google Ads MCC scripts let you iterate through child accounts using AdsManagerApp.accounts() and run the same check across your entire portfolio.
I typically set the threshold at 80 percent of the daily budget rather than 100 percent -- it gives you time to react before the full budget is consumed.
2. Broken URL and Landing Page Checker
The 404 scenario above is not rare. Sites get restructured, product pages expire, redirects break. Google Ads will keep sending paid clicks to a dead page. The ad stays approved. Quality score may eventually drop, but by then you have already wasted budget.
This script pulls every final URL from active ads, fetches each one, and flags anything that does not return HTTP 200.
function main() {
var EMAIL = 'you@example.com';
var issues = [];
var adIterator = AdsApp.ads()
.withCondition('Status = ENABLED')
.withCondition('CampaignStatus = ENABLED')
.withCondition('AdGroupStatus = ENABLED')
.get();
while (adIterator.hasNext()) {
var ad = adIterator.next();
var url = ad.urls().getFinalUrl();
if (!url) continue;
try {
var response = UrlFetchApp.fetch(url, {muteHttpExceptions: true, followRedirects: true});
var code = response.getResponseCode();
if (code !== 200) {
issues.push(ad.getCampaign().getName() + ' | ' + url + ' | HTTP ' + code);
}
} catch (e) {
issues.push(ad.getCampaign().getName() + ' | ' + url + ' | Error: ' + e.message);
}
}
if (issues.length > 0) {
MailApp.sendEmail(EMAIL,
'Broken landing pages in Google Ads',
issues.join('\n'));
}
}
Run it daily. Accounts with hundreds of ads should schedule it during off-peak hours because each URL fetch counts against your script execution limits. For large accounts, filter by campaign label to stay within limits.
One note: this checks the final URL as stored in the ad, not the post-click URL after tracking template redirects. If your templates introduce redirect breaks, you need the validator in script five.
3. Conversion Tracking Anomaly Detector
This is the script I wish every account had before I get called in. Conversion tracking breaks silently. A developer pushes a site update that removes the data layer push. A GTM container version goes live with a misconfigured trigger. Enhanced conversions get toggled off by someone who did not know what it was. The conversions stop, but nobody notices because the campaigns keep running and the dashboards still show clicks.
This script compares the last seven days of conversion data to the prior seven days and alerts you when the conversion rate swings beyond a threshold you define.
function main() {
var THRESHOLD = 0.30; // alert on 30%+ change
var EMAIL = 'you@example.com';
var today = new Date();
var fmt = function(d) { return Utilities.formatDate(d, 'Europe/Berlin', 'yyyyMMdd'); };
var daysAgo = function(n) { var d = new Date(today); d.setDate(d.getDate() - n); return d; };
var current = getStats(fmt(daysAgo(7)), fmt(daysAgo(1)));
var previous = getStats(fmt(daysAgo(14)), fmt(daysAgo(8)));
if (previous.convRate === 0) return;
var change = (current.convRate - previous.convRate) / previous.convRate;
if (Math.abs(change) > THRESHOLD) {
MailApp.sendEmail(EMAIL,
'Conversion rate anomaly detected',
'Current 7d conv rate: ' + (current.convRate * 100).toFixed(2) + '%\n' +
'Previous 7d conv rate: ' + (previous.convRate * 100).toFixed(2) + '%\n' +
'Change: ' + (change * 100).toFixed(1) + '%');
}
}
function getStats(startDate, endDate) {
var report = AdsApp.report(
'SELECT metrics.clicks, metrics.conversions FROM customer ' +
'WHERE segments.date BETWEEN "' + startDate + '" AND "' + endDate + '"'
);
var clicks = 0, conversions = 0;
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
clicks += parseInt(row['metrics.clicks'], 10);
conversions += parseFloat(row['metrics.conversions']);
}
return { convRate: clicks > 0 ? conversions / clicks : 0 };
}
I set the threshold at 30 percent for most accounts. Smaller accounts with low conversion volume may need a wider threshold to avoid false positives. The point is not precision -- it is catching the catastrophic drops where conversion tracking dies and the account keeps spending on blind faith.
If you are not confident your conversion tracking is configured correctly in the first place, fix that first. The script detects breaks, but it cannot tell you whether the baseline was accurate.
4. Search Term N-Gram Report
Google has been limiting search term visibility since 2020, hiding terms that do not meet a privacy threshold. Broad match and Performance Max make this worse. But patterns emerge when you aggregate by n-gram (two-word and three-word fragments).
This script pulls search terms, breaks them into bigrams, sums spend and conversions per fragment, and outputs to a Google Sheet. Expensive fragments with zero conversions are candidates for negative keywords.
function main() {
var SHEET_URL = 'https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/';
var DAYS = 30;
var today = new Date();
var startDate = new Date(today); startDate.setDate(today.getDate() - DAYS);
var fmt = function(d) { return Utilities.formatDate(d, 'Europe/Berlin', 'yyyyMMdd'); };
var report = AdsApp.report(
'SELECT search_term_view.search_term, metrics.cost_micros, metrics.conversions ' +
'FROM search_term_view ' +
'WHERE segments.date BETWEEN "' + fmt(startDate) + '" AND "' + fmt(today) + '"'
);
var ngrams = {};
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var words = row['search_term_view.search_term'].toLowerCase().split(/\s+/);
var cost = row['metrics.cost_micros'] / 1e6;
var convs = parseFloat(row['metrics.conversions']);
for (var i = 0; i < words.length - 1; i++) {
var bigram = words[i] + ' ' + words[i + 1];
if (!ngrams[bigram]) ngrams[bigram] = {cost: 0, conversions: 0, count: 0};
ngrams[bigram].cost += cost;
ngrams[bigram].conversions += convs;
ngrams[bigram].count++;
}
}
var ss = SpreadsheetApp.openByUrl(SHEET_URL);
var sheet = ss.getSheetByName('N-Grams') || ss.insertSheet('N-Grams');
sheet.clear();
sheet.appendRow(['N-Gram', 'Occurrences', 'Cost', 'Conversions']);
for (var key in ngrams) {
var d = ngrams[key];
sheet.appendRow([key, d.count, d.cost.toFixed(2), d.conversions.toFixed(1)]);
}
}
Run it weekly or monthly. Sort the output by cost descending, filter for zero conversions, and you have a negative keyword list. If you have been looking for Google Ads scripts Performance Max can actually benefit from, this is the one -- it is one of the few ways to get search-level insight -- Performance Max does not surface search terms in the standard UI, but search term reports are partially available via scripts.
I wrote about the cost inputs problem in ROAS Calculation: The Formula Is Easy, Your Inputs Are Wrong. Wasted spend on irrelevant terms is one of the most common reasons the denominator in your ROAS formula is bigger than it needs to be.
5. Tracking Template and UTM Validator
Inconsistent tracking templates are one of the most overlooked sources of dirty data. One campaign launches with utm_source=google, another uses utm_source=Google_Ads. The result: fragmented source/medium data in GA4 and broken attribution.
This script iterates through active campaigns and flags any tracking template that does not match a pattern you define.
function main() {
var REQUIRED_PATTERN = /utm_source=google/i;
var EMAIL = 'you@example.com';
var issues = [];
var campaignIterator = AdsApp.campaigns()
.withCondition('Status = ENABLED')
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var template = campaign.urls().getTrackingTemplate() || '';
if (template && !REQUIRED_PATTERN.test(template)) {
issues.push(campaign.getName() + ' | Template: ' + template);
}
if (!template) {
issues.push(campaign.getName() + ' | No tracking template set');
}
}
if (issues.length > 0) {
MailApp.sendEmail(EMAIL,
'Tracking template issues in Google Ads',
'The following campaigns have non-standard or missing tracking templates:\n\n' +
issues.join('\n'));
}
}
This is critical for agencies managing multiple accounts. When different people create campaigns, templates drift. One sends utm_medium=cpc, another utm_medium=paid_search, and GA4 splits a single channel into fragments. I covered the downstream consequences in Google Analytics UTM Parameters: Naming Conventions.
Schedule this weekly. Adjust REQUIRED_PATTERN to match your convention. For MCC accounts, wrap the logic in AdsManagerApp.accounts().executeInParallel() to validate templates across all managed accounts -- a practical use case for Google Ads MCC scripts.
Setting Up Google Ads Scripts: Practical Notes
A few things I have learned from running these across dozens of accounts:
Scheduling matters. The budget guard runs hourly. The URL checker and anomaly detector run daily. The n-gram and UTM scripts run weekly. Match the cadence to the failure mode.
Email alerts are the minimum. For best Google Ads scripts automation, write results to a Google Sheet with timestamps. Sheets give you a historical log and keep the whole team informed.
Test before scheduling. Use the Preview button in the scripts editor to run each script once and verify the output. Make sure the script completes within the 30-minute execution limit.
Scripts do not replace proper tracking setup. These are guardrails, not foundations. If your conversion tracking is misconfigured, a script monitoring conversion rate changes is monitoring a broken metric. Fix the foundation first.
What These Scripts Cannot Do
Google Ads scripts are powerful for monitoring and alerting, but they have limits. They cannot fix your consent mode, set up server-side tagging, or configure enhanced conversions. What they can do is catch problems early -- before they compound into weeks of wasted spend.
If you want free Google Ads scripts beyond these five, Google maintains a solutions library with additional scripts for bid management and reporting. The five above are my picks because they directly protect measurement quality -- the layer where most budget waste actually originates.
FAQ
Are Google Ads scripts free to use?
Yes. Google Ads scripts are built into every Google Ads account at no extra cost. You access them under Tools and Settings in the Scripts section. The only limits are execution time (30 minutes per run) and URL fetch quotas, which are generous for most use cases.
Do Google Ads scripts work with Performance Max campaigns?
Partially. Scripts can read Performance Max campaign metrics like spend, conversions, and cost per conversion. However, you cannot modify asset groups or audience signals via scripts. Search term reports for Performance Max are also limited compared to standard Search campaigns.
Can I run Google Ads scripts across multiple accounts in an MCC?
Yes. MCC-level scripts use AdsManagerApp to iterate through child accounts. You can run the same monitoring logic across dozens of accounts in a single scheduled execution, which makes them practical for agencies and consultants managing portfolios.
Will Google Ads scripts conflict with Smart Bidding?
The scripts in this article are read-only monitors. They check data and send alerts but do not change bids or budgets. As long as your scripts do not modify bidding settings, they will not interfere with Smart Bidding strategies like Target CPA or Target ROAS.
How often should I run Google Ads scripts?
It depends on the script. Budget monitors should run hourly. Landing page checkers and conversion anomaly detectors work well on a daily schedule. N-gram analysis and tracking template audits are best run weekly or monthly since the patterns they detect develop over longer time periods.
Not sure whether your tracking is giving you numbers you can trust? Book a tracking audit -- I will tell you exactly what is broken and what these scripts should be monitoring.