Google Ads Script – Monthly Budget Script Nordic Branch

Introduction

Discover the future of Google Ads campaign management with our new automation script, meticulously developed by Joel Björnfors from NordicBranch. This script revolutionizes the way you manage your advertising budget by dynamically allocating campaign funds based on a set total account budget. It’s designed to ensure you hit your spending targets effectively and efficiently without constant manual adjustments.

The Purpose of the Script

This automation script is engineered to take the stress out of managing your Google Ads budgets. By setting a predefined total account budget, the script dynamically distributes funds across your campaigns throughout the month. Whether you’re dealing with under-spending or over-spending early in the month, the script adjusts each campaign’s budget to ensure your advertising goals are met without surpassing your overall budget limit.

Key Features

  • Dynamic Budget Management: The script automatically adjusts budgets daily based on your spending goals and actual expenditures, ensuring optimal use of your advertising budget.
  • Total Account Budget Setting: Enter your total desired spend for the month, and the script handles the rest, dynamically adjusting individual campaign budgets.
  • Adaptability: Adjusts to real-time campaign performance and spending, providing flexibility and precision in budget allocation.

Benefits of Using the Script

  • Effortless Budget Management: Once the total budget is set, the script manages individual campaign budgets, eliminating the need to manually track and adjust spending.
  • Consistent Spending: Ensures your advertising spend is on target with your strategic goals, automatically correcting under-spending or over-spending.
  • Peace of Mind: Reduces the workload and stress associated with daily budget management, allowing you to focus on strategic decision-making and optimizing campaign performance.

Implementing the Script

To implement the Google Ads automation script:

  1. Access the Scripts Section: Go to the ‘Bulk Actions’ > ‘Scripts’ in your Google Ads account.
  2. Add the Script: Copy the script code provided and paste it into a new script in your Google Ads interface.
  3. Configure and Run: Set your total monthly budget in the script’s configuration, authorize it, and run the script to start automating your budget distribution.

Conclusion

Leverage the power of automation to simplify your Google Ads campaigns with our dynamic budget management script. By ensuring that your spending aligns seamlessly with your goals, this tool allows you to maximize the effectiveness of your digital advertising efforts. Start optimizing today for a stress-free management experience. For assistance and more detailed setup instructions, contact us directly at NordicBranch.

The script

/** CONFIG PART
 * ------------------------------------------------------------------------------------------
 * Google Ads Automation Script by Joel Björnfors - NordicBranch
 * ------------------------------------------------------------------------------------------
 * Welcome to advanced Google Ads scripting, crafted by Joel Björnfors from NordicBranch,
 * where cutting-edge digital marketing meets precision and personalization.
 * 
 * This script is made to make sure you dont have to worry about spending you monthly budgets, 
 * if the spend is to low in the begining, then the budgets will increase for the remaning period, same if to much is spent at the start
 * if more campaigns are created, the budget will be destributed to those aswell so you dont have to worry.
 * 
 *  I will improve this script going forward with features like: 
 *    - distribute budget based on ROAS
 *    - dynamic budget where the total budget is dynamic and you set a target ROAS for the period
 *    - support for DEMAND GEN / Discovery campaigns (cant edit them with scripts as of April 2024)
 *    - MCC version of the script
 *    - other things that might be usefull
 *    
 *
 * At NordicBranch, we specialize in transforming Google Ads performance with custom automation,
 * leveraging deep industry knowledge and technical expertise to drive superior ROI.
 * This script embodies our commitment to excellence and innovation in digital advertising.
 *
 * Why Choose Joel Björnfors and NordicBranch?
 * - Expertise: Deep understanding of Google Ads management and optimization.
 * - Customization: Tailored solutions that match your specific business needs.
 * - Performance: Focused on delivering measurable results and enhancing campaign efficiency.
 * - Support: Proactive communication and ongoing support to ensure your campaigns succeed.
 *
 * For more information, visit our website: https://nordicbranch.com
 * Doing google Shopping? Save 20% on your bids or gain 20% more clicks by using our CSS https://nordicbranch.shop/api/signup
 *
 *
 * Have feature suggestions or want to contact me?
 * 
 * Contact info
 * Mail: Joel@nordicbranch.com
 * Linkedin : www.linkedin.com/in/joel-björnfors-06628960
 * Website : https://nordicbranch.com
 * CSS site: https://nordicbranch.shop
 * 
 *
 * Connect with me for bespoke digital marketing solutions that propel your business forward.
 * ------------------------------------------------------------------------------------------
 */

// Global Configuration with inclusion flags and percentages

var defaults = {
    'include_performance_max': true,                                                        //include pmax campaigns in both cost and budget calculations
    'include_search': true,                                                                 //include Search campaigns in both cost and budget calculations
    'include_shopping': true,                                                               //include Shopping campaigns in both cost and budget calculations
    'include_display': false,                                                               //include Display campaigns in both cost and budget calculations
    'include_video': false,                                                                 //include Video campaigns in both cost and budget calculations
    'include_discovery': false,                                                             //include discovery/demandgen campaigns in both cost and budget calculations, this feature is not supported currently
    'total_budget': 100000,                                                                  // Total budget as default, this will be used as monthly budget for the current month as default 
    'start_date': getFirstDayCurrentMonthNordicBranchJoel(),                                                // defaults to first day of current month
    'end_date': getLastDayCurrentMonthNordicBranchJoel(),                                                   // defaults to last day of current month
    'performance_max_budget_percentage': 0.6,                                               // percentage of the total budget that should be allocated to pmax campaigns 
    'search_budget_percentage': 0.3,                                                        // this can be set as 0.6 or 60, there is a normalisation factor added 
    'shopping_budget_percentage': 0.1,                                                      // so if pmax is set to 2.0 and seach is set to 1.0 and all others are not included
    'display_budget_percentage': 0,                                                         // then the pmax budget will be 2/3 of the total budget and search 1/3
    'video_budget_percentage': 0,                                                           // it is also possible to set budgets to for example pmax 50000 and search 20000 and shopping 20000 because of this normalisation factor
    'discovery_budget_percentage': 0,                                                       // but the sum needs to be the same as the same as the total budget for this to work correctly  
    'split_evenly_percentage': 0.9                                                          // percentage of the budget per campaign type that will be evenly distributed across the campaigns, if you have 3 pmax campaigns and 10k budget for them with 0.9 split, each campaign would get a minimum of 3k and the last 1k will be split between them based on spend and conversion value % contribution  
}


var ignoreCampaignNames = ''; // ignore campaigns containing this in the name. use comma to apply more then 1 filter and leave empty to ignore, example 'store' to exclude campaigns containing store (case insensetive) or 'store,brand' to exclude both store and brand campaigns.
var MONTHLY_CONFIGS = { 

    // It is possible to have custom periods, for example if you have 100k to spend across 2024-03-25 to 2024-05-07 then you add those start and end dates in both march, april and may (see commented out examples)
    // in may, once the 7:th have passed, the script will loop through each item in the array of the current month and use the first that matches, so it is a good idea to always have the default as the last item each month to keep monthly budgets consistent

  'January': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  'February': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],

  
  'March': [{
  /*  'total_budget': 100000, 'start_date': '2024-03-25', 'end_date': '2024-05-07', 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage,
  },{
    */
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],

  
  'April': [{
    /*  'total_budget': 100000, 'start_date': '2024-03-25', 'end_date': '2024-05-07', 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage,
  },{
    */
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage,
  /*
  },{
    'total_budget': defaults.total_budget/4, 'start_date': '2024-04-01', 'end_date': '2024-04-14', 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage,
  },{
    'total_budget': defaults.total_budget/2, 'start_date': '2024-04-01', 'end_date': '2024-04-20', 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage,
  },{
    'total_budget': defaults.total_budget, 'start_date': '2024-04-01', 'end_date': '2024-05-01', 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  */
}],
  
  
  'May': [{  
    /*  'total_budget': 100000, 'start_date': '2024-03-25', 'end_date': '2024-05-07', 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage,
  },{
    */
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
 
  
  'June': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  
  'July': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  
  'August': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  
  'September': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  
  'October': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  
  'November': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }],
  
  
  'December': [{
    'total_budget': defaults.total_budget, 'start_date': defaults.start_date, 'end_date': defaults.end_date, 'include_performance_max': defaults.include_performance_max, 'include_search': defaults.include_search, 'include_shopping': defaults.include_shopping, 'include_display': defaults.include_display, 'include_video': defaults.include_video, 'include_discovery': defaults.include_discovery, 'performance_max_budget_percentage': defaults.performance_max_budget_percentage, 'search_budget_percentage': defaults.search_budget_percentage, 'shopping_budget_percentage': defaults.shopping_budget_percentage , 'display_budget_percentage': defaults.display_budget_percentage, 'video_budget_percentage': defaults.video_budget_percentage, 'discovery_budget_percentage': defaults.discovery_budget_percentage, 'split_evenly_percentage': defaults.split_evenly_percentage
  }]
};

Code part





var CONFIG = NordicBranchJoelgetCurrentMonthConfig(); // Get the config for the current month

function NordicBranchJoelgetCurrentMonthConfig() {
  Logger.log(defaults.start_date)
    var today = new Date();
    var monthNames = ["January", "February", "March", "April", "May", "June",
                      "July", "August", "September", "October", "November", "December"];
    var currentMonthName = monthNames[today.getMonth()]; // Use local month
    var periods = MONTHLY_CONFIGS[currentMonthName];

    // Normalize today to the beginning of the day in GMT+2
    today.setHours(0 - 2, 0, 0, 0); // Adjust for GMT+2 timezone

    for (var i = 0; i < periods.length; i++) {
        var startDate = new Date(periods[i].start_date + "T00:00:00.000+02:00"); // Start of the period in GMT+2
        var endDate = new Date(periods[i].end_date + "T23:59:59.999+02:00"); // End of the period in GMT+2

        if (today >= startDate && today <= endDate) {
            Logger.log('Current period: ' + NordicBranchJoelformatDateStringForStartAndEnd(startDate) + ' to ' + NordicBranchJoelformatDateStringForStartAndEnd(endDate));
            Logger.log('Thank you for using this script, if you have any questions or need help with your google ads setup please reach out to joel@nordicbranch.com')
            return periods[i];
        }
    }
    Logger.log('No active budget period found for today.');
    return null; // Or return a default configuration
}

/**/





/*
function main() {
  var CONFIG = NordicBranchJoelgetCurrentMonthConfig(); // Get the config for the current month
  var today = new Date();
  var startDate = new Date(CONFIG.start_date + "T00:00:00");
  var endDate = new Date(CONFIG.end_date + "T23:59:59");

  if (today < startDate || today > endDate) {
    Logger.log('Today is not within the budget distribution period.');
    return;
  }
*/
/**/
function main() {
  var CONFIG = NordicBranchJoelgetCurrentMonthConfig(); // Get the config for the current month
  var today = new Date();
  var now = new Date(); // Capture the current time for precise comparison
  var startDate = new Date(CONFIG.start_date + "T00:00:00");
  var endDate = new Date(CONFIG.end_date + "T23:59:59");

  // Adjust the comparison to use the precise current time (now) against the end date
  if (now < startDate || now > endDate) {
 //   Logger.log(NordicBranchJoelformatDateStringForStartAndEnd(now))
 //   Logger.log(NordicBranchJoelformatDateStringForStartAndEnd(startDate))
 //   Logger.log(NordicBranchJoelformatDateStringForStartAndEnd(endDate))
    Logger.log('Today is not within the budget distribution period.');
    return;
  }
   // Use 'today' at the start of the day for consistent date comparison
  today.setHours(0, 0, 0, 0);

 // var normalizationFactor = NordicBranchJoelcalculateNormalizationFactor();
 // var totalDays = NordicBranchJoeldatediff(startDate, endDate) + 1;
  // Ensure daysRemaining is at least 1 on the last day
//  var daysRemaining = Math.max(NordicBranchJoeldatediff(today, endDate) + 1, 1);
  
  /**/
  var normalizationFactor = NordicBranchJoelcalculateNormalizationFactor();
  var totalDays = NordicBranchJoeldatediff(startDate, endDate) + 1;
  var daysRemaining = NordicBranchJoeldatediff(today, endDate) + 1;

  
  var costSoFar = NordicBranchJoelfetchCostSoFar(startDate, today).totalCost;
  var valueSoFar = NordicBranchJoelfetchCostSoFar(startDate, today).totalConversionValue;
  var budgetRemaining = parseFloat(CONFIG.total_budget - costSoFar);
  var dailyBudgetRemaining = budgetRemaining / daysRemaining;
  Logger.log('Spent so far : ' + costSoFar + ' | Remaning budgget : ' + budgetRemaining + ' | Days Remaining: ' + daysRemaining + ' total Days in period: ' + totalDays);
  Logger.log('Value so far: '+valueSoFar);

  // Calculate initial allocations for each campaign type
  var initialAllocations = {
    'PERFORMANCE_MAX': CONFIG.include_performance_max ? dailyBudgetRemaining * (CONFIG.performance_max_budget_percentage / normalizationFactor) : 0,
    'SEARCH': CONFIG.include_search ? dailyBudgetRemaining * (CONFIG.search_budget_percentage / normalizationFactor) : 0,
    'DISPLAY': CONFIG.include_display ? dailyBudgetRemaining * (CONFIG.display_budget_percentage / normalizationFactor) : 0,
    'SHOPPING': CONFIG.include_shopping ? dailyBudgetRemaining * (CONFIG.shopping_budget_percentage / normalizationFactor) : 0,
    'VIDEO': CONFIG.include_video ? dailyBudgetRemaining * (CONFIG.video_budget_percentage / normalizationFactor) : 0,
    'DISCOVERY': CONFIG.include_discovery ? dailyBudgetRemaining * (CONFIG.discovery_budget_percentage / normalizationFactor) : 0
  };
  //  Logger.log('initialAllocations before scaling factor |')
  //  Logger.log(initialAllocations)

  // Sum the initial allocations to determine if they exceed the daily budget remaining
  var totalAllocatedBudget = Object.values(initialAllocations).reduce((sum, allocation) => sum + allocation, 0);
//Logger.log('total allocated Budget  before scaling factor |  '+totalAllocatedBudget)

  // If the total allocated budget exceeds the daily budget remaining, calculate a scaling factor
  var scalingFactor = totalAllocatedBudget > dailyBudgetRemaining ? dailyBudgetRemaining / totalAllocatedBudget : 1;
//Logger.log('scalingFactor | '+scalingFactor)
  // Apply the scaling factor to each campaign type's budget and distribute
for (var campaignType in initialAllocations) {
  if (CONFIG['include_' + campaignType.toLowerCase()]) {
    // Log before applying the scaling factor
   // Logger.log('Applying scaling factor to ' + campaignType + ': ' + scalingFactor);
    
    // Apply the scaling factor
    initialAllocations[campaignType] *= scalingFactor;

    // Log the new budget after scaling
    Logger.log(campaignType + ' new budget after scaling: ' + initialAllocations[campaignType]);

    NordicBranchJoeldistributeBudget(campaignType, initialAllocations[campaignType], startDate, today);
  }
}

}


function NordicBranchJoelgetConversionValueForCampaign(campaignId, startDate, endDate) {
  // Format the start and end dates for the report
  var formattedStartDate = Utilities.formatDate(startDate, AdsApp.currentAccount().getTimeZone(), "yyyyMMdd");
  var formattedEndDate = Utilities.formatDate(endDate, AdsApp.currentAccount().getTimeZone(), "yyyyMMdd");

  // Define the report query
  var reportQuery = "SELECT CampaignId, ConversionValue,SearchImpressionShare, Cost " +
                    "FROM CAMPAIGN_PERFORMANCE_REPORT " +
                    "WHERE CampaignId = '" + campaignId + "' " +
                    "AND CampaignStatus IN ['ENABLED'] " +
                    "DURING " + formattedStartDate + "," + formattedEndDate;

  // Execute the report query
  var report = AdsApp.report(reportQuery);
  var rows = report.rows();

  // Initialize the conversion value
  var conversionValue = 0;
 var searchImpressionShare=0;
  var spend= 0;
  // Iterate over the report rows (should be only one row matching the specific campaign ID)
  if (rows.hasNext()) {
    var row = rows.next();

    conversionValue = parseFloat(row['ConversionValue'].replace(/,/g,""));
    searchImpressionShare = parseFloat(row['SearchImpressionShare'])>0?parseFloat(row['SearchImpressionShare']):0.001;
    spend = parseFloat(row['Cost'].replace(/,/g,""));
  }

  return {conversionValue,searchImpressionShare,spend};
}


function NordicBranchJoeldistributeBudget(campaignType, budgetForType, startDate, today) {
  var CONFIG = NordicBranchJoelgetCurrentMonthConfig(); // Ensures the current configuration is used
  var totalDays = NordicBranchJoeldatediff(new Date(CONFIG.start_date), today);
  var daysRemaining = Math.max(NordicBranchJoeldatediff(today, new Date(CONFIG.end_date)), 1);
  var evenDistributionPercentage = CONFIG.split_evenly_percentage// || 0.2; // 20% for even distribution, adjust as needed

  var campaignIterator = NordicBranchJoelgetCampaignIterator(campaignType);
  var campaigns = [];
  var totalSpend = 0;
  var totalConversionValue = 0;
  var excessBudget = 0;

  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    var conversionData = NordicBranchJoelgetConversionValueForCampaign(campaign.getId(), startDate, today);
    var spend = conversionData.spend;
    var conversionValue = conversionData.conversionValue;
    var searchIS = conversionData.searchImpressionShare / 100;
    var pastCap = ((spend / (searchIS || 0.01)) / totalDays).toFixed(2);
//    Logger.log(`${campaign.getName()} \n pastcap: ${pastCap}:\n spend:${spend}\n searchIS: ${searchIS}\n totalDays: ${totalDays}`)
//    Logger.log(conversionData)
    
    totalSpend += spend;
    totalConversionValue += conversionValue;

    campaigns.push({
      campaign: campaign,
      spend: spend,
      conversionValue: conversionValue,
      searchIS: searchIS,
      pastCap: parseFloat(pastCap*1.2),
      finalBudget: 0 // Placeholder for later calculation
    });
  }

   // Evenly distribute a portion of the budget
  var evenDistributionBudget = budgetForType * evenDistributionPercentage;
  var evenBudgetPerCampaign = evenDistributionBudget / campaigns.length;
  
    // Allocate the rest based on ratios
  var ratioBasedBudget = budgetForType * (1 - evenDistributionPercentage);
  
  
  // Calculate and assign budgets
  
campaigns.forEach(campaign => {
    var spendRatio = campaign.spend / totalSpend;
 //   Logger.log('spendRatio '+spendRatio)

    var conversionValueRatio = campaign.conversionValue / totalConversionValue;
 //   Logger.log('conversionValueRatio '+conversionValueRatio)

    var effectiveness1 = ((spendRatio>0?spendRatio:0.01) + (conversionValueRatio>0?conversionValueRatio:0.01));
      var effectiveness = effectiveness1/2;

//   Logger.log('effectiveness '+effectiveness)
    var ratioBasedAllocation = ratioBasedBudget * effectiveness;
//  Logger.log('evenBudgetPerCampaign '+evenBudgetPerCampaign)
//    Logger.log('ratioBasedAllocation '+ratioBasedAllocation)

    var totalAllocatedBudget = evenBudgetPerCampaign + ratioBasedAllocation;
    
    if (totalAllocatedBudget > campaign.pastCap && campaign.pastCap > 100) {
      excessBudget += totalAllocatedBudget - campaign.pastCap;
 //     Logger.log("1 | "+ campaign.finalBudget)
      campaign.finalBudget = campaign.pastCap;
 //     Logger.log("2 | "+campaign.finalBudget)
    } else {
  //    Logger.log("3 | "+campaign.finalBudget)
      campaign.finalBudget = totalAllocatedBudget;
  //    Logger.log("4 | "+campaign.finalBudget)
    }
  
  campaign.finalBudget = Math.max(campaign.finalBudget, 0.01);
  });

  // Redistribute excess budget if any
  if (excessBudget > 0) {
    var eligibleCampaignsForRedistribution = campaigns.filter(campaign => campaign.finalBudget < campaign.pastCap || campaign.pastCap <= 100);
    let additionalBudgetPerCampaign = excessBudget / eligibleCampaignsForRedistribution.length;
    
    eligibleCampaignsForRedistribution.forEach(campaign => {
   //   Logger.log(`${campaign.campaign.getName()} \n campaign.finalBudget: ${campaign.finalBudget}  \n  additionalBudgetPerCampaign:  ${additionalBudgetPerCampaign}`)
      campaign.finalBudget += additionalBudgetPerCampaign;
      campaign.finalBudget = Math.max(campaign.finalBudget, 0.01); // Ensure no budget below 0.01 after redistribution

 //           Logger.log(`${campaign.campaign.getName()} \n new final budget: ${campaign.finalBudget} `)

    });
  }

  // Apply the final budget to campaigns
  campaigns.forEach(campaign => {
    campaign.campaign.getBudget().setAmount(campaign.finalBudget);
    Logger.log(`Updated budget for ${campaign.campaign.getName()}: ${campaign.finalBudget}`);
  });
}



function NordicBranchJoelgetCampaignIterator(campaignType) {
  Logger.log('Fetching campaigns for type: ' + campaignType);
  
  // Start with an empty base query which will be built upon based on the campaign type
  let campaignQuery;

  switch (campaignType) {
    case 'PERFORMANCE_MAX':
      if (CONFIG.include_performance_max) {
        campaignQuery = AdsApp.performanceMaxCampaigns()
              //            .withCondition("Name DOES_NOT_CONTAIN_IGNORE_CASE 'store'")
                          .withCondition("campaign.serving_status != 'ENDED'")
                          .withCondition("Status = ENABLED");
        campaignQuery = NordicBranchJoelapplyNameExclusionCondition(campaignQuery, ignoreCampaignNames);
      }
      break;
    case 'SEARCH':
      if (CONFIG.include_search) {
        campaignQuery = AdsApp.campaigns()
                          .withCondition("AdvertisingChannelType = SEARCH")
                          .withCondition("Status = ENABLED")
                          .withCondition("campaign.serving_status != 'ENDED'");
        campaignQuery = NordicBranchJoelapplyNameExclusionCondition(campaignQuery, ignoreCampaignNames);
      }
      break;
    case 'DISPLAY':
      if (CONFIG.include_display) {
        campaignQuery = AdsApp.campaigns()
                          .withCondition("AdvertisingChannelType = DISPLAY")
                          .withCondition("Status = ENABLED")
                          .withCondition("campaign.serving_status != 'ENDED'");
        campaignQuery = NordicBranchJoelapplyNameExclusionCondition(campaignQuery, ignoreCampaignNames);
      }
      break;
    case 'SHOPPING':
      if (CONFIG.include_shopping) {
        campaignQuery = AdsApp.shoppingCampaigns()
                          .withCondition("AdvertisingChannelType = SHOPPING")
                          .withCondition("Status = ENABLED")
                          .withCondition("campaign.serving_status != 'ENDED'");
        campaignQuery = NordicBranchJoelapplyNameExclusionCondition(campaignQuery, ignoreCampaignNames);
      }
      break;
    case 'VIDEO':
      if (CONFIG.include_video) {
        campaignQuery = AdsApp.videoCampaigns()
                          .withCondition("Status = ENABLED")
                          .withCondition("campaign.serving_status != 'ENDED'");
        campaignQuery = NordicBranchJoelapplyNameExclusionCondition(campaignQuery, ignoreCampaignNames);
      }
      break;
    case 'DISCOVERY':
      if (CONFIG.include_discovery) {
        campaignQuery = AdsApp.campaigns()
                          .withCondition("AdvertisingChannelType = DISCOVERY")
                          .withCondition("Status = ENABLED")
                          .withCondition("campaign.serving_status != 'ENDED'");
        campaignQuery = NordicBranchJoelapplyNameExclusionCondition(campaignQuery, ignoreCampaignNames);
      }
      break;
    default:
      Logger.log('Campaign type ' + campaignType + ' is not included or unsupported.');
      return { hasNext: function() { return false; } }; // Returning an iterator-like object for consistency
  }

  // Add condition to exclude specific campaign names if ignoreCampaignNames is provided and not empty
  if (ignoreCampaignNames && ignoreCampaignNames.trim() !== "") {
    campaignQuery = campaignQuery.withCondition("Name DOES_NOT_CONTAIN_IGNORE_CASE '" + ignoreCampaignNames + "'");
  }

  return campaignQuery ? campaignQuery.get() : { hasNext: function() { return false; } };
}




function NordicBranchJoelfetchCostSoFar(startDate, today) {
  var totalCost = 0;
  
  var totalConversionValue = 0;

  var campaignIds = NordicBranchJoelgetAllCampaignIds();
  if (campaignIds.length === 0) {
    Logger.log("No campaign IDs found based on CONFIG settings.");
    return {totalCost, totalConversionValue};

//    return totalCost;
  }

  // Format the date strings for the report
  var formattedStartDate = NordicBranchJoelformatDateStringForReport(startDate);
  var formattedEndDate = NordicBranchJoelformatDateStringForReport(today);

  // Update the AWQL query to use the formatted campaign IDs
var reportQuery = "SELECT CampaignId, CampaignName, AdvertisingChannelType, Cost, ConversionValue " +
                  "FROM CAMPAIGN_PERFORMANCE_REPORT " +
                  "WHERE CampaignStatus IN ['ENABLED'] " +
                  "AND CampaignId IN [" + campaignIds.join(',') + "] " +
                  "DURING " + formattedStartDate + "," + formattedEndDate;


  var report = AdsApp.report(reportQuery);

  
  var rows = report.rows();
  
  while (rows.hasNext()) {
    var row = rows.next();
    var costWithoutCommas = row['Cost'].replace(/,/g, '');
    var parsedCost = parseFloat(costWithoutCommas);
    var valueWithoutCommas = row['ConversionValue'].replace(/,/g, '');    
    var parsedValue = parseFloat(valueWithoutCommas);
    totalConversionValue += parsedValue

    totalCost += parsedCost;
   // totalCost += parseFloat(row['Cost']);
   //   Logger.log(row['CampaignName'] + '| parseFloated cost = '+ parseFloat(row['Cost'])+ '| none parsed cost = '+ row['Cost']+ '| new parsd cost = '+ parsedCost)
  }
  return {totalCost, totalConversionValue};
//  return totalCost;
}


function NordicBranchJoelgetAllCampaignIds() {
  var campaignIds = [];
  var campaignNames = []; // This seems unused except for logging purposes, consider removing if not needed



  // For Performance Max Campaigns, excluding those with 'store' in the name
  if (CONFIG.include_performance_max) {
    var pmaxCampaigns = NordicBranchJoelapplyNameExclusionCondition(
      AdsApp.performanceMaxCampaigns()
        .withCondition("Status = ENABLED")
//        .withCondition("Name DOES_NOT_CONTAIN_IGNORE_CASE 'store'")
    ).get();

    while (pmaxCampaigns.hasNext()) {
      campaignIds.push(pmaxCampaigns.next().getId());
    }
  }

  // Other campaign types follow the same pattern
  if (CONFIG.include_search) {
    var searchCampaigns = NordicBranchJoelapplyNameExclusionCondition(
      AdsApp.campaigns()
        .withCondition("AdvertisingChannelType = SEARCH")
        .withCondition("Status = ENABLED")
    ).get();

    while (searchCampaigns.hasNext()) {
      campaignIds.push(searchCampaigns.next().getId());
    }
  }

  if (CONFIG.include_display) {
    var displayCampaigns = NordicBranchJoelapplyNameExclusionCondition(
      AdsApp.campaigns()
        .withCondition("AdvertisingChannelType = DISPLAY")
        .withCondition("Status = ENABLED")
    ).get();

    while (displayCampaigns.hasNext()) {
      campaignIds.push(displayCampaigns.next().getId());
    }
  }

  if (CONFIG.include_video) {
    var videoCampaigns = NordicBranchJoelapplyNameExclusionCondition(
      AdsApp.videoCampaigns()
        .withCondition("AdvertisingChannelType = VIDEO")
        .withCondition("Status = ENABLED")
    ).get();

    while (videoCampaigns.hasNext()) {
      campaignIds.push(videoCampaigns.next().getId());
    }
  }

  if (CONFIG.include_shopping) {
    var shoppingCampaigns = NordicBranchJoelapplyNameExclusionCondition(
      AdsApp.shoppingCampaigns()
        .withCondition("Status = ENABLED")
    ).get();

    while (shoppingCampaigns.hasNext()) {
      var campaign = shoppingCampaigns.next();
      campaignNames.push(campaign.getName()); // Note this is just for logging, remove if not needed
      campaignIds.push(campaign.getId());
    }
  }

  if (CONFIG.include_discovery) {
    var discoveryCampaigns = NordicBranchJoelapplyNameExclusionCondition(
      AdsApp.campaigns()
        .withCondition("AdvertisingChannelType = DISCOVERY")
        .withCondition("Status = ENABLED")
    ).get();

    while (discoveryCampaigns.hasNext()) {
      var campaign = discoveryCampaigns.next();
      campaignNames.push(campaign.getName()); // Note this is just for logging, remove if not needed
      campaignIds.push(campaign.getId());
    }
  }

  return campaignIds;
}




function NordicBranchJoelcalculateNormalizationFactor() {
  var enabledTypes = ['include_performance_max', 'include_search', 'include_display', 'include_shopping', 'include_video','include_discovery'];
  var totalPercentage = 0;

  enabledTypes.forEach(function(type) {
    if (CONFIG[type]) {
      totalPercentage += CONFIG[type.replace('include_', '') + '_budget_percentage'];

    }
  });
  var test= totalPercentage > 0 ? 1 / totalPercentage : 0;
  // Logger.log('totalPercentage ' + totalPercentage)
  //Logger.log('NordicBranchJoelcalculateNormalizationFactor ' + test)
 

  return totalPercentage > 0 ? 1 / totalPercentage : 0;
}


/*
function NordicBranchJoeldatediff(first, second) {
  // Take the difference between the dates and divide by milliseconds per day.
  // Round to nearest whole number to deal with DST and partial days.
  var millisPerDay = 24 * 60 * 60 * 1000; // Hours * Minutes * Seconds * Milliseconds
  return Math.round((second - first) / millisPerDay);
}
*/
function NordicBranchJoeldatediff(first, second) {
  // Instead of rounding, calculate the exact difference and then floor to get whole days.
  var millisPerDay = 24 * 60 * 60 * 1000; // Hours * Minutes * Seconds * Milliseconds
//  Logger.log('date diff : '+ Math.floor((second - first) / millisPerDay))
  return Math.floor((second - first) / millisPerDay);
}



function NordicBranchJoelformatDateStringForReport(date) {
  // Google Ads scripts use the account's time zone for date-based operations.
  // This ensures consistency with the reporting data.
  var timeZone = AdsApp.currentAccount().getTimeZone();
  return Utilities.formatDate(date, timeZone, "yyyyMMdd");
}

function NordicBranchJoelformatDateStringForStartAndEnd(date) {
    // Set the timezone to GMT+2
    var timeZone = "GMT+02:00";  // Adjust this if there is a specific city or timezone ID more appropriate for your location
    return Utilities.formatDate(date, timeZone, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
}


  // Helper function to conditionally add name exclusion if ignoreCampaignNames is not empty
function NordicBranchJoelapplyNameExclusionCondition(campaignQuery) {
    if (ignoreCampaignNames && ignoreCampaignNames.trim() !== "") {
        var namesToIgnore = ignoreCampaignNames.split(',').map(name => name.trim()); // Split and trim names
        namesToIgnore.forEach(name => {
            if (name !== "") {
                campaignQuery = campaignQuery.withCondition("Name DOES_NOT_CONTAIN_IGNORE_CASE '" + name + "'");
            }
        });
    }
    return campaignQuery;
}

Current_dates

/**
 * Get the current year.
 * @returns {number} The current year.
 */
function getCurrentYearNordicBranchJoel() {
    return new Date().getFullYear();
}

/**
 * Get the current month formatted as a two-digit string.
 * @returns {string} The current month in "mm" format.
 */
function getCurrentMonthNordicBranchJoel() {
    var month = new Date().getMonth() + 1;  // January is 0!
    return month < 10 ? '0' + month : '' + month; // Pad with zero if necessary
}

/**
 * Get the first day of the current month formatted as "yyyy-mm-dd".
 * @returns {string} The first day of the current month in "yyyy-mm-dd" format.
 */
function getFirstDayCurrentMonthNordicBranchJoel() {
    var year = getCurrentYearNordicBranchJoel();
    var month = getCurrentMonthNordicBranchJoel();
    return `${year}-${month}-01`; // First day is always "01"
}

/**
 * Get the last day of the current month formatted as "yyyy-mm-dd".
 * @returns {string} The last day of the current month in "yyyy-mm-dd" format.
 */
function getLastDayCurrentMonthNordicBranchJoel() {
    var date = new Date();
    var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); // Get last day of the current month
    var year = getCurrentYearNordicBranchJoel();
    var month = getCurrentMonthNordicBranchJoel();
    return `${year}-${month}-${lastDay < 10 ? '0' + lastDay : lastDay}`; // Ensure the day is two digits
}

// Example usage:
//console.log("Current Year: " + getCurrentYearNordicBranchJoel());
//console.log("Current Month: " + getCurrentMonthNordicBranchJoel());
//console.log("First Day of Current Month: " + getFirstDayCurrentMonthNordicBranchJoel());
//console.log("Last Day of Current Month: " + getLastDayCurrentMonthNordicBranchJoel());
Scroll to Top