Google Ads Scripts are not a replacement for Smart Bidding, Performance Max, or human account strategy. Their best role is narrower and more useful: monitoring, QA, exports, anomaly checks, URL checks, search-term review, and reporting workflows that built-in automation may not surface clearly enough on its own.
What you will learn
- Where scripts still help in heavily automated Google Ads accounts.
- Which monitoring jobs are safer as scripts than as account-changing automation.
- How to install starter scripts with DRY_RUN, QA notes, and simple rollback thinking.
- When to use scripts, API jobs, or manual audits instead.
Why Google Ads Scripts still matter in an automated account
Automation optimizes inside the rules and signals it can already see. Scripts help on a different layer: blind-spot monitoring, exports, alerting, and structured QA.
That is why the strongest script use cases are usually read-only. They help a human see issues faster rather than changing bids, budgets, or campaign state without review.
What scripts can and cannot do safely
Scripts are well suited to:
- reporting exports,
- budget alerts,
- search-term review,
- final URL checks,
- PMax and AI Max QA support,
- anomaly detection,
- spreadsheet logging.
Scripts are not the safest first tool for:
- bulk bid changes,
- automatic budget shifts,
- campaign pausing at scale,
- negative keyword insertion without review,
- asset deletion,
- account-wide mutation without dry-run and rollback logic.
Before you install any script: permissions, DRY_RUN, and QA
Start with a copy of the account or a narrow campaign scope where possible. Keep scripts read-only by default, and if a script writes to Google Sheets or sends emails, validate the output format before scheduling it.
DRY_RUN first
If a script could change anything, start with `DRY_RUN = true`. In this article, the starter scripts are read-only or export-only, and any write behavior is limited to logging or Sheets output.
Safe placeholders only
Use placeholders like `YOUR_SPREADSHEET_URL` and `ALERT_EMAIL@invalid.local`. Do not paste real customer IDs, secrets, or live notification addresses into shared code without review.
Script summary table
| Script | Problem it catches | Main data source | Output | Safe default | Suggested cadence |
|---|---|---|---|---|---|
| Budget Burn Alert | Campaigns spending too fast against daily budget | Campaign stats | Logger or email summary | Read-only | Daily |
| Search Term N-Gram Waste Miner | Repeated waste patterns in search terms | Search terms report | Google Sheets or Logger rows | Read-only | Weekly |
| Broken Final URL Checker | Landing pages returning errors or redirects | Final URLs + UrlFetchApp | Error list | Read-only | Daily |
| Zombie Product or Low-Activity Item Hunter | Shopping or feed items with spend but weak activity signals | Shopping performance view | Sheet or log rows | Read-only | Weekly |
| Auction Pressure Monitor | Share-loss and auction pressure shifts | Campaign metrics | Alert summary | Read-only | Weekly |
| Conversion Action Sanity Reporter | Stale, duplicate, or mis-prioritized conversion actions | Conversion action metadata | Review list | Read-only | Weekly |
| Performance Max Asset and Feed Coverage Reporter | Missing or thin PMax coverage signals | PMax campaign and asset/feed checks | Coverage review list | Starter template | Weekly |
| AI Max Final URL and Tracking-Template Risk Checker | URL expansion or tracking-template mismatch risk | Campaign URL settings | Risk list | Starter template | Weekly |
| Spend Pacing and Anomaly Alert | Sudden cost spikes or pacing drift | Campaign stats | Alert summary | Read-only | Daily |
| Reporting Export to Google Sheets | Repeated reporting copy-paste | Campaign metrics and spreadsheet export | Structured sheet tab | Export-only | Daily or weekly |
Want the full script pack with dashboard templates and setup notes? Request the Google Ads script pack, or book an account systems audit and I’ll install the right monitoring layer for your account.
Script safety and testing table
| Risk area | What can go wrong | Safe setup rule | QA check | Rollback or stop condition |
|---|---|---|---|---|
| Permissions | Script sees more than intended | Start in one account or narrow scope | Confirm selected entities before run | Stop if scope looks wider than expected |
| DRY_RUN | Script writes before review | Keep read-only or DRY_RUN by default | Inspect logs before any live write | Revert to DRY_RUN and rerun |
| Spreadsheet output | Bad columns or duplicate rows | Write to a test tab first | Check headers and sample rows | Clear test tab and adjust mapping |
| Email alerts | Alert spam or noisy rules | Send to one review inbox first | Review one sample message | Disable alert send until threshold is fixed |
| Account-level changes | Unsafe mutations at scale | Avoid destructive changes in starter scripts | Review mutation lines manually | Remove mutation block entirely |
| MCC scale | Runtime and scope blow up | Start with one account or one label filter | Check runtime and row volume | Split by account or narrow filters |
| Timeouts | Large reports stop mid-run | Limit date range or entity scope | Check logs for partial completion | Reduce scope and rerun |
| Old methods or deprecated assumptions | Script fails silently or returns wrong data | Review current Scripts docs first | Validate field names against docs | Remove unsupported fields or methods |
| False positives | Alert fires on normal variation | Add thresholds and exclusions | Review first few runs manually | Raise thresholds or add filters |
Script 1: Budget Burn Alert
What it catches
Campaigns that are spending unusually fast relative to their daily budget.
Why automation may miss it
Built-in automation can optimize bidding, but it does not always give you a simple operator-facing alert when pacing becomes the main issue.
Setup variables
- alert email
- budget-burn threshold
- label filter if you want to narrow scope
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var ALERT_EMAIL = 'ALERT_EMAIL@invalid.local';
var BURN_RATIO = 0.85;
var rows = [];
var iter = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.get();
while (iter.hasNext()) {
var campaign = iter.next();
var stats = campaign.getStatsFor("TODAY");
var cost = stats.getCost();
var budget = campaign.getBudget().getAmount();
if (!budget) continue;
if (cost / budget >= BURN_RATIO) {
rows.push(campaign.getName() + ' | cost=' + cost + ' | budget=' + budget);
}
}
if (rows.length === 0) {
Logger.log('No campaigns crossed the burn threshold.');
return;
}
var body = rows.join('n');
Logger.log(body);
MailApp.sendEmail(ALERT_EMAIL, 'Budget burn alert', body);
}
Expected output
Sample output format: `Campaign Name | cost=… | budget=…`
Safe test method
Run it once manually and review the log before enabling email delivery.
Scheduling note
Daily is usually enough for this type of alert.
What not to automate
Do not auto-raise budgets from this alert. Keep it as a review signal.
Script 2: Search Term N-Gram Waste Miner
What it catches
Repeated word patterns in search terms that may point to weak query control.
Why automation may miss it
Automation can spend efficiently within campaign logic and still leave a human with poor visibility into recurring waste language.
Setup variables
- date range
- minimum clicks
- minimum cost
- spreadsheet URL
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var DATE_RANGE = 'LAST_30_DAYS';
var MIN_CLICKS = 2;
var MIN_COST = 5;
var report = AdsApp.report(
"SELECT search_term_view.search_term, metrics.clicks, metrics.cost_micros " +
"FROM search_term_view " +
"WHERE segments.date DURING " + DATE_RANGE
);
var counts = {};
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var clicks = Number(row['metrics.clicks']);
var cost = Number(row['metrics.cost_micros']) / 1000000;
if (clicks < MIN_CLICKS || cost < MIN_COST) continue;
var terms = String(row['search_term_view.search_term']).toLowerCase().split(/s+/);
for (var i = 0; i < terms.length; i++) {
var token = terms[i];
if (token.length < 3) continue;
if (!counts[token]) counts[token] = { clicks: 0, cost: 0 };
counts[token].clicks += clicks;
counts[token].cost += cost;
}
}
for (var token in counts) {
Logger.log(token + ' | clicks=' + counts[token].clicks + ' | cost=' + counts[token].cost);
}
}
Expected output
Sample output format: `token | clicks=… | cost=…`
Safe test method
Run on a short date range first and inspect whether the token list contains obvious noise words before exporting it anywhere else.
Scheduling note
Weekly is usually more useful than daily for this review.
What not to automate
Do not auto-create negatives from tokens alone. Human review still matters.
Script 3: Broken Final URL Checker
What it catches
Ads or keywords pointing to URLs that return errors or risky redirects.
Why automation may miss it
You may still have broken landing paths even when campaigns continue to serve and spend.
Setup variables
- HTTP timeout
- max entities per run
- status codes to flag
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var TIMEOUT_MS = 10000;
var MAX_CHECKS = 100;
var checked = 0;
var issues = [];
var iter = AdsApp.keywords().withCondition("Status = ENABLED").get();
while (iter.hasNext() && checked < MAX_CHECKS) {
var keyword = iter.next();
var url = keyword.urls().getFinalUrl();
if (!url) continue;
checked++;
try {
var response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
followRedirects: false
});
var code = response.getResponseCode();
if (code >= 400 || code === 301 || code === 302) {
issues.push(keyword.getText() + ' | ' + url + ' | status=' + code);
}
} catch (e) {
issues.push(keyword.getText() + ' | ' + url + ' | fetch_error=' + e);
}
}
if (issues.length === 0) {
Logger.log('No broken or redirected final URLs found in this sample.');
return;
}
Logger.log(issues.join('n'));
}
Expected output
Sample output format: `keyword | final_url | status=…`
Safe test method
Start with a low `MAX_CHECKS` value and confirm whether the redirect rules you flag are actually worth review.
Scheduling note
Daily or several times per week is usually enough.
What not to automate
Do not auto-rewrite final URLs from the script.
Script 4: Zombie Product or Low-Activity Item Hunter
What it catches
Shopping items that keep spending or serving while producing weak activity signals.
Why automation may miss it
PMax and Shopping automation can continue to spend while item-level underperformance is hidden inside broader campaign logic.
Setup variables
- date range
- minimum spend
- minimum clicks
- output destination
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var DATE_RANGE = 'LAST_30_DAYS';
var MIN_COST = 10;
var MIN_CLICKS = 3;
var report = AdsApp.report(
"SELECT segments.product_item_id, metrics.clicks, metrics.cost_micros, metrics.conversions " +
"FROM shopping_performance_view " +
"WHERE segments.date DURING " + DATE_RANGE
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var clicks = Number(row['metrics.clicks']);
var cost = Number(row['metrics.cost_micros']) / 1000000;
var conversions = Number(row['metrics.conversions']);
if (cost >= MIN_COST && clicks >= MIN_CLICKS && conversions === 0) {
Logger.log(
row['segments.product_item_id'] +
' | clicks=' + clicks +
' | cost=' + cost +
' | conversions=' + conversions
);
}
}
}
Expected output
Sample output format: `product_item_id | clicks=… | cost=… | conversions=…`
Safe test method
Treat the first run as a review list, not a suppression list.
Scheduling note
Weekly usually gives better signal quality than daily.
What not to automate
Do not auto-exclude products from this script alone.
Script 5: Auction Pressure Monitor
What it catches
Campaigns where impression-share loss signals suggest new pressure or weaker coverage.
Why automation may miss it
Automation can react inside auctions without clearly surfacing competitive pressure as an operator alert.
Setup variables
- date range
- alert thresholds
- campaign filter
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var DATE_RANGE = 'LAST_7_DAYS';
var BUDGET_LOSS_THRESHOLD = 0.20;
var RANK_LOSS_THRESHOLD = 0.20;
var report = AdsApp.report(
"SELECT campaign.name, metrics.search_budget_lost_impression_share, " +
"metrics.search_rank_lost_impression_share " +
"FROM campaign WHERE campaign.status = 'ENABLED' " +
"AND segments.date DURING " + DATE_RANGE
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var budgetLoss = Number(row['metrics.search_budget_lost_impression_share']);
var rankLoss = Number(row['metrics.search_rank_lost_impression_share']);
if (budgetLoss >= BUDGET_LOSS_THRESHOLD || rankLoss >= RANK_LOSS_THRESHOLD) {
Logger.log(row['campaign.name'] + ' | budget_loss=' + budgetLoss + ' | rank_loss=' + rankLoss);
}
}
}
Expected output
Sample output format: `campaign | budget_loss=… | rank_loss=…`
Safe test method
Compare the log against what you already know from the campaign view before acting on it.
Scheduling note
Weekly is usually enough unless the account is highly volatile.
What not to automate
Do not let this script change bids or budgets automatically.
Script 6: Conversion Action Sanity Reporter
What it catches
Conversion actions that need cleanup, review, or clearer ownership.
Why automation may miss it
Automation can optimize to the signals it sees even when the conversion layer itself is stale or poorly structured.
Setup variables
- date range
- conversion categories to inspect
- output destination
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var report = AdsApp.report(
"SELECT conversion_action.name, conversion_action.category, conversion_action.status, " +
"conversion_action.primary_for_goal " +
"FROM conversion_action"
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
Logger.log(
row['conversion_action.name'] + ' | ' +
row['conversion_action.category'] + ' | ' +
row['conversion_action.status'] + ' | primary=' +
row['conversion_action.primary_for_goal']
);
}
}
Expected output
Sample output format: `conversion_action | category | status | primary=…`
Safe test method
Use the output to prepare a manual review list before touching goals or campaign settings.
Scheduling note
Weekly or after any tracking change is usually sensible.
What not to automate
Do not auto-change primary or secondary settings from a starter script.
Script 7: Performance Max Asset and Feed Coverage Reporter
What it catches
Coverage gaps that deserve manual review in Performance Max campaigns.
Why automation may miss it
PMax can continue serving while asset depth, feed quality, or campaign coverage issues stay hard to spot in a quick audit.
Setup variables
- campaign filter
- output destination
- fields to enrich from the current docs
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var report = AdsApp.report(
"SELECT campaign.name, campaign.status " +
"FROM campaign " +
"WHERE campaign.advertising_channel_type = 'PERFORMANCE_MAX'"
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
Logger.log(
row['campaign.name'] +
' | status=' + row['campaign.status'] +
' | review asset-group and feed coverage fields against current docs'
);
}
}
Expected output
Sample output format: `campaign | status=… | review asset-group and feed coverage fields`
Safe test method
Use it as a campaign list for manual PMax coverage review before you expand the query.
Scheduling note
Weekly is usually enough for this review layer.
What not to automate
Do not auto-add or remove assets from this starter template.
Script 8: AI Max Final URL and Tracking-Template Risk Checker
What it catches
Campaigns that need manual review for final URL expansion and tracking-template compatibility risk.
Why automation may miss it
Google documents that AI Max final URL expansion can create broken expanded landing pages when tracking templates are incompatible. That is a QA problem, not a benchmark claim.
Setup variables
- campaign filter
- list of tracking patterns to review
- output destination
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var report = AdsApp.report(
"SELECT campaign.name, campaign.advertising_channel_type " +
"FROM campaign " +
"WHERE campaign.status = 'ENABLED'"
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
Logger.log(
row['campaign.name'] +
' | channel=' + row['campaign.advertising_channel_type'] +
' | manually review AI Max final URL expansion and tracking-template compatibility'
);
}
}
Expected output
Sample output format: `campaign | channel=… | manually review AI Max final URL expansion`
Safe test method
Use the output as a review queue, then test URLs manually before enabling any automated expansion changes.
Scheduling note
Weekly or after major URL-template changes is usually enough.
What not to automate
Do not auto-toggle URL-expansion settings from this script.
Script 9: Spend Pacing and Anomaly Alert
What it catches
Cost patterns that look materially different from recent pacing.
Why automation may miss it
Automation may continue operating normally while an operator still needs a simple pacing alert that compares today against a recent baseline.
Setup variables
- pacing threshold
- date windows
- campaign filter
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var MULTIPLIER = 1.5;
var iter = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.get();
while (iter.hasNext()) {
var campaign = iter.next();
var today = campaign.getStatsFor("TODAY").getCost();
var last7 = campaign.getStatsFor("LAST_7_DAYS").getCost() / 7;
if (last7 > 0 && today > last7 * MULTIPLIER) {
Logger.log(campaign.getName() + ' | today=' + today + ' | avg_7d=' + last7);
}
}
}
Expected output
Sample output format: `campaign | today=… | avg_7d=…`
Safe test method
Review flagged campaigns against the account calendar before deciding the spike is a real issue.
Scheduling note
Daily is usually the right fit.
What not to automate
Do not auto-pause campaigns from an anomaly alert alone.
Script 10: Reporting Export to Google Sheets
What it catches
Repeated manual reporting work that should be standardized.
Why automation may miss it
Built-in reporting is helpful, but teams still need simple exports that land in a predictable template for QA or stakeholder review.
Setup variables
- spreadsheet URL
- sheet name
- date range
- metric columns
Starter code
Starter script — review and test in a copy of your account before scheduling.
function main() {
var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';
var SHEET_NAME = 'campaign_export';
var sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL).getSheetByName(SHEET_NAME);
if (!sheet) throw new Error('Missing sheet: ' + SHEET_NAME);
sheet.clearContents();
sheet.appendRow(['Campaign', 'Impressions', 'Clicks', 'Cost']);
var report = AdsApp.report(
"SELECT campaign.name, metrics.impressions, metrics.clicks, metrics.cost_micros " +
"FROM campaign WHERE campaign.status = 'ENABLED' " +
"AND segments.date DURING LAST_7_DAYS"
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
sheet.appendRow([
row['campaign.name'],
row['metrics.impressions'],
row['metrics.clicks'],
Number(row['metrics.cost_micros']) / 1000000
]);
}
Logger.log('Export completed to ' + SHEET_NAME);
}
Expected output
Sample spreadsheet columns: `Campaign | Impressions | Clicks | Cost`
Safe test method
Write to a test tab first, confirm headers and row counts, then schedule it.
Scheduling note
Daily or weekly works depending on the reporting rhythm.
What not to automate
Do not overwrite a live stakeholder sheet until the export format is stable.
How to schedule scripts without creating noise
Start with manual runs, then a narrow schedule, then a broader rollout only if the output stays useful. Most accounts do not need every script on the same cadence.
Use daily schedules for high-sensitivity alerts like budget pacing or URL risk. Use weekly schedules for review workflows like search-term mining, conversion-action sanity, PMax coverage, or auction-pressure review.
When to use scripts, API jobs, or manual audits
| Need | Use script when | Use API job when | Use manual audit when | Risk to avoid |
|---|---|---|---|---|
| Quick alerting | You need a lightweight monitoring layer | You need cross-account orchestration | Context still matters more than scale | Using a script for a workflow that needs system-level engineering |
| Repeated exports | You want simple scheduled output | You need large-scale pipelines or warehouse logic | One-off export is enough | Overbuilding before the reporting need is clear |
| Search-term review | A compact review list is enough | You need broader warehousing and joins | Query intent needs deep human judgment | Auto-negatives from weak evidence |
| URL QA | You need recurring checks | You need deeper crawling logic | Landing-page intent needs manual review | Treating status codes as the whole answer |
| Account diagnosis | You need ongoing QA signals | You need programmatic infrastructure | The account needs structural audit work | Confusing monitoring with strategy |
For a deeper systems workflow, connect this pillar with the Google Ads data retention export script, the SMB Google Ads audit checklist, and the Consent Mode checklist.
Common mistakes to avoid
- Treating scripts as a substitute for account strategy.
- Letting read-only QA scripts turn into uncontrolled mutation tools.
- Assuming PMax or AI Max blind spots should be fixed automatically.
- Using starter code without manual test runs.
- Claiming scripts improve CPA, ROAS, revenue, or conversion rate without first-party evidence.
- Scheduling every script too often and burying the team in alert noise.
Final recommendation
Use scripts where they are strongest: monitoring, exports, QA, and anomaly detection. Let automation optimize inside campaigns, let scripts surface issues around the edges, and let human review decide what should actually change.
Want the full script pack with dashboard templates and setup notes? Request the Google Ads script pack, or book an account systems audit and I’ll install the right monitoring layer for your account.
FAQ
Do Google Ads Scripts still work with automation?
Yes. Their value is usually as a monitoring and QA layer around automated campaigns, not as a replacement for automation.
Are Google Ads Scripts safe for client accounts?
They can be, if you keep them read-only or export-only at first, scope them carefully, and review output before scheduling.
Can scripts change budgets or bids automatically?
They can, but that is not the safest default. For most accounts, alerts and review workflows are the better first use case.
Do these scripts work with Performance Max?
Some do directly and some support PMax review indirectly. PMax often needs coverage, feed, and landing-page QA more than bulk change automation.
Can scripts detect every wasted query or product issue?
No. They can surface patterns and review queues, but they do not replace account judgment.
How often should Google Ads Scripts run?
Match cadence to the workflow. High-sensitivity alerts can run daily, while review scripts are often more useful on a weekly schedule.
Should I use scripts or the Google Ads API?
Use scripts for lightweight monitoring and exports. Use the API when you need larger-scale systems, pipelines, or cross-account engineering.
Source notes
- Google Ads Scripts overview
- Google Ads Scripts examples
- Google Ads API introduction
- Google Ads API reporting overview
- Performance Max in the Google Ads API
- AI Max for Search campaigns
- How AI Max for Search campaigns works
- About Performance Max campaigns
- Consent mode setup
- Consent Mode overview