Because campaign performance can vary a lot per device, it's possible to make bid adjustments for desktop, mobile, tablet & TV. We often use this when there is a significant difference in the CPA between the different devices. But instead of checking all campaigns every week, we have automated this process.

We also have scripts for bidadjustments for audiences, locations or adschedule. Start automating all of your bidadjustments today.

In the script you can choose to include search, display and shopping campaigns in the bid adjustments. You can only include display campaigns, when search campaigns are included. In addition, the script only looks at campaigns with a manual bidding strategy, because bid adjustments at Smart Bidding already happen automatically. To gain control, a minimum number of conversions and a minimum amount of spend can be specified to filter devices per campaign. You're also able to set a maximum and minimum bidadjustment. 

Settings

In the script you can adjust various settings as desired:

  • LOG: Specify whether the script should report the intermediate steps by adjusting the value to 'true'.
  • DATE_RANGE: The script looks at the conversions and costs over this period.
  • INCLUDE_SEARCH: Indicate whether you want to run the script on your search campaigns.
  • INCLUDE_DISPLAY: Indicate whether you want to run the script on your display campaigns. Search must be included.
  • INCLUDE_SHOPPING: Indicate whether you want to run the script on your search campaigns
  • MINIMUM_CONVERSIONS: Optional, select only devices with this number of conversions or more
  • MINIMUM_COST: Optional, select only devices with this amount of spend or more
  • MAX_BID: The bidadjustment will not be higher than this. 1.3 = +30% or 2 = +100%
  • MIN_BID: The bidadjustment will not be lower than this. 0.75 = -25% or 0.5 = -50%.
  • LABEL: Optional, only select campaigns with this label.

Scheduling: To be able to collect enough data, we recommend running this script on a weekly basis.

Script
// Copyright 2019. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
// 
// Created: 01-03-2018
// Last update: 20-06-2019
//
// ABOUT THE SCRIPT
// With this script we adjust the biddings for devices for
// all active campaigns in the account. Change will be based
// on the device statistics vs campaign statistics.
//
////////////////////////////////////////////////////////////////////

var config = {
  
    LOG : true,
    DATE_RANGE : "LAST_30_DAYS",

    // Set these to true if you want to include them.
    INCLUDE_SEARCH : true,		
    INCLUDE_DISPLAY : false,
    INCLUDE_SHOPPING : true,	
  
    // Optional: Use minimum conversions and/or cost to select devices.
    // Cost is in account currency. Leave empty to skip.
    MINIMUM_CONVERIONS : "1",
    MINIMUM_COST : "10",
  
    // Bidadjustments won't be higher then MAX_BID and not lower then MIN_BID.
    // Examples: 1.2 = +20%, 0.8 = -20%, 2 = +100%.
    MAX_BID : 1.5,
    MIN_BID : 0.5,
	
    // Optional: Use a label to make a selection of campaigns.
    // Leave empty to skip.
    LABEL : ""

}

////////////////////////////////////////////////////////////////////

function main() {  

  // Starting with selecting Search campaigns, if included in the config.
  if(config.INCLUDE_SEARCH === true) {
    if(config.LABEL != ""){
      if(config.INCLUDE_DISPLAY === true) {      
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .withCondition("LabelNames CONTAINS_ANY ['" + config.LABEL + "']")
        .get();
      } else {
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .withCondition("LabelNames CONTAINS_ANY ['" + config.LABEL + "']")
        .withCondition("AdvertisingChannelType != DISPLAY") 
        .get();        
      }
    } else{
      if(config.INCLUDE_DISPLAY === true) {  
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .get();
      } else {
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .withCondition("AdvertisingChannelType != DISPLAY") 
        .get();
      }
    }

    // With campaigns selected, the bidadjustments will be set.
    bidAdjustments(campaignIterator);
  }

  // Select Shopping campaigns, if included in the config.
  if(config.INCLUDE_SHOPPING === true) {
    if(config.LABEL != ""){
      var campaignIterator = AdsApp
      .shoppingCampaigns()
      .withCondition("Status = ENABLED")
      .withCondition("LabelNames CONTAINS_ANY ['" + config.LABEL + "']")
      .get();
    } else{
      var campaignIterator = AdsApp
      .shoppingCampaigns()
      .withCondition("Status = ENABLED")
      .get();
    }

    // With campaigns selected, the bidadjustments will be set.
    bidAdjustments(campaignIterator);
  }    
  
  Logger.log("Thanks for using this custom script by Tibbe van Asten. Winning!")
  
} // function main()

////////////////////////////////////////////////////////////////////

function bidAdjustments(iterator) {
  
   while (iterator.hasNext()) {
    var campaign = iterator.next();
    
    // We only want to alter the campaigns and adgroups with manual bidding strategies.
    if(campaign.bidding().getStrategyType() == "MANUAL_CPC" || campaign.bidding().getStrategyType() == "MANUAL_CPM" || campaign.bidding().getStrategyType() == "MANUAL_CPV"){
      
      var campaignCpa = campaign.getStatsFor(config.DATE_RANGE).getCost() / campaign.getStatsFor(config.DATE_RANGE).getConversions();

      	if(config.LOG === true){
          Logger.log("Checking " + campaign.getName());
          Logger.log("-----");
        }
      
      // Within the campaign, we will check for devices.
      var deviceIterator = deviceSelector(campaign);

      while(deviceIterator.hasNext()){
        var device = deviceIterator.next();

        var deviceConversions = device.getStatsFor(config.DATE_RANGE).getConversions();
        var deviceCost = device.getStatsFor(config.DATE_RANGE).getCost();
        var deviceCpa = deviceCost / deviceConversions;
        var newDeviceBid = campaignCpa / deviceCpa;

        // If there is an audience CPA, we will place a bidadjustment.
        if(isNaN(newDeviceBid) === false && isFinite(deviceCpa) === true && device.getBidModifier() !== newDeviceBid) {
          if(newDeviceBid < config.MIN_BID) { newDeviceBid = config.MIN_BID; }
          if(newDeviceBid > config.MAX_BID) { newDeviceBid = config.MAX_BID; }

          device.setBidModifier(newDeviceBid);  

          if(config.LOG === true){
            Logger.log("Bidadjustment " + device.getName() + " changed to " + device.getBidModifier());
          }
        }        

        // Negative bidadjustment when conversions = 0.
        if(deviceConversions == 0 && deviceCost > config.MINIMUM_COST && isNaN(newDeviceBid) == false){
          device.setBidModifier(config.MIN_BID);

          if(config.LOG === true){
            Logger.log("Bidadjustment " + device.getName() + " changed to " + device.getBidModifier());
          }
        }

      } // deviceIterator   
      
      if(config.LOG === true){
        Logger.log(" ");
      }
            
    } // manual bidding
    
  } // campaignIterator
  
} // function bidAn()

////////////////////////////////////////////////////////////////////

function deviceSelector(campaign){

  // When no limitations are set.
  if(config.MINIMUM_CONVERIONS == "" && config.MINIMUM_COST == ""){
    var deviceSelector = campaign
      .targeting()
      .platforms()
      .get();
  }
  
  // When a converions minimum is set.
  if(config.MINIMUM_CONVERIONS != "" && config.MINIMUM_COST == ""){
    var deviceSelector = campaign
      .targeting()
      .platforms()
      .withCondition("Conversions > " + config.MINIMUM_CONVERIONS)
      .forDateRange(config.DATE_RANGE)
      .get();
  }
  
  // When a cost minimum is set.
  if(config.MINIMUM_CONVERIONS == "" && config.MINIMUM_COST != ""){
    var deviceSelector = campaign
      .targeting()
      .platforms()
      .withCondition("Cost > " + config.MINIMUM_COST)
      .forDateRange(config.DATE_RANGE)
      .get();
  }
  
  // When cost and conversion minima are set.
  if(config.MINIMUM_CONVERIONS != "" && config.MINIMUM_COST != ""){
    var deviceSelector = campaign
      .targeting()
      .platforms()
      .withCondition("Conversions > " + config.MINIMUM_CONVERIONS)
      .withCondition("Cost > " + config.MINIMUM_COST)
      .forDateRange(config.DATE_RANGE)
      .get();
  }
  
  return deviceSelector;
  
} // function deviceSelectorCampaign()

Sharing knowledge

Adsscripts.com is all about sharing knowledge. In the current market, PPC specialists like to keep their knowledge and experience to themselves. We're convinced that sharing knowledge can ensure that everyone gets better at their work. We want to change this by sharing our knowledge about scripts with everyone.

Do you also want to contribute? We are open to new ideas and feedback on everything you find on Adsscripts.com.

Contact us

Nils Rooijmans
Google Ads Scripter
Water Cooler Topics
Nils Rooijmans, Google Ads Scripter
Bas Baudoin
Teamleader PPC
Happy Leads
Bas Baudoin, Teamleider SEA
Tibbe van Asten
PPC Specialist
Founder Adsscripts
Tibbe van Asten, Senior PPC Automation Specialist