Have you ever started work on a Monday morning and, when checking accounts over, realised you’d overspent?
Have you ever wondered what the best solution to overspending on a Google Ads account could be?
Maybe you’ve considered setting rules in place to pause individual campaigns when they overspend or even decided to work over the weekend to make sure nothing goes wrong?
With this in mind, I have written a Google Ads overspend script, named the Hard Stop Script, which will allow you to stop worrying about a huge overspend over the weekend or bank holiday and have peace of mind that yours or your clients’ budgets aren’t being overshadowed by spend.
Firstly, let’s look at why and how Google Ads overspend:
Google Ads’ bidding algorithms and optimisations figure out which days of the month to adjust spend in order to gain more clicks and conversions, for example, when search volume spikes, or conversion rates are higher. This means that on some days you may spend above or below your daily campaign budget – as much as two times your average daily budget. Therefore, it is crucial to find ways to minimise the risk of potentially spending double your clients’ budget, and this is exactly what this Hard Stop Script is all about.
Hard Stop will prevent any horror overspends, which can be critical to put in place generally, but more importantly, over a time away from an account, such as during a bank holiday weekend, or at a time of higher spend such as Black Friday.
Some accounts will be able to take advantage of monthly spend limits within their settings, however, this is only available to accounts that aren’t using the invoice billing method.
The script works best when run on an hourly basis, in order to be as strict as possible on budgets. It will read the current spend of the account for the month and compare it to the budget you can enter at the top of the script. If it exceeds the amount, it will iterate through all currently live campaigns, add the label ‘PAUSED BY HS’ (it will create this if you don’t have the label in your account already), and then pause all live campaigns. The script will then email you with a note on what’s just happened, and you can enter the email address (or email addresses) that you’d like to be notified at the top of the script. At the beginning of the next month, the script will take all campaigns which were paused and labelled, set them back live and remove the label.
See below for the script, feel free to copy and paste and upload onto your accounts. You need to just adjust the budget and email addresses, and you’re good to go.
// HARD STOP SCRIPT
// IF YOU’RE LOOKING TO PREVENT A MASSIVE OVERSPEND, ENTER YOUR MONTHLY BUDGET,
// AND THIS SCRIPT WILL PAUSE EVERYTHING IF YOU GO OVER. IT WILL THEN REENABLE
// THE CAMPAIGNS AND REMOVE THE LABEL ON THE FIRST DAY OF THE NEXT MONTH.
// Written By Aaron Digby, Honchō Search, 24/05/2022 \\
function main() {
// Enter Your Monthly Budget \\
var budget = 1000;
// Enter Your Preferred Email Address For The Emergency Email \\
var emailAddress = ‘example@example.com’;
// Enter Preferred Label Name
var campaignLabel = “PAUSED BY HS”;
// PULL SPEND FROM ACCOUNT
var query = “SELECT metrics.cost_micros, customer.id” +
” FROM customer” +
” WHERE segments.date DURING THIS_MONTH”;
var report = AdsApp.report(query);
var rows = report.rows();
if (rows.hasNext()) {
var row = rows.next();
var spend = (row[“metrics.cost_micros”] / 1000000).toFixed(2);
var account = row[“customer.id”];
if (spend > budget){
Logger.log(spend)
Logger.log(“OVER BUDGET – PAUSING ALL CAMPAIGNS”);
}
}
// FINDS IF ACCOUNT HAS LABEL, IF NOT, IT CREATES ONE
function labelMaker() {
try {
var labelSelector = AdsApp.labels();
var labelIterator = labelSelector.get();
var createCampaignLabel = true;
while (labelIterator.hasNext()) {
var label = labelIterator.next();
var labelName = label.getName();
if (labelName == campaignLabel) {
createCampaignLabel = false;
}
}
if (createCampaignLabel) {
AdsApp.createLabel(campaignLabel);
Logger.log(“Creating Campaign Label ‘” + campaignLabel + “‘.”)
}
} catch (e) {
Logger.log(
“Error Creating Label – Already Exists Within Account”
)
}
}
labelMaker();
/* CYCLES THROUGH SEARCH, DISPLAY, SHOPPING & VIDEO CAMPAIGNS AND
PAUSES THOSE WHICH ARE LIVE AND LABELS THEM */
if(spend > budget){
var campaignSelector = AdsApp
.campaigns()
.withCondition(“campaign.status = ENABLED”)
var campaignIterator = campaignSelector.get();
while (campaignIterator.hasNext()){
var campaign = campaignIterator.next();
Logger.log(campaign.getName())
campaign.applyLabel(campaignLabel);
campaign.pause();
}
var shoppingCampaignSelector = AdsApp
.shoppingCampaigns()
.withCondition(“campaign.status = ENABLED”)
var shoppingCampaignIterator = shoppingCampaignSelector.get();
while (shoppingCampaignIterator.hasNext()){
var shoppingCampaign = shoppingCampaignIterator.next();
Logger.log(shoppingCampaign.getName())
shoppingCampaign.applyLabel(campaignLabel);
shoppingCampaign.pause();
}
var videoCampaignSelector = AdsApp
.videoCampaigns()
.withCondition(“campaign.status = ENABLED”)
var videoCampaignIterator = videoCampaignSelector.get();
while (videoCampaignIterator.hasNext()){
var videoCampaign = videoCampaignIterator.next();
Logger.log(videoCampaign.getName())
videoCampaign.applyLabel(campaignLabel);
videoCampaign.pause();
}
}
/* ENABLES PREVIOUSLY PAUSED CAMPAIGNS AND REMOVES LABEL AT THE
START OF THE FOLLOWING MONTH */
var d = new Date();
d.toUTCString();
Logger.log(d.toUTCString())
Logger.log(d.getUTCDate())
Logger.log(d.getUTCHours())
Logger.log(“Now Enabling Campaigns Previously Paused By The HS Script”)
var labelSelector = AdsApp.labels()
.withCondition(“label.name = ‘” + campaignLabel + “‘”)
var labelIterator = labelSelector.get();
var labelID;
if (labelIterator.hasNext()) {
var label = labelIterator.next();
labelID = label.getId()
}
Logger.log(account)
Logger.log(labelID)
if (labelID != null){
var campaignSelector = AdsApp
.campaigns()
.withCondition(“campaign.status = PAUSED”)
.withCondition(“campaign.labels CONTAINS ALL (‘customers/” + account + “/labels/” + labelID + “‘)”)
var campaignIterator = campaignSelector.get();
while (campaignIterator.hasNext()){
var campaign = campaignIterator.next();
Logger.log(campaign.getName())
var utcDate = d.getUTCDate();
var utcHours = d.getUTCHours();
if(utcDate == 1 && utcHours == 1) {
campaign.enable();
campaign.removeLabel(campaignLabel);
}
}
var shoppingCampaignSelector = AdsApp
.shoppingCampaigns()
.withCondition(“campaign.status = PAUSED”)
.withCondition(“campaign.labels CONTAINS ALL (‘customers/” + account + “/labels/” + labelID + “‘)”)
var shoppingCampaignIterator = shoppingCampaignSelector.get();
while (shoppingCampaignIterator.hasNext()){
var shoppingCampaign = shoppingCampaignIterator.next();
Logger.log(shoppingCampaign.getName())
var utcDate = d.getUTCDate();
var utcHours = d.getUTCHours();
if(utcDate == 1 && utcHours == 1) {
shoppingCampaign.enable();
shoppingCampaign.removeLabel(campaignLabel);
}
}
var videoCampaignSelector = AdsApp
.videoCampaigns()
.withCondition(“campaign.status = PAUSED”)
.withCondition(“campaign.labels CONTAINS ALL (‘customers/” + account + “/labels/” + labelID + “‘)”)
var videoCampaignIterator = videoCampaignSelector.get();
while (videoCampaignIterator.hasNext()){
var videoCampaign = videoCampaignIterator.next();
Logger.log(videoCampaign.getName())
var utcDate = d.getUTCDate();
var utcHours = d.getUTCHours();
if(utcDate == 1 && utcHours == 1) {
videoCampaign.enable();
videoCampaign.removeLabel(campaignLabel);
}
}
}
// COMPOSE AND SEND ALERT EMAIL \\
var accountName = AdsApp.currentAccount().getName()
var subject = ‘URGENT BUDGET OVERSPEND EMAIL FOR ‘ + accountName.toUpperCase();
var message = ‘THE HARD STOP SCRIPT HAS PAUSED ALL CAMPAIGNS’;
if (spend > budget){
MailApp.sendEmail(
emailAddress,
subject,
message)
}
}
A couple of crucial points to note though:
I will update this script to include Performance Max once Google Ads updates their API.
Hope you find this useful, and feel free to message me with any suggestions or improvements.
Google Ads FAQs
It depends. Daily checks can be extremely useful to make sure nothing goes wrong, but can be time consuming. Taking a quick glance every couple of days is best to utilise your time, however, when first uploading a new campaign or adjusting budgets, you may want to do this on a more regular basis.
Google’s internal optimisation means that the daily budget that you assign to a campaign may be over or underspent each day, based on the days of the month when you’re most likely to get clicks and/or conversions. This means that your daily spend on a campaign can be up to two times your daily budget. This is crucial to note as many people assume that the daily budget is all that will be spent in a day.
It depends on the campaign. You can use keyword and performance planner within Google to predict the search volume and the average CPC to predict how much can be spent on a daily basis. Once your campaign has been running for a few days, you can use Impression Share lost to Budget, to figure out how many impressions you missed out on, due to lack of budget, and adjust your daily spend from there.
Aaron Digby, Paid Search Analyst
If you need support from Honcho’s Paid Media team, get in touch today.