Google Ads scripts: automations for budgets, bids, and reporting, with working code
Updated 9 min read
In short
Google Ads scripts are a built-in, serverless automation layer in Google Ads that run modern JavaScript (V8, ES6+) on Google’s infrastructure at no extra cost, letting you manage entities and run GAQL reports without your own servers or API tokens. You write and schedule scripts in a browser IDE, authorize them with OAuth2, and they can run hourly or less frequently for up to 30 minutes per advertiser account execution, or up to 60 minutes for certain parallel manager-account runs.
Key takeaways
Google Ads scripts run ES6+ JavaScript in a browser IDE on Google’s infrastructure, use OAuth2, and cost nothing beyond your ad spend.
Advertiser-account scripts can run for up to 30 minutes per execution, with hourly as the tightest schedule; manager scripts can reach 60 minutes with executeInParallel.
AdsApp exposes campaigns, budgets, stats, and GAQL reporting, while SpreadsheetApp, MailApp, and UrlFetchApp handle Sheets, email, and external APIs.
A budget-monitoring script reads campaign stats, compares cost against a threshold, and sends alert emails via MailApp when spend crosses that line.
GAQL queries run through AdsApp.search or AdsApp.report, and AdsApp.report(query).exportToSheet(sheet) can write performance data directly into a Google Sheet.
Google Ads scripts give you a way to automate recurring Google Ads work with serverless JavaScript, without managing API tokens or infrastructure, and they fit cleanly into a weekly operating loop of monitoring spend, performance, and pacing. For what to monitor and why, see this audit-focused reference, then use the patterns here to implement the checks in code.
What Google Ads scripts are, where they run, and what they cost
Google Ads scripts are a serverless automation platform inside Google Ads that let you manage and query account data using modern JavaScript on the V8 runtime. Per Google’s documentation, you write scripts in a browser-based IDE, and they run entirely on Google’s infrastructure at no additional cost beyond your advertising spend. Scripts use OAuth2 for authorization and do not require Google Ads API developer tokens or any external servers.
Scripts run against your Google Ads account context using the AdsApp object model. AdsApp exposes campaigns, ad groups, keywords, and ads through selectors and iterators, and provides methods to inspect and change entities. For example, campaign iterators support methods such as getName(), getBudget() with getAmount() and setAmount(), getStatsFor(dateRange) with getCost() and getClicks(), and control methods like pause() and enable.
Because scripts are first-party in the Google Ads UI, you can test them in preview mode before applying changes. Preview mode lets you see what changes would be made without committing them, which is useful when you are adapting sample code to your own account structure.
Execution limits, scheduling, and authorization behavior
Advertiser-account scripts have a maximum runtime of 30 minutes per execution. If a script hits that 30-minute limit, Google cancels the execution, but any changes made before cancellation are still applied, per Google’s limits documentation. Scheduling options are hourly, daily, weekly, monthly, or on demand, so scripts cannot run continuously; the tightest schedule is once per hour.
Manager-account scripts share the same base behavior, but with additional capabilities. A manager script can run for 30 minutes in normal mode, and up to 60 minutes when you use executeInParallel with a callback. executeInParallel can process up to 50 child accounts in parallel, each with its own quota, and each processAccount result is capped at 10 MB.
Authorization uses OAuth2. Each account allows 250 authorized scripts, and scripts run without an active user session. If the user who authorized a script loses access to the account, the script stops running until it is reauthorized by someone with access. Google’s limits documentation notes that limits can change at any time without warning, so you should treat these values as current but not permanent.
Budget monitoring script with email alerts
A budget-monitoring script reads campaign statistics for a date range, compares cost against a threshold, and sends an email when spend crosses that threshold. It uses AdsApp.campaigns() to select campaigns, getStatsFor() to read cost, and MailApp.sendEmail() to send alerts. This pattern is useful when you want a simple watch on daily spend without building a full pacing system; for deeper budget mechanics, see this budget reference.
Correction of a common misconception: scripts do not require API access or developer tokens. They run inside the Google Ads UI with OAuth2 authorization only, and no Google Ads API token or external infrastructure is needed.
Below is a complete, working example that emails when a campaign’s cost for TODAY exceeds a configurable threshold. Adaptation points are commented.
function main() {
// <=== ADAPTATION POINTS ===>
// 1. Set the email address that should receive alerts.
var ALERT_EMAIL = 'you@example.com';
// 2. Set the daily spend threshold in account currency units.
// Example: 500 means 500 USD if the account is in USD.
var DAILY_SPEND_THRESHOLD = 500;
// 3. Optionally, restrict to specific campaigns by name or label
// by adding withCondition() calls below.
// Fetch all non-removed search/display campaigns.
// Note: AdsApp.campaigns() does NOT include video or shopping campaigns.
// For those, use AdsApp.videoCampaigns() and AdsApp.shoppingCampaigns().
var campaignIterator = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.get();
var alerts = [];
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
// Get today's stats for the campaign.
var stats = campaign.getStatsFor('TODAY');
var cost = stats.getCost(); // Cost in account currency units.
if (cost >= DAILY_SPEND_THRESHOLD) {
alerts.push({
name: campaign.getName(),
cost: cost
});
}
}
if (alerts.length === 0) {
Logger.log('No campaigns exceeded the daily spend threshold.');
return;
}
// Build email body.
var lines = [];
lines.push('The following campaigns exceeded the daily spend threshold of ' +
DAILY_SPEND_THRESHOLD + ':');
lines.push('');
for (var i = 0; i < alerts.length; i++) {
var alert = alerts[i];
lines.push(alert.name + ': ' + alert.cost.toFixed(2));
}
var body = lines.join('\n');
// Send the alert email.
// MailApp quotas apply: per Apps Script quotas, consumer accounts are
// limited to 100 recipients per day, and Google Workspace accounts to
// 1,500 recipients per day.
MailApp.sendEmail({
to: ALERT_EMAIL,
subject: 'Google Ads daily budget alert',
body: body
});
Logger.log('Sent budget alert email to ' + ALERT_EMAIL);
}
Key adaptation points:
- ALERT_EMAIL should be a monitored inbox or group address.
- DAILY_SPEND_THRESHOLD should reflect the daily spend level that warrants attention in your account currency.
- You can narrow the campaigns with additional withCondition() filters, for example by campaign name or label, using only documented selector conditions.
This script uses AdsApp.campaigns(), which per Google’s limits documentation does not include video or shopping campaigns. To monitor those, you would repeat the pattern with AdsApp.videoCampaigns() and AdsApp.shoppingCampaigns(). This corrects another misconception that campaigns() returns every campaign type.
GAQL reporting and exporting results to Google Sheets
Google Ads scripts support reporting through Google Ads Query Language (GAQL) using AdsApp.search and AdsApp.report. GAQL queries use snake_case field names in the query text, for example SELECT campaign.name, metrics.clicks, metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_7_DAYS. When you iterate results from AdsApp.search, fields are read in camelCase on the returned GoogleAdsRow objects, for example row.campaign.name, row.metrics.clicks, and row.metrics.costMicros.
Cost metrics are returned in micros, so you divide cost_micros by 1,000,000 to get the amount in the account currency. AdsApp.report(query).exportToSheet(sheet) can write a report’s rows directly to a Google Sheet. SpreadsheetApp.openByUrl lets you open a destination spreadsheet, and you can then select or create a sheet and pass it to exportToSheet.
Below is a complete example that runs a GAQL performance report for the last 7 days and writes it to a specific sheet. Adaptation points are commented.
function main() {
// <=== ADAPTATION POINTS ===>
// 1. URL of the Google Sheet where you want the report.
var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/your-spreadsheet-id/edit';
// 2. Name of the sheet (tab) to write into. The script will create it if missing.
var SHEET_NAME = 'Campaign Performance - Last 7 Days';
// GAQL query using snake_case field names.
var query = '' +
'SELECT\n' +
' campaign.name,\n' +
' metrics.clicks,\n' +
' metrics.cost_micros\n' +
'FROM campaign\n' +
'WHERE segments.date DURING LAST_7_DAYS';
// Open the spreadsheet and get or create the sheet.
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = spreadsheet.insertSheet(SHEET_NAME);
} else {
// Clear existing content before writing a fresh report.
sheet.clearContents();
}
// Run the GAQL report and export directly to the sheet.
var report = AdsApp.report(query);
report.exportToSheet(sheet);
Logger.log('Exported GAQL report to sheet: ' + SHEET_NAME);
}
This example uses AdsApp.report(query).exportToSheet(sheet), which is documented as writing the report rows directly into the provided sheet. Because reports are not subject to entity limits, this avoids selector caps that apply to AdsApp selectors and iterators. If you instead use AdsApp.search(query), you would iterate the results and manually write to the sheet using sheet.appendRow or range operations.
Correction of a common misconception: same-morning performance data is not always reliable. Google’s own sample account summary solution states that account statistics can be delayed by up to 3 hours, and recommends scheduling daily reporting scripts at 03:00 or later. You should follow the same pattern for GAQL-based reporting scripts if you need complete previous-day data.
Manager-account scripts and working across multiple accounts
Manager-account scripts use the AdsManagerApp object to work across child accounts. AdsManagerApp provides an accounts selector that you can filter and limit, then either iterate sequentially with select() or process in parallel with executeInParallel. Inside the context of each child account, AdsApp operates as if the script were running in that account directly.
Per Google’s documentation, executeInParallel can process up to 50 accounts at once, each with its own quota, and each processAccount return value is capped at 10 MB. When you use executeInParallel with a callback, the total runtime limit for the manager script increases to 60 minutes. Without executeInParallel, manager scripts share the 30-minute limit.
A typical pattern is to use AdsManagerApp.accounts().withCondition() to select a subset of accounts, then either loop through them and run AdsApp logic sequentially, or use executeInParallel to run a processAccount function in parallel across up to 50 accounts. This lets you reuse the same budget-monitoring or reporting logic across a portfolio of accounts under a manager.
Selector limits, campaign-type access, and stats timing gotchas
Selectors and iterators in AdsApp have documented limits. Iterators default to at most 50,000 results, which you can modify with withLimit(). The withIds() method on selectors can handle at most 10,000 IDs, and logging output is truncated at 100 KB. Reports run through AdsApp.report or AdsApp.search are not subject to these entity limits, which is why GAQL reporting is preferred for large result sets.
A key gotcha is that AdsApp.campaigns().get() filters out video and shopping campaigns. Google’s limits documentation states that you must use videoCampaigns() and shoppingCampaigns() selectors to access those campaign types. This corrects the misconception that campaigns() returns every campaign.
Another timing gotcha is statistics delay. Google’s account summary solution notes that account statistics can be delayed by up to 3 hours, and their own sample schedules daily reporting scripts at 03:00 or later. This corrects the misconception that same-morning performance data is always reliable. When you build scripts that depend on recent stats, you should account for this delay in your scheduling.
MailApp has daily email recipient limits enforced by Apps Script quotas: 100 recipients per day for consumer accounts and 1,500 per day for Google Workspace accounts. If you send alerts to many recipients or from many scripts, you need to design within those limits.
When scripts are enough and when you need the Google Ads API
Google’s product-selection guidance is explicit: use scripts when you prefer not to manage infrastructure, and use the Google Ads API when you are building your own software or high-volume pipelines on your own servers. Automated rules and Google Ads Editor cover no-code management scenarios. Scripts sit in the middle, giving you programmable control without external hosting.
Scripts have hard execution limits: 30 minutes per advertiser account run and, for manager scripts, 30 minutes normally or 60 minutes with executeInParallel. Bulk uploads are limited to 50 MB or 1,000,000 rows per file, and upload jobs time out after 2 hours. If your automation needs exceed these limits, or you need more complex integrations such as large-scale offline conversion uploads, Google’s guidance points you toward the API; for offline conversions specifically, see this API-focused reference.
Google also notes that limits can change at any time without warning. For long-lived, business-critical integrations where you need explicit versioning and tighter control over quotas, the Google Ads API is the tool designed for that use case, while scripts remain the fastest way to automate recurring in-account workflows without extra infrastructure.
TF's automation interest is in service of the same operating loop as everything else — ROAS, CPC, deliverability, and impression share, read weekly. Scripts earn their place when they watch the things TF already watches: budget pacing against the spending-limit mechanics, campaigns slipping into Limited by budget while converting, and recurring report pulls that feed the weekly read. Automation never replaces the judgment call — the inflection-point decision on spend stays human — but it removes the manual pulling that makes teams skip the weekly read in the first place.
Frequently asked questions
Google Ads scripts selectors and iterators default to a maximum of 50,000 results, which you can adjust with withLimit() according to Google’s limits documentation. The withIds() method on selectors can handle at most 10,000 IDs in a single call. Logging output is truncated at 100 KB, so very large logs will be cut off. GAQL reports run through AdsApp.report or AdsApp.search are not subject to these entity limits.
Sources
This resource is for educational purposes. Ad platforms change their policies, features, and pricing frequently; verify details against the platform's current documentation before acting on them.
