Here’s a question I read on Reddit today:
“Can anyone help on how to setup a campaign / account in Google Ads to automatically pause once it reaches a certain spend daily / monthly limit? For example if I want X campaign to pause once it spends $100 / day?”
Source: https://www.reddit.com/r/PPC/comments/1jvp276/automatically_pause_campaigns_when_spend_is_met/
These kinds of scripts can easily be created by LLMs these days. Here’s the prompt I used and the code it generated. I have not tried previewing the script myself, but it looks like a good start.
Hope this helps!
Here’s the LLM prompt:
You are a senior Google Ads specialist with excellent Google Ads Scripts skills. You are also a JavaScript teacher and explain the code you create to a 14 year old. You always add comments to your code that describe its workings, you add logger output to the most important parts of the scripts and you also explain the code you create.
I need you to create a script that reads the campaign names and associated daily spend limits from a Google Sheet. The script checks the spend of the campaigns and if today’s spend exceeds the spend limit, the script will pause the campaign and send an email. Every night at 1AM the script will re-enable all the campaigns it has paused the day before. Here’s a place for you to enter some engaging and enticing text. Be sure to check for typos, and don’t forget — real people will be reading this!
Here’s the code it generated:
// --- Configuration ---
// Hey! These are the settings you need to change.
// 1. Put the URL of your Google Sheet here.
// It should look something like "https://docs.google.com/spreadsheets/d/ SPREADSHEET_ID /edit"
var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL_HERE';
// 2. Put the name of the sheet tab within your spreadsheet that has the campaign names and limits.
// Usually, it's 'Sheet1' unless you renamed it.
var SHEET_NAME = 'Sheet1';
// 3. Enter the email address where you want to receive notifications.
// You can add more emails separated by commas, like 'me@example.com,boss@example.com'
var EMAIL_ADDRESS = 'YOUR_EMAIL_HERE';
// 4. This is a label we'll use to mark campaigns paused by this script.
// You can change the name if you like, but it's usually fine like this.
var PAUSED_BY_SCRIPT_LABEL = 'PausedBySpendScript';
// --- End of Configuration ---
/**
* Main function - This is where the script starts.
* It decides whether to check campaign spending or re-enable campaigns based on the time.
*/
function main() {
// Get the current hour (0-23) in the account's timezone.
var currentHour = parseInt(Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'H'), 10);
Logger.log('Current hour in account timezone: ' + currentHour);
// Check if the label we use exists, and create it if it doesn't.
// It's like making sure we have the right sticker before we start using it.
ensureLabelExists(PAUSED_BY_SCRIPT_LABEL);
// If it's currently 1 AM (hour 1), run the function to re-enable campaigns.
if (currentHour === 1) {
Logger.log('It is 1 AM. Running the re-enable function.');
reenablePausedCampaigns();
} else {
// Otherwise (any other hour), run the function to check spending and pause if needed.
Logger.log('It is not 1 AM. Running the check and pause function.');
checkAndPauseCampaigns();
}
Logger.log('Script finished.');
}
/**
* Reads the campaign names and their daily spend limits from the Google Sheet.
* It's like the robot reading your instruction list.
* Returns: An object where keys are campaign names and values are their spend limits.
* Example: { 'Summer Sale Campaign': 50, 'Winter Promo': 100 }
*/
function readSpendLimitsFromSheet() {
Logger.log('Reading campaign limits from Google Sheet: ' + SPREADSHEET_URL);
try {
// Open the spreadsheet using the URL.
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
// Get the specific sheet (tab) by its name.
var sheet = spreadsheet.getSheetByName(SHEET_NAME);
if (!sheet) {
Logger.log('Error: Sheet "' + SHEET_NAME + '" not found in the spreadsheet. Make sure the name is correct.');
return {}; // Return an empty object if sheet not found
}
// Get all the data from the sheet, starting from the second row (A2)
// down to the last row with data, and across two columns (A and B).
// sheet.getLastRow() - 1 means "get all rows except the header".
var data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getValues();
// Create an empty object to store the limits. Think of it as an empty dictionary.
var campaignLimits = {};
// Loop through each row of data we got from the sheet.
for (var i = 0; i < data.length; i++) {
var campaignName = data[i][0]; // First column (index 0) is the campaign name.
var spendLimit = data[i][1]; // Second column (index 1) is the spend limit.
// Check if the campaign name and limit look valid.
if (campaignName && typeof campaignName === 'string' && campaignName.trim() !== '' && typeof spendLimit === 'number' && spendLimit >= 0) {
// If they are valid, add them to our dictionary.
// .trim() removes any accidental spaces before or after the name.
campaignLimits[campaignName.trim()] = spendLimit;
} else {
Logger.log('Warning: Skipping row ' + (i + 2) + ' in the sheet. Invalid data. Campaign Name: "' + campaignName + '", Spend Limit: "' + spendLimit + '"');
}
}
Logger.log('Successfully read ' + Object.keys(campaignLimits).length + ' campaign limits from the sheet.');
// Return the dictionary of campaign names and their limits.
return campaignLimits;
} catch (e) {
// If anything goes wrong (like the URL is bad or we don't have permission), log the error.
Logger.log('Error reading spreadsheet: ' + e);
// Send an email alert about the sheet reading error
sendEmailNotification('Error Reading Spreadsheet', 'Failed to read data from Google Sheet: ' + SPREADSHEET_URL + '\nSheet Name: ' + SHEET_NAME + '\nError: ' + e);
return {}; // Return an empty object on error
}
}
/**
* Checks the spending of campaigns listed in the sheet.
* Pauses campaigns that have spent over their limit for TODAY.
* Sends an email notification if any campaigns are paused.
*/
function checkAndPauseCampaigns() {
// Get the campaign limits from the Google Sheet first.
var campaignLimits = readSpendLimitsFromSheet();
// If the campaignLimits object is empty (maybe the sheet was empty or couldn't be read), stop here.
if (Object.keys(campaignLimits).length === 0) {
Logger.log('No campaign limits found or sheet could not be read. Exiting check function.');
return;
}
// Get all the campaign names from our dictionary (the keys).
var campaignNames = Object.keys(campaignLimits);
Logger.log('Checking spend for ' + campaignNames.length + ' campaigns listed in the sheet.');
// Keep track of campaigns we actually pause to put them in the email later.
var pausedCampaignsInfo = []; // An empty list to start.
// Find all campaigns in the Google Ads account whose names match the ones from our sheet
// AND are currently enabled (we don't need to check paused or removed ones).
var campaignIterator = AdsApp.campaigns()
.withCondition("CampaignName IN ['" + campaignNames.join("','") + "']") // Efficiently filter by multiple names
.withCondition("CampaignStatus = ENABLED")
.get();
Logger.log('Found ' + campaignIterator.totalNumEntities() + ' enabled campaigns matching names in the sheet.');
// Loop through each matching campaign found in Google Ads.
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
// Double-check if this campaign name is actually in our limits dictionary
// (it should be, because of the filter, but it's good practice).
if (campaignLimits.hasOwnProperty(campaignName)) {
var limit = campaignLimits[campaignName]; // Get the limit for this specific campaign.
// Get the stats (like cost) for this campaign for TODAY.
var stats = campaign.getStatsFor('TODAY');
var cost = stats.getCost(); // How much has it spent today?
Logger.log('Campaign: "' + campaignName + '" | Todays Spend: ' + cost + ' | Limit: ' + limit);
// THE BIG CHECK: Is today's cost GREATER than the limit we set?
if (cost > limit) {
Logger.log('ALERT: Campaign "' + campaignName + '" spent ' + cost + ', exceeding limit of ' + limit + '. Pausing campaign.');
// PAUSE the campaign! This stops it from spending more today.
campaign.pause();
// Apply our special label so we know this script paused it.
campaign.applyLabel(PAUSED_BY_SCRIPT_LABEL);
// Add the details to our list for the email report.
pausedCampaignsInfo.push({ name: campaignName, spend: cost, limit: limit });
}
} else {
// This shouldn't happen often with the IN condition, but good to log if it does.
Logger.log('Warning: Campaign "' + campaignName + '" found in Ads but not in the limits dictionary? Skipping.');
}
}
// After checking all campaigns, did we pause any?
if (pausedCampaignsInfo.length > 0) {
Logger.log(pausedCampaignsInfo.length + ' campaign(s) were paused due to exceeding spend limits.');
// If yes, build and send the email notification.
var emailSubject = 'Google Ads Alert: Campaigns Paused Due to Overspending';
var emailBody = 'The following Google Ads campaigns were automatically paused today because their spending exceeded the daily limit set in the Google Sheet:\n\n';
// Add each paused campaign's details to the email body.
for (var i = 0; i < pausedCampaignsInfo.length; i++) {
var info = pausedCampaignsInfo[i];
emailBody += '- Campaign: "' + info.name + '"\n';
emailBody += ' - Todays Spend: ' + formatCurrency(info.spend) + '\n'; // Format spend nicely
emailBody += ' - Daily Limit: ' + formatCurrency(info.limit) + '\n\n'; // Format limit nicely
}
emailBody += '\nThese campaigns have been labeled "' + PAUSED_BY_SCRIPT_LABEL + '" and will be automatically re-enabled around 1 AM account time.';
emailBody += '\n\nSpreadsheet Used: ' + SPREADSHEET_URL;
// Send the email!
sendEmailNotification(emailSubject, emailBody);
} else {
Logger.log('No campaigns needed pausing today.');
}
}
/**
* Re-enables campaigns that were paused by this script (identified by the label).
* This function is intended to run around 1 AM.
*/
function reenablePausedCampaigns() {
Logger.log('Searching for campaigns paused by this script (label: "' + PAUSED_BY_SCRIPT_LABEL + '") to re-enable them.');
// Find all campaigns that are currently PAUSED *and* have our special label.
var campaignIterator = AdsApp.campaigns()
.withCondition("LabelNames CONTAINS ANY ['" + PAUSED_BY_SCRIPT_LABEL + "']")
.withCondition("CampaignStatus = PAUSED")
.get();
Logger.log('Found ' + campaignIterator.totalNumEntities() + ' campaign(s) to re-enable.');
var reenabledCount = 0;
// Loop through each campaign found.
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
Logger.log('Re-enabling campaign: "' + campaignName + '"');
// ENABLE the campaign, making it active again.
campaign.enable();
// REMOVE the label, as it's no longer paused by the script.
campaign.removeLabel(PAUSED_BY_SCRIPT_LABEL);
reenabledCount++;
}
if (reenabledCount > 0) {
Logger.log('Successfully re-enabled ' + reenabledCount + ' campaign(s).');
// Optional: Send a confirmation email that campaigns were re-enabled.
// sendEmailNotification('Campaigns Re-enabled', reenabledCount + ' campaign(s) paused by the spend script yesterday have been re-enabled.');
} else {
Logger.log('No campaigns needed re-enabling.');
}
}
/**
* Sends an email notification.
* A helper function to keep the email sending code in one place.
* @param {string} subject The subject line of the email.
* @param {string} body The main content (body) of the email.
*/
function sendEmailNotification(subject, body) {
// Check if an email address has actually been provided in the configuration.
if (EMAIL_ADDRESS && EMAIL_ADDRESS.trim() !== '' && EMAIL_ADDRESS.toLowerCase() !== 'your_email_here') {
Logger.log('Sending email notification to: ' + EMAIL_ADDRESS + ' | Subject: ' + subject);
// Use the MailApp service to send the email.
MailApp.sendEmail({
to: EMAIL_ADDRESS,
subject: subject,
body: body,
});
} else {
// If no email address is set, just log a warning.
Logger.log('Warning: Email notification not sent because EMAIL_ADDRESS is not configured.');
Logger.log('Email Subject: ' + subject);
Logger.log('Email Body:\n' + body);
}
}
/**
* Checks if a label with the given name exists. If not, it creates it.
* This prevents errors if the script tries to apply a label that doesn't exist yet.
* @param {string} labelName The name of the label to check/create.
*/
function ensureLabelExists(labelName) {
// Look for labels with the exact name.
var labelSelector = AdsApp.labels().withCondition("Name = '" + labelName + "'");
var labelIterator = labelSelector.get();
// If the iterator doesn't find any labels (count is 0), then the label doesn't exist.
if (!labelIterator.hasNext()) {
Logger.log('Label "' + labelName + '" not found. Creating it now.');
// Create the label. You could add a description or color if you wanted.
AdsApp.createLabel(labelName, 'Campaign paused by automated spend script');
Logger.log('Label "' + labelName + '" created.');
} else {
// If the label already exists, we don't need to do anything.
Logger.log('Label "' + labelName + '" already exists.');
}
}
/**
* Formats a number as currency, using the account's currency code.
* Example: 123.456 becomes "$123.46" or "€123.46"
* @param {number} amount The number to format.
* @return {string} The formatted currency string.
*/
function formatCurrency(amount) {
// Get the currency code for the Google Ads account (e.g., "USD", "EUR", "GBP").
var currencyCode = AdsApp.currentAccount().getCurrencyCode();
// Format the number to 2 decimal places and add the currency symbol/code.
// Note: This basic formatting might not be perfect for all currencies, but it's usually good enough.
return currencyCode + ' ' + amount.toFixed(2);
}
Want to test it?
It might need some debugging (again, with the help of an LLM, this should not be a lot of work). Let me know if you get stuck.
– Nils
PS: In case you missed it, I recently launched my first eBook “21 Must Have Scripts” with scripts of equal or higher value for your accounts . It’s not too late to grab your copy!