Control Budget Script: We all know that Google loves to spend the overbudget. You may also be aware that daily costs might exceed your average daily budget.
Why Google Ads Spend More Than Budget?
Even Google says that your campaign spend might vary, but you will never pay more than:
- Your daily spending limit (two times your average daily budget for most campaigns) on any particular day.
- Your monthly spending limit (30.4 times your average daily budget for most campaigns) in any particular month.
But, the fact is that spending over budget on a particular day creates issues for advertisers and sometimes your learnings and metrics get thrown away and cause havoc for your business function, if you can’t afford your overspending.
Control Budget Script
Google Ads calculates the budget on a daily basis. If you need to determine the monthly budget, multiply the daily budget by 30.4, which is the average number of days in a month. However, this is not the story, if you calculate the budget on a daily basis, Google can spend 2x of your daily budget on a particular day. To control overspending you can create a rule and write the script so that as soon as your daily budget threshold reaches the campaigns get paused.
In this article, we will learn to create a rule to control the budget as well as I will share the script for this rule. The script was originally published on brainlabsdigital.com.
How To Create Rule To Control Budget in Google Ads (Adwords)
1. Go to Google Ads panel > Tools and setting > Bulk Actions > Rules
2. Click on the ‘+’ icon to create a new rule
3. Click on ‘Campaign Rules;
4. Give the name to your rule > Select ‘Campaigns in selected accounts’ from apply to section > In condition select ‘campaign status: enabled’ > In Action select select ‘Pause Campaign’
5. In Frequency: Select Daily > Time starting from 12:00 AM – 1:00 AM > Using data from ‘Today’ >Eamil results: only if there are changes or errors and put your email id and then Save Rule.
How To Add Script
6. Now Go The Script > Click on ‘+’ button > New Script
7. Copy and past below the script code (provided at the bottom of this article)
In Code you can change a couple of things as per your requirement:
- Put your email where it is asking ‘id var email =‘
- Put your threshold where it is asking ‘labelThreshold=‘. You can put 1.2, if you are ok to spend 20% more of your daily budget, or you need to put 1.1, if you are ok with a 10% overbudget spent. In case you don’t want to spend a single penny as overbudget, simply make it 1.0
- For ‘campaignPauser =’ set this as ‘true’ if you want to pause the campaigns if spends exceeds the labelThreshold and set this as ‘false’ if you want to keep them enabled
That is it. Now your rule to control the budget in Google Ads (formerly known as Adwords) is set.
Few Things To Care
- The script only functions with display and search campaigns! It is useless for ads including video, retail, or universal apps.
- This script cannot entirely stop you from going over your budget. Since the script only runs once every hour, the campaign’s spending limit may be exceeded in between runs. Additionally, the spend data has a lag of 15 to 20 minutes.
- Campaigns won’t be revived as soon as a new day starts because scheduled scripts don’t run on the hour. Instead, between midnight and 1:00 a.m. on the first run of the script on a fresh day, they will be reactivated. If your campaign doesn’t experience low traffic at this time of day, you might wish to put up automated procedures to unpause operations as soon as the clock strikes midnight.
Below, you will find a script that will allow you to pause campaigns automatically when they consume the budget within a specified period.
/**
*
* Campaign Budget Overspend Monitoring
*
* This script labels campaigns whose spend today is more than their daily
* budgets. Optionally, it also pauses campaigns whose spend exceeds the
* budget by too much. An email is then sent, listing the newly labelled
* and paused campaigns.
* When spend no longer exceeds budget, the campaigns are reactivated and
* labels are removed.
*
* Version: 1.0
* Google AdWords Script maintained on brainlabsdigital.com
*
*/// ////////////////////////////////////////////////////////////////////////////
// Optionsvar campaignNameContains = [];
// Use this if you only want to look at some campaigns.
// For example [“Generic”] would only look at campaigns with ‘generic’ in the name,
// while [“Generic”, “Competitor”] would only look at campaigns with either
// ‘generic’ or ‘competitor’ in the name.
// Leave as [] to include all campaigns.var campaignNameDoesNotContain = [];
// Use this if you want to exclude some campaigns.
// For example [“Brand”] would ignore any campaigns with ‘brand’ in the name,
// while [“Brand”, “Key Terms”] would ignore any campaigns with ‘brand’ or
// ‘key terms’ in the name.
// Leave as [] to not exclude any campaigns.var email = [‘youremail@domain.com’];
// The email address you want the hourly update to be sent to.
// If you’d like to send to multiple addresses then have them separated by commas,
// for example [“aa@example.com”, “bb@example.com”]var currencySymbol = ‘£’;
// Used for formatting in the email.var thousandsSeparator = ‘,’;
// Numbers will be formatted with this as the thousands separator.
// eg If this is “,”, 1000 will appear in the email as 1,000
// If this is “.”, 1000 will appear in the email as 1.000
// If this is “” 1000 will appear as 1000.var decimalMark = ‘.’;
// Numbers will be formatted with this as the decimal mark
// eg if this is “.”, one and a half will appear in the email as 1.5
// and if this is “,” it will be 1,5var labelThreshold = 1.0;
// This is multiplied by the campaign’s daily budget to create a threshold.
// If the campaign spend is higher than the threshold, it will be labelled
// (and you will be emailed).
// For example if labelThreshold = 1.0 then campaigns are labelled when
// their spend is greater than or equal to their budget.var labelName = ‘Over Budget’;
// The name of the label you want to apply to campaigns that have gone
// over budget.var campaignPauser = false;
// Set this to true to pause campaigns if spend exceeds the pauseThreshold.
// Set to false if campaigns are to be kept enabled.var pauseThreshold = 1.2;
// This is multiplied by the campaign’s daily budget to create a threshold.
// If campaignPauser is true and the campaign spend is higher than this threshold,
// it will be paused (and you will be emailed).
// For example if pauseThreshold = 1.2 then campaigns are paused when
// their spend is greater than or equal to 120% of their budget.
// pauseThreshold MUST be greater than or equal to the labelThreshold, so that all
// campaigns that the script pauses are also labelled.// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Functionsfunction main() {
checkInputs();// Get the campaign IDs (based on campaignNameDoesNotContain and campaignNameContains)
var campaignData = getCampaignIds();var campaignsToAddLabel = [];
var campaignsToRemoveLabel = [];
var campaignsToPause = [];
var campaignsToEnable = [];for (var campaignId in campaignData) {
var currentCampaign = campaignData[campaignId];var budget = currentCampaign.Budget;
var status = currentCampaign.CampaignStatus;
var spend = currentCampaign.Spend;
var label = currentCampaign.Labels;var pauseBudgetCap = pauseThreshold * budget;
var labelBudgetCap = labelThreshold * budget;if (status == ‘enabled’ && label.indexOf(‘”‘ + labelName + ‘”‘) == -1) {
if (spend >= labelBudgetCap) {
campaignsToAddLabel.push(currentCampaign);
}
}
if (status == ‘enabled’) {
if (spend >= pauseBudgetCap) {
campaignsToPause.push(currentCampaign);
}
}
if (status == ‘paused’ && label.indexOf(‘”‘ + labelName + ‘”‘) != -1) {
if (spend <= pauseBudgetCap) {
campaignsToEnable.push(currentCampaign);
}
}
if (status == ‘enabled’ && label.indexOf(‘”‘ + labelName + ‘”‘) != -1) {
if (spend <= labelBudgetCap) {
campaignsToRemoveLabel.push(currentCampaign);
}
}
}// Change and update campaigns
if (campaignsToEnable.length > 0 && campaignPauser === true) {
Logger.log(campaignsToEnable.length + ‘ campaigns to enable’);
enableCampaigns(campaignsToEnable);
}
if (campaignsToRemoveLabel.length > 0) {
Logger.log(campaignsToRemoveLabel.length + ‘ campaigns to unlabel’);
removeLabel(campaignsToRemoveLabel);
}
if (campaignsToAddLabel.length > 0) {
Logger.log(campaignsToAddLabel.length + ‘ campaigns to label’);
addLabel(campaignsToAddLabel);
}
if (campaignsToPause.length > 0 && campaignPauser === true) {
Logger.log(campaignsToPause.length + ‘ campaigns to pause’);
pauseCampaigns(campaignsToPause);
}// Send an email, if actions were taken
sendSummaryEmail(campaignsToAddLabel, campaignsToPause, campaignPauser, email);
}// Check the inputs
function checkInputs() {
if (!isValidNumber(labelThreshold)) {
throw “labelThreshold ‘” + labelThreshold + “‘ is not a valid, positive number.”;
}
if (labelThreshold > 2) {
Logger.log(“Warning: labelThreshold ‘” + labelThreshold + “‘ is greater than 2. As AdWords does not spend more than twice the budget, this threshold will not be reached.”);
}
if (campaignPauser) {
if (!isValidNumber(pauseThreshold)) {
throw “pauseThreshold ‘” + pauseThreshold + “‘ is not a valid, positive number.”;
}
if (pauseThreshold < labelThreshold) {
throw “pauseThreshold ‘” + pauseThreshold + “‘ is less than labelThreshold ‘” + labelThreshold + “‘. It should be greater than or equal to labelThreshold.”;
}
if (pauseThreshold > 2) {
Logger.log(“Warning: pauseThreshold ‘” + pauseThreshold + “‘ is greater than 2. As AdWords does not spend more than twice the budget, this threshold will not be reached.”);
}
}
}// Checks the input is a number that’s finite and greater than zero
function isValidNumber(number) {
var isANumber = !isNaN(number) && isFinite(number);
var isPositive = number > 0;
return isANumber && isPositive;
}// Get the IDs of campaigns which match the given options
function getCampaignIds() {
var whereStatement = “WHERE CampaignStatus IN [‘ENABLED’,’PAUSED’] “;
var whereStatementsArray = [];
var campData = {};for (var i = 0; i < campaignNameDoesNotContain.length; i++) {
whereStatement += “AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE ‘” + campaignNameDoesNotContain[i].replace(/”/g, ‘\\\”‘) + “‘ “;
}if (campaignNameContains.length == 0) {
whereStatementsArray = [whereStatement];
} else {
for (var i = 0; i < campaignNameContains.length; i++) {
whereStatementsArray.push(whereStatement + ‘AND CampaignName CONTAINS_IGNORE_CASE “‘ + campaignNameContains[i].replace(/”/g, ‘\\\”‘) + ‘” ‘);
}
}for (var i = 0; i < whereStatementsArray.length; i++) {
var report = AdWordsApp.report(
‘SELECT CampaignId, CampaignName, CampaignStatus, Amount, Labels, LabelIds, Cost ‘
+ ‘FROM CAMPAIGN_PERFORMANCE_REPORT ‘
+ whereStatementsArray[i]
+ “AND AdvertisingChannelType IN [‘SEARCH’, ‘DISPLAY’] ”
+ ‘DURING TODAY’
);var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var budget = parseFloat(row.Amount.replace(/,/g, ”));
var spend = parseFloat(row.Cost.replace(/,/g, ”));
var campaignId = row.CampaignId;campData[campaignId] = {
CampaignId: campaignId,
CampaignName: row.CampaignName,
CampaignStatus: row.CampaignStatus,
Spend: spend,
Budget: budget,
Labels: row.Labels,
LabelId: row.LabelIds
};
}
}var campaignIds = Object.keys(campData);
if (campaignIds.length == 0) {
throw (‘No campaigns found with the given settings.’);
}
Logger.log(campaignIds.length + ‘ campaigns found’);return campData;
}// Create the label if it doesn’t exist, and return its ID.
// (Returns a dummy ID if the label does not exist and this is a preview run,
// because we can’t create or apply the label)
function getOrCreateLabelId(labelName) {
var labels = AdWordsApp.labels().withCondition(“Name = ‘” + labelName + “‘”).get();if (!labels.hasNext()) {
AdWordsApp.createLabel(labelName);
labels = AdWordsApp.labels().withCondition(“Name = ‘” + labelName + “‘”).get();
}if (AdWordsApp.getExecutionInfo().isPreview() && !labels.hasNext()) {
var labelId = 0;
} else {
var labelId = labels.next().getId();
}
return labelId;
}// Pause Campaigns
function pauseCampaigns(campaignData) {
for (j = 0; j < campaignData.length; j++) {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(‘CampaignId = ‘ + campaignData[j].CampaignId)
.get();
if (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.pause();
campaignData[j].CampaignStatus = ‘paused’;
}
}
}// Enable Campaigns that were paused
function enableCampaigns(campaignData) {
for (j = 0; j < campaignData.length; j++) {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(‘CampaignId = ‘ + campaignData[j].CampaignId)
.get();
if (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.enable();
campaignData[j].CampaignStatus = ‘enabled’;
}
}
}// Add Labels to campaigns
function addLabel(campaignData) {
for (j = 0; j < campaignData.length; j++) {
var campaign = AdWordsApp.campaigns()
.withCondition(‘CampaignId = ‘ + campaignData[j].CampaignId).get().next();
getOrCreateLabelId(labelName);
campaign.applyLabel(labelName);
}
}// Remove Labels from campaigns
function removeLabel(campaignData) {
for (j = 0; j < campaignData.length; j++) {
var campaign = AdWordsApp.campaigns()
.withCondition(‘CampaignId = ‘ + campaignData[j].CampaignId).get().next();
campaign.removeLabel(labelName);
}
}// Combines information on labelled and paused campaigns and emails this
function sendSummaryEmail(campaignsToAddLabel, campaignsToPause, campaignPauser, email) {
var localDate = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), ‘yyyy-MM-dd’);
var localTime = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), ‘HH:mm’);// Assemble the email message
var subject = AdWordsApp.currentAccount().getName() + ‘ – Budget Overspend Email’;
var message = ”;
message += makeChangeMessage(campaignsToAddLabel, ‘labelled’);
if (campaignPauser) {
message += makeChangeMessage(campaignsToPause, ‘paused’);
}
if (message == ”) {
Logger.log(‘No message to send.’);
return;
}
message = localDate + ‘ at ‘ + localTime + ‘ :’ + message;
MailApp.sendEmail({
to: email.join(‘,’),
subject: subject,
htmlBody: message
});
Logger.log(‘Message to ‘ + email.join(‘,’) + ‘ sent.’);
}// Turns campaign data into an HTML table
function makeChangeMessage(campaignData, changed) {
if (campaignData.length == 0) {
return ”;
}
var message = ‘<br><br>Campaigns that have been ‘ + changed + ‘<br><br>’;
var table = “<table border=1 style=’border: 1px solid black; border-collapse: collapse;’>”;
table += ‘<tr><th>Campaign ID</th><th>Campaign Name</th><th>Status</th><th>Spend</th><th>Budget</th></tr>’;
for (var k = 0; k < campaignData.length; k++) {
table += ‘<tr><td>’ + campaignData[k].CampaignId + ‘</td><td>’ + campaignData[k].CampaignName + ‘</td><td>’
+ campaignData[k].CampaignStatus + ‘</td><td>’ + formatNumber(campaignData[k].Spend, true) + ‘</td><td>’ + formatNumber(campaignData[k].Budget, true) + ‘</td>’;
table += ‘</tr>’;
}
table += ‘</table>’;
message += table;
return message;
}// Formats a number with the specified thousand separator and decimal mark
// Adds the currency symbol and two decimal places if isCurrency is true
function formatNumber(number, isCurrency) {
if (isCurrency) {
var formattedNumber = number.toFixed(2);
formattedNumber = formattedNumber.substr(0, formattedNumber.length – 3);
formattedNumber = formattedNumber.split(”).reverse().join(”).replace(/(…)/g, ‘$1 ‘)
.trim()
.split(”)
.reverse()
.join(”)
.replace(/ /g, thousandsSeparator);
formattedNumber = currencySymbol + formattedNumber + decimalMark + number.toFixed(2).substr(-2);
} else {
var formattedNumber = number.toFixed(0).split(”).reverse().join(”)
.replace(/(…)/g, ‘$1 ‘)
.trim()
.split(”)
.reverse()
.join(”)
.replace(/ /g, thousandsSeparator);
}
return formattedNumber;
}