Page Summary
-
The script allows dynamic adjustment of Google Ads campaign budgets daily according to a custom distribution scheme.
-
This is useful for marketing initiatives with fixed total costs over a period, where the standard daily budget setting is insufficient for flexible spend distribution.
-
The script includes test code to simulate different budget strategies before implementation.
-
Two prebuilt budget strategies are provided:
calculateBudgetEvenlyfor even distribution andcalculateBudgetWeightedfor a distribution that favors the end of the campaign period. -
Users can define their own budget allocation function and apply it by modifying the script's configuration and main function.

Google Ads lets you set a daily budget amount for each campaign. However, some marketing initiatives will have a fixed cost associated with them; for example, "I want to spend $5000 leading up to our fall sale". The bidding strategy gives you some control over how the daily budget is spent, but no control over how the budget is consumed during the campaign.
For example, if we want to spend only $5000 to advertise our fall sale and we want to advertise for 10 days, we could set a daily budget of $500 to use up the entire budget. However, this assumes that we will spend the entire amount each day AND we want to spend it evenly. It's not possible to tell Google Ads that you want to spend the bulk of your budget during the last few days.
This script will dynamically adjust your campaign budget daily with a custom budget distribution scheme.
How it works
Testing budget strategies
The script includes some test code to simulate the effects of running for multiple days. This gives you a better idea of what might happen when the script is scheduled to run daily over a period of time.
By default, this script simulates an even budget distribution of $500 spent over 10 days.
function
main
()
{
testBudgetStrategy
(
calculateBudgetEvenly
,
10
,
500
);
// setNewBudget(calculateBudgetEvenly, CAMPAIGN_NAME, TOTAL_BUDGET, START_DATE, END_DATE);
}
The setNewBudget
function call is commented out, indicating that it'll only
run the test code. Here is the output from the example:
Day 1.0 of 10.0, new budget 50.0, cost so far 0.0
Day 2.0 of 10.0, new budget 50.0, cost so far 50.0
Day 3.0 of 10.0, new budget 50.0, cost so far 100.0
Day 4.0 of 10.0, new budget 50.0, cost so far 150.0
Day 5.0 of 10.0, new budget 50.0, cost so far 200.0
Day 6.0 of 10.0, new budget 50.0, cost so far 250.0
Day 7.0 of 10.0, new budget 50.0, cost so far 300.0
Day 8.0 of 10.0, new budget 50.0, cost so far 350.0
Day 9.0 of 10.0, new budget 50.0, cost so far 400.0
Day 10.0 of 10.0, new budget 50.0, cost so far 450.0
Day 11.0 of 10.0, new budget 0.0, cost so far 500.0
Each day the script calculates a new budget to make sure that budget spend is evenly distributed. When the allotted budget limit is reached, the budget is set to zero, halting spend.
You can change the budget strategy used by changing which function is used, or
modifying the function itself. The script comes with two prebuilt strategies: calculateBudgetEvenly
and calculateBudgetWeighted
. To set a weighted test
budget strategy, change testBudgetStrategy
like so:
testBudgetStrategy
(
calculateBudgetWeighted
,
10
,
500
);
Click Previewand check the logger output. Notice that this budget strategy allocates less budget early in the period and more during the last few days.
You can use this test method to simulate changes to the budget calculation functions and try your own approach to distributing a budget.
Allocate a budget
The calculateBudgetWeighted
budget strategy is implemented through the
following function:
function
calculateBudgetWeighted
(
costSoFar
,
totalBudget
,
daysSoFar
,
totalDays
)
{
const
daysRemaining
=
totalDays
-
daysSoFar
;
const
budgetRemaining
=
totalBudget
-
costSoFar
;
if
(
daysRemaining
< =
0
)
{
return
budgetRemaining
;
}
else
{
return
budgetRemaining
/
(
2
*
daysRemaining
-
1
)
;
}
}
This function takes these arguments:
-
costSoFar - Campaign's accrued cost from
START_DATEto today. -
totalBudget - Allocated spend from
START_DATEtoEND_DATE. -
daysSoFar - Days elapsed from
START_DATEto today. -
totalDays - Total number of days between
START_DATEandEND_DATE.
You can write your own function as long as it takes these arguments. Using these values, you can compare how much money you've spent so far against how much to spend overall and determine where you are within the timeline for the entire budget.
In particular, this budget strategy figures out how much budget remains
( totalBudget - costSoFar
) and divides that by twice the number of days
remaining. This weighs the budget distribution towards the end of the
campaign. By using the cost since START_DATE
, it also takes into account "slow
days" where the set budget is not entirely spent.
Budget for real
Once you're happy with your budget strategy, you need to make a few changes before scheduling this script to run daily.
First, update the constants at the top of the file:
-
START_DATE: Set this to the start of your budget strategy. This should be the current date or a day in the past. -
END_DATE: Set this to the last day you want to advertise with this budget. -
TOTAL_BUDGET: The total amount you're trying to spend. This value is in account currency and might be exceeded depending on when the script is scheduled to run. -
CAMPAIGN_NAME: The name of the campaign to apply the budget strategy to.
Next, disable the test and enable the logic to actually change the budget:
function
main
()
{
// testBudgetStrategy(calculateBudgetEvenly, 10, 500);
setNewBudget
(
calculateBudgetWeighted
,
CAMPAIGN_NAME
,
TOTAL_BUDGET
,
START_DATE
,
END_DATE
);
}
Scheduling
Schedule this script to run daily, at or shortly after midnight in the local
time zone so as to direct as much as possible the upcoming day's budget. Note,
however, that retrieved reports data such as cost could be delayed by about 3
hours, so the costSoFar
parameter might be referencing yesterday's total for a
script that is scheduled to run after midnight.
Setup
-
Click the button to create the script in your Google Ads account.
-
Save the script and click the Previewbutton. This script (by default) simulates a budget strategy with $500 over 10 days. The logger output reflects the day being simulated, the allocated budget for that day, and the total amount spent to date.
Source code
// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @name Flexible Budgets
*
* @overview The Flexible budgets script dynamically adjusts campaign budget for
* an advertiser account with a custom budget distribution scheme on a daily
* basis. See
* https://developers.google.com/google-ads/scripts/docs/solutions/flexible-budgets
* for more details.
*
* @author Google Ads Scripts Team [adwords-scripts@googlegroups.com]
*
* @version 2.1
*
* @changelog
* - version 2.1
* - Split into info, config, and code.
* - version 2.0
* - Updated to use new Google Ads scripts features.
* - version 1.0.3
* - Add support for video and shopping campaigns.
* - version 1.0.2
* - Use setAmount on the budget instead of campaign.setBudget.
* - version 1.0.1
* - Improvements to time zone handling.
* - version 1.0
* - Released initial version.
*/
/**
* Configuration to be used for the Flexible Budgets script.
*/
CONFIG
=
{
'total_budget'
:
500
,
'campaign_name'
:
'Special Promotion'
,
'start_date'
:
'November 1, 2021 0:00:00 -0500'
,
'end_date'
:
'December 1, 2021 0:00:00 -0500'
};
const
TOTAL_BUDGET
=
CONFIG
.
total_budget
;
const
CAMPAIGN_NAME
=
CONFIG
.
campaign_name
;
const
START_DATE
=
new
Date
(
CONFIG
.
start_date
);
const
END_DATE
=
new
Date
(
CONFIG
.
end_date
);
function
main
()
{
testBudgetStrategy
(
calculateBudgetEvenly
,
10
,
500
);
// setNewBudget(calculateBudgetEvenly, CAMPAIGN_NAME, TOTAL_BUDGET,
// START_DATE, END_DATE);
}
function
setNewBudget
(
budgetFunction
,
campaignName
,
totalBudget
,
start
,
end
)
{
const
today
=
new
Date
();
if
(
today
<
start
)
{
console
.
log
(
'Not ready to set budget yet'
);
return
;
}
const
campaign
=
getCampaign
(
campaignName
);
const
costSoFar
=
campaign
.
getStatsFor
(
getDateStringInTimeZone
(
'yyyyMMdd'
,
start
),
getDateStringInTimeZone
(
'yyyyMMdd'
,
end
)).
getCost
();
const
daysSoFar
=
datediff
(
start
,
today
);
const
totalDays
=
datediff
(
start
,
end
);
const
newBudget
=
budgetFunction
(
costSoFar
,
totalBudget
,
daysSoFar
,
totalDays
);
campaign
.
getBudget
().
setAmount
(
newBudget
);
}
function
calculateBudgetEvenly
(
costSoFar
,
totalBudget
,
daysSoFar
,
totalDays
)
{
const
daysRemaining
=
totalDays
-
daysSoFar
;
const
budgetRemaining
=
totalBudget
-
costSoFar
;
if
(
daysRemaining
< =
0
)
{
return
budgetRemaining
;
}
else
{
return
budgetRemaining
/
daysRemaining
;
}
}
function
calculateBudgetWeighted
(
costSoFar
,
totalBudget
,
daysSoFar
,
totalDays
)
{
const
daysRemaining
=
totalDays
-
daysSoFar
;
const
budgetRemaining
=
totalBudget
-
costSoFar
;
if
(
daysRemaining
< =
0
)
{
return
budgetRemaining
;
}
else
{
return
budgetRemaining
/
(
2
*
daysRemaining
-
1
);
}
}
function
testBudgetStrategy
(
budgetFunc
,
totalDays
,
totalBudget
)
{
let
daysSoFar
=
0
;
let
costSoFar
=
0
;
while
(
daysSoFar
< =
totalDays
+
2
)
{
const
newBudget
=
budgetFunc
(
costSoFar
,
totalBudget
,
daysSoFar
,
totalDays
);
console
.
log
(
`Day
${
daysSoFar
+
1
}
of
${
totalDays
}
, new budget `
+
`
${
newBudget
}
, cost so far
${
costSoFar
}
`
);
costSoFar
+=
newBudget
;
daysSoFar
+=
1
;
}
}
/**
* Returns number of days between two dates, rounded up to nearest whole day.
*/
function
datediff
(
from
,
to
)
{
const
millisPerDay
=
1000
*
60
*
60
*
24
;
return
Math
.
ceil
((
to
-
from
)
/
millisPerDay
);
}
function
getDateStringInTimeZone
(
format
,
date
,
timeZone
)
{
date
=
date
||
new
Date
();
timeZone
=
timeZone
||
AdsApp
.
currentAccount
().
getTimeZone
();
return
Utilities
.
formatDate
(
date
,
timeZone
,
format
);
}
/**
* Finds a campaign by name, whether it is a regular, video, or shopping
* campaign, by trying all in sequence until it finds one.
*
* @param {string} campaignName The campaign name to find.
* @return {Object} The campaign found, or null if none was found.
*/
function
getCampaign
(
campaignName
)
{
const
selectors
=
[
AdsApp
.
campaigns
(),
AdsApp
.
videoCampaigns
(),
AdsApp
.
shoppingCampaigns
()];
for
(
const
selector
of
selectors
)
{
const
campaignIter
=
selector
.
withCondition
(
`CampaignName = "
${
campaignName
}
"`
)
.
get
();
if
(
campaignIter
.
hasNext
())
{
return
campaignIter
.
next
();
}
}
throw
new
Error
(
`Could not find specified campaign:
${
campaignName
}
`
);
}

