A Daily/Monthly Google Ads Budget Management Script
Budget management scripts for limiting Google Ads spend are essential, especially given that Google can technically spend 2x your daily budget.
I've been a big fan of Frederick Vallaeys's script, though it lacks a few choice features, such as an MCC version and Google Shopping campaign support (this script is a few years old and it does appear to select shopping campaigns, I think this feature simply broke over time), as well as the ability to add campaign exclusions when used for otherwise account wide spending. So, I decided to build my own version.
I based my script off Cypress North's budget overspend script, which is itself based off this script from the Google Ads Script forum. The changes to Cypress North's script are:
The ability to pause campaigns based on monthly spend
MCC support
Shopping campaign support
Removed the need to create an automated rule to switch campaigns back on for a new day/month
Removed the need to create a "pause" label first in the Google Ads interface.
A Couple Caveats
This script doesn't yet work for Smart Shopping campaigns (it does work for regular shopping campaigns, and once Google allows for Smart Shopping I'll add it)
It won't be able to stop your campaigns the second your budget is spent - it runs every hour, so you'll have 1h's worth of additional spend over and above what you specify in the script
It only works for Daily and Monthly time periods
With that out of the way, here's the script, first for individual accounts, then for MCC accounts:
Individual Account Version
function main() {
var SPEND = 0;
var now = new Date();
var time_zone = AdsApp.currentAccount().getTimeZone();
var the_hour = Utilities.formatDate(now, time_zone, "H");
var the_day = Utilities.formatDate(now, time_zone, "dd");
//STEP 1: Specify the daily/monthly budget you want to limit spend to
var MAX_TOTALS = 1;
//STEP 2: Specify whether you want to cap daily or monthly spend below - TODAY & THIS_MONTH are the options
var time_period = "TODAY";
//STEP 2.5 (OPTIONAL): Change the name of the pause label
var pause_label = "Daily Budget Script - Paused";
//STEP 3: If you have any campaigns you'd like to keep running regardless of spend levels, give them this label in the Google ads interface
var exemption_label = "Daily Budget Script - Exemption";
function getCosts(selector) {
var totals = 0;
while (selector.hasNext()) {
var campaign = selector.next();
var stats = campaign.getStatsFor(time_period)
totals += stats.getCost();
}
return totals;
}
function processSpend() {
SPEND += getCosts(AdsApp.campaigns().get());
SPEND += getCosts(AdsApp.videoCampaigns().get());
SPEND += getCosts(AdsApp.shoppingCampaigns().get());
return SPEND;
}
function labelCheck(campaign_to_pause) {
try {
campaign_to_pause.applyLabel(pause_label)
} catch (err) {
AdsApp.createLabel(pause_label)
labelCheck(campaign_to_pause)
}
}
function pauseCampaigns(selector) {
while (selector.hasNext()) {
var campaign_to_pause = selector.next();
campaign_to_pause.pause();
labelCheck(campaign_to_pause)
}
}
function processCampaignPause() {
Logger.log("Spend: " + SPEND + " - " + "Budget: " + MAX_TOTALS)
if (SPEND > MAX_TOTALS) {
try {
var adsSelector = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_NONE " + "['" + exemption_label + "']")
.withCondition("CampaignExperimentType = BASE")
.get();
var videoSelector = AdsApp.videoCampaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_NONE " + "['" + exemption_label + "']")
.withCondition("CampaignExperimentType = BASE")
.get();
var shoppingSelector = AdsApp.shoppingCampaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_NONE " + "['" + exemption_label + "']")
.withCondition("CampaignExperimentType = BASE")
.get();
pauseCampaigns(adsSelector);
pauseCampaigns(videoSelector);
pauseCampaigns(shoppingSelector);
} catch (err) {
AdsApp.createLabel(exemption_label)
processCampaignPause()
}
}
}
processSpend();
processCampaignPause();
var re_enable_time = time_period === "TODAY" ? the_hour : the_day;
var re_enable_number = time_period === "TODAY" ? 0 : 1;
if (Number(re_enable_time) === re_enable_number) {
function enableCampaigns(selector) {
while (selector.hasNext()) {
var campaign = selector.next();
campaign.enable();
campaign.removeLabel(pause_label)
}
}
function processCampaignEnable() {
try {
var adsSelector = AdsApp.campaigns()
.withCondition("LabelNames CONTAINS_ANY " + "['" + pause_label + "']")
.get();
var videoSelector = AdsApp.videoCampaigns()
.withCondition("LabelNames CONTAINS_ANY " + "['" + pause_label + "']")
.get();
var shoppingSelector = AdsApp.shoppingCampaigns()
.withCondition("LabelNames CONTAINS_ANY " + "['" + pause_label + "']")
.get();
enableCampaigns(adsSelector);
enableCampaigns(videoSelector);
enableCampaigns(shoppingSelector);
} catch (err) {
AdsApp.createLabel(pause_label)
processCampaignEnable()
}
}
processCampaignEnable()
}
}
MCC Account Version
function main() {
var accountIterator = AdsManagerApp
.accounts()
//STEP 1: Input your client ids below
.withIds(['','']) //.withIds(['123-456-7890','098-765-4321'])
.get();
while (accountIterator.hasNext()) {
var account = accountIterator.next();
AdsManagerApp.select(account)
var MAX_TOTALS = 0;
var SPEND = 0;
var now = new Date();
var time_zone = AdsApp.currentAccount().getTimeZone();
var the_hour = Utilities.formatDate(now, time_zone, "H");
var the_day = Utilities.formatDate(now, time_zone, "dd");
//STEP 2: Specify whether you want to cap daily or monthly spend below - TODAY & THIS_MONTH are the options
var time_period = "TODAY";
//STEP 2.5 (OPTIONAL): Change the name of the pause label
var pause_label = "Daily Budget Script - Paused";
//STEP 3: If you have any campaigns you'd like to keep running regardless of spend levels, give them this label in the Google ads interface
var exemption_label = "Daily Budget Script - Exemption";
//STEP 4: For each client ID in your MCC, create a "case" line with the id and the daily/monthly budget you want to limit spend to for "MAX_TOTALS"
switch (account.getCustomerId()) {
//case '123-456-7890': MAX_TOTALS = 10; break; //Client Name #1
//case '987-654-3210': MAX_TOTALS = 25; break; //Client Name #2
}
function getCosts(selector) {
var totals = 0;
while (selector.hasNext()) {
var campaign = selector.next();
var stats = campaign.getStatsFor(time_period)
totals += stats.getCost();
}
return totals;
}
function processSpend() {
SPEND += getCosts(AdsApp.campaigns().get());
SPEND += getCosts(AdsApp.videoCampaigns().get());
SPEND += getCosts(AdsApp.shoppingCampaigns().get());
return SPEND;
}
function labelCheck(campaign_to_pause) {
try {
campaign_to_pause.applyLabel(pause_label)
} catch (err) {
AdsApp.createLabel(pause_label)
labelCheck(campaign_to_pause)
}
}
function pauseCampaigns(selector) {
while (selector.hasNext()) {
var campaign_to_pause = selector.next();
campaign_to_pause.pause();
labelCheck(campaign_to_pause)
}
}
function processCampaignPause() {
Logger.log("Account: " + account.getName() + " - " + "Spend: " + SPEND + " - " + "Budget: " + MAX_TOTALS)
if (SPEND > MAX_TOTALS) {
try {
var adsSelector = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_NONE " + "['" + exemption_label + "']")
.withCondition("CampaignExperimentType = BASE")
.get();
var videoSelector = AdsApp.videoCampaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_NONE " + "['" + exemption_label + "']")
.withCondition("CampaignExperimentType = BASE")
.get();
var shoppingSelector = AdsApp.shoppingCampaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_NONE " + "['" + exemption_label + "']")
.withCondition("CampaignExperimentType = BASE")
.get();
pauseCampaigns(adsSelector);
pauseCampaigns(videoSelector);
pauseCampaigns(shoppingSelector);
} catch (err) {
AdsApp.createLabel(exemption_label)
processCampaignPause()
}
}
}
processSpend();
processCampaignPause();
var re_enable_time = time_period === "TODAY" ? the_hour : the_day;
var re_enable_number = time_period === "TODAY" ? 0 : 1;
if (Number(re_enable_time) === re_enable_number) {
function enableCampaigns(selector) {
while (selector.hasNext()) {
var campaign = selector.next();
campaign.enable();
campaign.removeLabel(pause_label)
}
}
function processCampaignEnable() {
try {
var adsSelector = AdsApp.campaigns()
.withCondition("LabelNames CONTAINS_ANY " + "['" + pause_label + "']")
.get();
var videoSelector = AdsApp.videoCampaigns()
.withCondition("LabelNames CONTAINS_ANY " + "['" + pause_label + "']")
.get();
var shoppingSelector = AdsApp.shoppingCampaigns()
.withCondition("LabelNames CONTAINS_ANY " + "['" + pause_label + "']")
.get();
enableCampaigns(adsSelector);
enableCampaigns(videoSelector);
enableCampaigns(shoppingSelector);
} catch (err) {
AdsApp.createLabel(pause_label)
processCampaignEnable()
}
}
processCampaignEnable()
}
}
}
Suggestions or bugs? Let me know in the comments.