Google Ads Scripts Guide: Benefits, Setup & 20 Examples

The ultimate guide to Google Ads scripts: Benefits, implementation, and 20 ready-to-use examples

Published by Marty Paukstys, founder of D2CEBL. 20+ years of Google PPC & Analytics experience. Google Ads Search and Google Analytics certified.


Google Ads is a fundamental tool for digital advertisers, but managing campaigns effectively often involves repetitive tasks and complex analysis. While the platform offers robust features, Google Ads Scripts provide a powerful way to automate actions, implement custom logic, and gain deeper control over your account using simple JavaScript code.
 

By leveraging scripts, you can move beyond standard functionalities, tailoring automation to your specific business goals and saving valuable time while potentially improving campaign performance significantly. As seen in the diverse examples previously discussed (from pausing low-performing keywords to dynamically updating ad copy), scripts offer solutions for numerous challenges faced by advertisers today.
 

This guide will walk you through the benefits of using scripts, provide step-by-step instructions on how to implement them, and offer 20 practical examples you can adapt for your own campaigns.

20 Google Ads scripts FB.png

Why Use Google Ads Scripts? The Key Benefits

Integrating scripts into your Google Ads management workflow offers several compelling advantages:

  • Save Time and Reduce Errors: Automate time-consuming, manual tasks like bid adjustments, status checks (e.g., pausing out-of-stock items), report generation, and A/B testing setup. Automation minimizes the risk of human error inherent in repetitive manual processes.
  • Enhance Performance: Implement custom bidding rules based on your unique KPIs, external data (like CRM or weather information), or complex performance patterns that standard bid strategies might not cover. Scripts allow you to react faster to performance shifts, optimizing bids and budgets more dynamically.
  • Gain Deeper Insights: Automate the creation of highly customized reports that combine data in ways not readily available in the standard interface. Integrate data from external sources like Google Sheets or third-party APIs to enrich your analysis and inform strategic decisions.
  • Improve Campaign Control: Automatically enforce account rules, such as pausing campaigns exceeding budget thresholds, enabling/disabling ads or keywords based on specific schedules or conditions (like promotions or inventory levels), and ensuring ads comply with specific guidelines.
  • Enable Advanced Strategies: Go beyond the built-in features to execute sophisticated strategies. This could involve dynamically adjusting ad copy based on real-time data feeds, managing bids based on competitor auction insights trends, or implementing complex budget allocation logic across campaigns.

How to Implement Google Ads Scripts: Step-by-Step

Adding and running scripts in your Google Ads account is a straightforward process:

  1. Access the Scripts Section:
    • Log in to your Google Ads account.
    • Navigate to "Tools & Settings" (wrench icon 🔧 in the top menu).
    • Under the "Bulk Actions" column, click on "Scripts".
  2. Create a New Script:
    • Click the blue "+" button to add a new script.
  3. Name Your Script:
    • In the "Script name" field at the top, give your script a clear and descriptive name (e.g., "Pause Low CTR Keywords Daily" or "Weekly Performance Report to Sheet"). This helps you easily identify its purpose later.
  4. Write or Paste the Code:
    • You'll see a code editor window. This is where you will either write your own JavaScript code or paste code you've obtained (like the examples provided below or from trusted sources online).
  5. Authorize the Script:
    • Before a script can run, it needs permission to access your account data and make changes. Click the "Authorize" button usually found below the script name or above the editor.
    • Review the permissions requested carefully. You'll likely need to choose the Google account associated with your Ads account and grant access. This step is crucial for the script to function.
  6. Preview the Script (Highly Recommended):
    • ALWAYS run your script in preview mode first. Click the "Preview" button (usually located near the "Run" button).
    • Preview mode executes the script logic and shows you the changes it would make (in the "Changes" log) and any messages it logs (in the "Logs" section) without actually modifying your account.
    • Check the logs for errors and review the intended changes to ensure the script behaves exactly as expected. Debug any issues found during the preview.
  7. Run the Script (Manually):
    • Once you are confident the script works correctly based on the preview, you can run it for real by clicking the "Run" button. Choose "Run script now". This will execute the script and apply the changes to your account.
  8. Schedule the Script:
    • Most scripts are designed for automation, so you'll want to schedule them.
    • Find the "Frequency" column next to your script name in the main Scripts list. Click the pencil icon ✏️ or the "Not scheduled" text.
    • Choose how often you want the script to run automatically (e.g., Hourly, Daily, Weekly, Monthly) and select the specific time or day.
    • Click "Save". The script will now run automatically according to the schedule you set. Choose a frequency appropriate for the script's task (e.g., daily for bid checks, hourly for critical alerts, weekly/monthly for reports).
  9. Monitor and Maintain:
    • Regularly check the execution logs for your scheduled scripts (accessible from the main Scripts page by clicking on the script name) to ensure they are running successfully and without errors.
    • Google Ads and its underlying API can change. Be prepared to occasionally update your scripts to maintain compatibility or improve their logic based on new performance data or changing goals.

20 Practical Google Ads Script Examples

Here are 20 specific examples based on common advertiser needs.

(Disclaimer: The provided JavaScript code snippets are illustrative examples. They may require modification and testing to fit your specific Google Ads account structure, goals, and the latest Google Ads Scripts API. Always preview scripts thoroughly before running them on your live account.)


1. Automatically generate a weekly report in Google Sheets detailing campaign performance broken down by Device and Ad Network

What the code exactly does: 

Fetches campaign-level performance data (Impressions, Clicks, Cost, Conversions) for the last 7 days, segmented by Device and Ad Network, and writes this data into a specified Google Sheet for easy analysis.
 

The code:

// Google Ads Script: Weekly Campaign Performance by Device/Network to Google Sheets
var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL'; // Replace with your Google Sheet URL
var SHEET_NAME = 'Weekly Performance'; // Replace with your Sheet name
function main() {
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = spreadsheet.getSheetByName(SHEET_NAME);
  sheet.clearContents(); // Clear previous data
  sheet.appendRow(['Date Range', 'Campaign', 'Device', 'Ad Network', 'Impressions', 'Clicks', 'Cost', 'Conversions']);
  var report = AdsApp.report(
      'SELECT CampaignName, Device, AdNetworkType1, Impressions, Clicks, Cost, Conversions ' +
      'FROM CAMPAIGN_PERFORMANCE_REPORT ' +
      'WHERE Status = "ENABLED" ' + // Or other statuses as needed
      'DURING LAST_7_DAYS');
  var rows = report.rows();
  var dateRange = AdsApp.currentAccount().getStatsFor('LAST_7_DAYS'); // Get date range for context
  var startDate = dateRange.getStartDate();
  var endDate = dateRange.getEndDate();
  var dateString = startDate.getYear() + '-' + (startDate.getMonth()+1) + '-' + startDate.getDate() + ' to ' +
                   endDate.getYear() + '-' + (endDate.getMonth()+1) + '-' + endDate.getDate();
  while (rows.hasNext()) {
    var row = rows.next();
    sheet.appendRow([
      dateString,
      row['CampaignName'],
      row['Device'],
      row['AdNetworkType1'],
      row['Impressions'],
      row['Clicks'],
      row['Cost'],
      row['Conversions']
    ]);
  }
  Logger.log('Weekly performance report updated in Google Sheet: ' + SPREADSHEET_URL);
}


2. Automatically compare this week's ad group performance (Clicks, CTR, Conversions) against the previous week and log significant changes (e.g., >20% drop in CTR)

What the code exactly does: 

Fetches ad group performance data for the last 7 days and the 7 days prior. It then compares key metrics (Clicks, CTR, Conversions) for each ad group and logs details for any ad group showing a change greater than a defined threshold (e.g., 20% decrease in CTR).
 

The code:

// Google Ads Script: Weekly Ad Group Performance Change Alert
var CTR_DROP_THRESHOLD = 0.20; // 20% drop threshold
function main() {
  Logger.log('Comparing Ad Group Performance: Last 7 Days vs. Previous 7 Days');
  var reportThisWeek = AdsApp.report(
      'SELECT AdGroupName, Ctr, Clicks, Conversions ' +
      'FROM ADGROUP_PERFORMANCE_REPORT ' +
      'DURING LAST_7_DAYS');
  var reportLastWeek = AdsApp.report(
      'SELECT AdGroupName, Ctr, Clicks, Conversions ' +
      'FROM ADGROUP_PERFORMANCE_REPORT ' +
      'DURING LAST_WEEK');
  var dataThisWeek = {};
  var rowsThisWeek = reportThisWeek.rows();
  while (rowsThisWeek.hasNext()) {
    var row = rowsThisWeek.next();
    // Convert CTR string "X.XX%" to a number
    var ctr = parseFloat(row['Ctr'].replace('%','')) / 100.0;
    dataThisWeek[row['AdGroupName']] = {'Ctr': ctr, 'Clicks': row['Clicks'], 'Conversions': row['Conversions']};
  }
  var rowsLastWeek = reportLastWeek.rows();
  while (rowsLastWeek.hasNext()) {
    var row = rowsLastWeek.next();
    var adGroupName = row['AdGroupName'];
    if (dataThisWeek[adGroupName]) {
      var ctrLastWeek = parseFloat(row['Ctr'].replace('%','')) / 100.0;
      var ctrThisWeek = dataThisWeek[adGroupName]['Ctr'];
      if (ctrLastWeek > 0 && (ctrLastWeek - ctrThisWeek) / ctrLastWeek > CTR_DROP_THRESHOLD) {
         Logger.log('Significant CTR Drop in Ad Group: ' + adGroupName +
                   '. Last Week CTR: ' + (ctrLastWeek * 100).toFixed(2) +
                   '%, This Week CTR: ' + (ctrThisWeek * 100).toFixed(2) + '%');
      }
      // Add comparisons for Clicks, Conversions etc. as needed
    }
  }
  Logger.log('Ad Group performance comparison complete.');
}


3. Automatically pause keywords that spent over $50 in the last 30 days but generated zero conversions

What the code exactly does: 

Iterates through all active keywords in enabled ad groups and campaigns. If a keyword has accumulated more than $50 in cost over the last 30 days and has zero conversions in the same period, the script pauses the keyword.
 

The code:

// Google Ads Script: Pause High-Spending, Zero-Conversion Keywords
var COST_THRESHOLD = 50.00; // Max cost allowed with zero conversions
var CONVERSION_THRESHOLD = 0; // Must have more than this many conversions
var DATE_RANGE = 'LAST_30_DAYS';
function main() {
  Logger.log('Checking for keywords spending over $' + COST_THRESHOLD + ' with 0 conversions in ' + DATE_RANGE);
  var keywordIterator = AdsApp.keywords()
      .withCondition('Status = ENABLED')
      .withCondition('AdGroupStatus = ENABLED')
      .withCondition('CampaignStatus = ENABLED')
      .withCondition('Cost > ' + COST_THRESHOLD)
      .withCondition('Conversions = ' + CONVERSION_THRESHOLD)
      .forDateRange(DATE_RANGE)
      .get();
  var pausedKeywords = 0;
  while (keywordIterator.hasNext()) {
    var keyword = keywordIterator.next();
    var stats = keyword.getStatsFor(DATE_RANGE); // Re-check stats explicitly for the period
     if (stats.getCost() > COST_THRESHOLD && stats.getConversions() === CONVERSION_THRESHOLD) {
         keyword.pause();
         Logger.log('Paused Keyword: "' + keyword.getText() + '" in Ad Group: "' +
                    keyword.getAdGroup().getName() + '". Cost: ' + stats.getCost() + ', Conversions: 0.');
         pausedKeywords++;
     }
  }
  Logger.log('Finished. Paused ' + pausedKeywords + ' keywords.');
}


4. Extract weekly Auction Insights data (Impression Share, Overlap Rate, Position Above Rate) for the top 5 competitors and log it to a Google Sheet to track trends

What the code exactly does: 

Runs an Auction Insights report at the account level (or specific campaigns) for the last week. It extracts data for the top 5 competitors based on Impression Share and appends this data, along with the date, to a designated Google Sheet. (Note: Direct Auction Insights competitor data access via scripts is limited).
 

The code:

// Google Ads Script: Weekly Auction Insights Tracker to Google Sheets
var SPREADSHEET_URL = 'YOUR_AUCTION_INSIGHTS_SPREADSHEET_URL'; // Replace with your Sheet URL
var SHEET_NAME = 'Auction Insights Log'; // Replace with your Sheet name
var TOP_N_COMPETITORS = 5;
function main() {
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = spreadsheet.getSheetByName(SHEET_NAME);
  // Check if header row exists, add if not
  if (sheet.getLastRow() < 1) {
    sheet.appendRow(['Date', 'Competitor Domain', 'Impression Share', 'Overlap Rate', 'Position Above Rate']);
  }
  // Note: Auction Insights report via Scripts API is limited.
  // This script illustrates fetching general account stats which *don't* include Auction Insights directly.
  // Auction Insights data requires manual download or using the Google Ads API (not Scripts) for full automation.
  // This example logs a message indicating the limitation.
  Logger.log('Auction Insights data needs to be manually downloaded or accessed via Google Ads API for full competitor tracking.');
  Logger.log('Scripts can report on *your* impression share, but not detailed competitor metrics from Auction Insights report directly.');
  // Example: Log own Impression Share data instead (as a placeholder for what scripts *can* access)
   var accountStats = AdsApp.currentAccount().getStatsFor('LAST_7_DAYS');
   var impressionShare = accountStats.getSearchImpressionShare(); // Example metric
   var dateRange = AdsApp.currentAccount().getStatsFor('LAST_7_DAYS');
   var endDate = dateRange.getEndDate();
   var dateString = endDate.getYear() + '-' + (endDate.getMonth()+1) + '-' + endDate.getDate();
   sheet.appendRow([dateString, 'My Account', impressionShare, 'N/A (Scripts Limitation)', 'N/A (Scripts Limitation)']);
   Logger.log('Logged account impression share for ' + dateString + ' to sheet: ' + impressionShare);
  // To get actual competitor data: Use the Google Ads UI Auction Insights report or Google Ads API.
}


5. Increase keyword bids by 10% if their conversion rate over the last 14 days is above 5%, and decrease by 10% if below 2%

What the code exactly does: 

Checks the conversion rate for active keywords over the past 14 days. If a keyword's conversion rate exceeds 5%, its Max CPC bid is increased by 10%. If the conversion rate is below 2%, the bid is decreased by 10%. Bid changes respect campaign bidding strategy constraints.
 

The code:

// Google Ads Script: Adjust Keyword Bids Based on Conversion Rate
var HIGH_CONV_RATE_THRESHOLD = 0.05; // 5%
var LOW_CONV_RATE_THRESHOLD = 0.02;  // 2%
var BID_INCREASE_MULTIPLIER = 1.10; // Increase by 10%
var BID_DECREASE_MULTIPLIER = 0.90; // Decrease by 10%
var MIN_CLICKS_FOR_ADJUSTMENT = 10; // Minimum clicks before adjusting
var DATE_RANGE = 'LAST_14_DAYS';
function main() {
  Logger.log('Adjusting keyword bids based on conversion rate (' + DATE_RANGE + ')');
  var keywordIterator = AdsApp.keywords()
      .withCondition('Status = ENABLED')
      .withCondition('AdGroupStatus = ENABLED')
      .withCondition('CampaignStatus = ENABLED')
      // Filter for campaigns using manual bidding or ECPC where bid adjustments are possible
      .withCondition("CampaignBiddingStrategyType IN ['MANUAL_CPC', 'ENHANCED_CPC']")
      .withCondition('Clicks > ' + MIN_CLICKS_FOR_ADJUSTMENT) // Ensure statistical significance
      .forDateRange(DATE_RANGE)
      .get();
  while (keywordIterator.hasNext()) {
    var keyword = keywordIterator.next();
    var stats = keyword.getStatsFor(DATE_RANGE);
    var conversions = stats.getConversions();
    var clicks = stats.getClicks();
    var currentBid = keyword.bidding().getCpc(); // Get current Max CPC bid
     if (clicks > 0 && currentBid != null) { // Ensure clicks and manual CPC bid exists
        var convRate = conversions / clicks;
        var newBid = currentBid;
        if (convRate > HIGH_CONV_RATE_THRESHOLD) {
          newBid = currentBid * BID_INCREASE_MULTIPLIER;
          Logger.log('Increasing bid for keyword: "' + keyword.getText() + '". Conv Rate: ' + (convRate * 100).toFixed(2) + '%. Old Bid: ' + currentBid + ', New Bid: ' + newBid.toFixed(2));
        } else if (convRate < LOW_CONV_RATE_THRESHOLD) {
          newBid = currentBid * BID_DECREASE_MULTIPLIER;
          Logger.log('Decreasing bid for keyword: "' + keyword.getText() + '". Conv Rate: ' + (convRate * 100).toFixed(2) + '%. Old Bid: ' + currentBid + ', New Bid: ' + newBid.toFixed(2));
        }
         if (newBid !== currentBid) {
            keyword.bidding().setCpc(newBid);
         }
    }
  }
  Logger.log('Keyword bid adjustments complete.');
}


6. For campaigns using Target CPA, if the actual CPA over the last 30 days is 20% below the target, increase the Target CPA by 5%. If it's 20% above, decrease the Target CPA by 5%

What the code exactly does: 

Examines campaigns using the Target CPA bid strategy. It compares the actual CPA over the last 30 days against the campaign's set Target CPA. Based on predefined thresholds (20% deviation), it adjusts the campaign's Target CPA up or down by 5%.
 

The code:

// Google Ads Script: Adjust Target CPA Based on Performance
var CPA_DEVIATION_THRESHOLD = 0.20; // 20% deviation
var TARGET_CPA_ADJUSTMENT_FACTOR = 0.05; // Adjust by 5%
var DATE_RANGE = 'LAST_30_DAYS';
var MIN_CONVERSIONS_FOR_ADJUSTMENT = 5; // Min conversions for reliable CPA calculation
function main() {
  Logger.log('Adjusting Target CPA based on performance (' + DATE_RANGE + ')');
  var campaignIterator = AdsApp.campaigns()
      .withCondition('Status = ENABLED')
      .withCondition('CampaignStatus = ENABLED') // Redundant but safe
      .withCondition("BiddingStrategyType = TARGET_CPA")
      .forDateRange(DATE_RANGE) // Filter stats for the date range
      .get();
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    var stats = campaign.getStatsFor(DATE_RANGE);
    var cost = stats.getCost();
    var conversions = stats.getConversions();
    if (conversions >= MIN_CONVERSIONS_FOR_ADJUSTMENT) {
      var actualCpa = cost / conversions;
      var biddingStrategy = campaign.getBiddingStrategy();
      // Check if it's a portfolio or standard strategy to get Target CPA
      var targetCpa = null;
      if (typeof biddingStrategy.getTargetCpa === 'function') { // Standard strategy
         targetCpa = biddingStrategy.getTargetCpa();
      } else if (campaign.bidding().getStrategyType() === 'TARGET_CPA' && typeof campaign.bidding().getTargetCpa === 'function') {
         // This might apply if it's a campaign-level setting, syntax varies
         targetCpa = campaign.bidding().getTargetCpa();
      }
      // Note: Accessing target CPA for portfolio strategies requires iterating through AdsApp.biddingStrategies()
      if (targetCpa !== null && targetCpa > 0) {
        var newTargetCpa = targetCpa;
        if (actualCpa < targetCpa * (1 - CPA_DEVIATION_THRESHOLD)) {
          // Actual CPA is significantly lower, increase target to get more volume
          newTargetCpa = targetCpa * (1 + TARGET_CPA_ADJUSTMENT_FACTOR);
          Logger.log('Increasing Target CPA for campaign: "' + campaign.getName() +
                     '". Actual CPA: ' + actualCpa.toFixed(2) + ', Current Target: ' + targetCpa.toFixed(2) +
                     ', New Target: ' + newTargetCpa.toFixed(2));
        } else if (actualCpa > targetCpa * (1 + CPA_DEVIATION_THRESHOLD)) {
          // Actual CPA is significantly higher, decrease target for efficiency
          newTargetCpa = targetCpa * (1 - TARGET_CPA_ADJUSTMENT_FACTOR);
           Logger.log('Decreasing Target CPA for campaign: "' + campaign.getName() +
                     '". Actual CPA: ' + actualCpa.toFixed(2) + ', Current Target: ' + targetCpa.toFixed(2) +
                     ', New Target: ' + newTargetCpa.toFixed(2));
        }
         if (newTargetCpa !== targetCpa && typeof biddingStrategy.setTargetCpa === 'function') {
            biddingStrategy.setTargetCpa(newTargetCpa); // Adjust portfolio strategy
         } else if (newTargetCpa !== targetCpa && typeof campaign.bidding().setTargetCpa === 'function') {
            campaign.bidding().setTargetCpa(newTargetCpa); // Adjust campaign-level strategy
         }
      } else {
          Logger.log('Could not retrieve Target CPA for campaign: ' + campaign.getName());
      }
    } else {
         Logger.log('Skipping campaign "' + campaign.getName() + '" due to insufficient conversions (' + conversions + ')');
    }
  }
   Logger.log('Target CPA adjustments complete.');
}


7. For campaigns using Target Impression Share bidding for 'Absolute Top of Page', if the actual Absolute Top IS% is below the target by 15% over the last 7 days, increase the campaign's Max CPC bid limit by 5%

What the code exactly does: 

Monitors campaigns aiming for Absolute Top Impression Share. If the achieved Absolute Top IS% falls significantly below the target (by 15% or more) over the last week, it incrementally increases the campaign's maximum CPC bid limit to help achieve the desired visibility.
 

The code:

// Google Ads Script: Adjust Max CPC Bid Limit for Target Impression Share Campaigns
var IMPRESSION_SHARE_TARGET_LOCATION = 'ABSOLUTE_TOP_OF_PAGE'; // Or 'TOP_OF_PAGE', 'ANYWHERE_ON_PAGE'
var IS_DEVIATION_THRESHOLD = 0.15; // 15% below target
var BID_LIMIT_INCREASE_FACTOR = 1.05; // Increase bid limit by 5%
var DATE_RANGE = 'LAST_7_DAYS';
function main() {
  Logger.log('Adjusting Max CPC Bid Limit for Target IS campaigns (' + DATE_RANGE + ')');
  var campaignIterator = AdsApp.campaigns()
      .withCondition('Status = ENABLED')
      .withCondition("BiddingStrategyType = TARGET_IMPRESSION_SHARE")
      .get();
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    var biddingStrategy = campaign.getBiddingStrategy(); // Assumes campaign-level or portfolio strategy object
    // Check if the strategy targets the correct location
     var targetLocation = '';
     if (typeof biddingStrategy.getTargetImpressionShareLocation === 'function') {
         targetLocation = biddingStrategy.getTargetImpressionShareLocation();
     } else if (campaign.bidding().getStrategyType() === 'TARGET_IMPRESSION_SHARE' && typeof campaign.bidding().getTargetImpressionShareLocation === 'function') {
         targetLocation = campaign.bidding().getTargetImpressionShareLocation(); // Check campaign level if not portfolio
     }
    if (targetLocation === IMPRESSION_SHARE_TARGET_LOCATION) {
      var targetIs = null;
      var currentBidLimit = null;
      // Get target IS and Bid Limit - varies if standard or portfolio
       if (typeof biddingStrategy.getTargetImpressionShare === 'function') {
           targetIs = biddingStrategy.getTargetImpressionShare(); // e.g., 0.80 for 80%
       } else if (campaign.bidding().getStrategyType() === 'TARGET_IMPRESSION_SHARE' && typeof campaign.bidding().getTargetImpressionShare === 'function'){
            targetIs = campaign.bidding().getTargetImpressionShare();
       }
       if (typeof biddingStrategy.getCpcBidCeiling === 'function') {
           currentBidLimit = biddingStrategy.getCpcBidCeiling();
       } else if (campaign.bidding().getStrategyType() === 'TARGET_IMPRESSION_SHARE' && typeof campaign.bidding().getCpcBidCeiling === 'function'){
            currentBidLimit = campaign.bidding().getCpcBidCeiling();
       }
      if (targetIs !== null && targetIs > 0 && currentBidLimit !== null && currentBidLimit > 0) {
        var stats = campaign.getStatsFor(DATE_RANGE);
        var actualAbsoluteTopIs = stats.getAbsoluteTopImpressionShare(); // This is the metric
        if (actualAbsoluteTopIs < (targetIs * (1 - IS_DEVIATION_THRESHOLD))) {
          var newBidLimit = currentBidLimit * BID_LIMIT_INCREASE_FACTOR;
          Logger.log('Increasing Max CPC Bid Limit for campaign: "' + campaign.getName() +
                     '". Target AbsTop IS: ' + (targetIs * 100).toFixed(2) + '%, Actual: ' + (actualAbsoluteTopIs * 100).toFixed(2) +
                     '%. Old Limit: ' + currentBidLimit.toFixed(2) + ', New Limit: ' + newBidLimit.toFixed(2));
          // Apply the new bid limit
           if (typeof biddingStrategy.setCpcBidCeiling === 'function') {
               biddingStrategy.setCpcBidCeiling(newBidLimit);
           } else if (campaign.bidding().getStrategyType() === 'TARGET_IMPRESSION_SHARE' && typeof campaign.bidding().setCpcBidCeiling === 'function'){
                campaign.bidding().setCpcBidCeiling(newBidLimit);
           }
        }
      } else {
          Logger.log('Could not retrieve Target IS or Bid Limit for campaign: ' + campaign.getName());
      }
    }
  }
  Logger.log('Target Impression Share bid limit adjustments complete.');
}


8. Integrate with CRM data (via Google Sheet) to increase bids by 15% for keywords that generated leads marked as "High Value" in the CRM within the last 45 days

What the code exactly does: 

Reads a Google Sheet containing CRM lead data, including the associated Google Ads keyword and a "Lead Value" status. It identifies keywords linked to "High Value" leads within the last 45 days and increases their Max CPC bid by 15%.
 

The code:

// Google Ads Script: Increase Bids for Keywords Generating High-Value CRM Leads
var SPREADSHEET_URL = 'YOUR_CRM_LEAD_DATA_SHEET_URL'; // Replace with Sheet URL
var SHEET_NAME = 'CRM Leads'; // Replace with Sheet name containing Keyword and Lead Value columns
var KEYWORD_COLUMN = 'Keyword'; // Column name for Keyword Text
var LEAD_VALUE_COLUMN = 'Lead Value'; // Column name for Lead Value (e.g., "High Value")
var DATE_COLUMN = 'Lead Date'; // Column name for the date the lead was generated
var HIGH_VALUE_LEAD_STATUS = 'High Value';
var LOOKBACK_DAYS = 45;
var BID_INCREASE_MULTIPLIER = 1.15; // Increase bid by 15%
function main() {
  var highValueKeywords = getHighValueKeywordsFromSheet_();
  if (Object.keys(highValueKeywords).length === 0) {
     Logger.log('No high-value keywords found in the specified date range in the sheet.');
     return;
  }
   Logger.log('Found ' + Object.keys(highValueKeywords).length + ' keywords associated with high-value leads. Adjusting bids...');
  var keywordIterator = AdsApp.keywords()
      .withCondition('Status = ENABLED')
      .withCondition('AdGroupStatus = ENABLED')
      .withCondition('CampaignStatus = ENABLED')
      .withCondition("CampaignBiddingStrategyType IN ['MANUAL_CPC', 'ENHANCED_CPC']") // Applicable bidding strategies
      .get();
  while (keywordIterator.hasNext()) {
    var keyword = keywordIterator.next();
    var keywordText = keyword.getText();
    var currentBid = keyword.bidding().getCpc();
    if (highValueKeywords[keywordText] && currentBid != null) {
      var newBid = currentBid * BID_INCREASE_MULTIPLIER;
       keyword.bidding().setCpc(newBid);
       Logger.log('Increased bid for High Value Lead Keyword: "' + keywordText +
                  '" in Ad Group: "' + keyword.getAdGroup().getName() +
                  '". Old Bid: ' + currentBid.toFixed(2) + ', New Bid: ' + newBid.toFixed(2));
       // Optionally, remove the keyword from the list to avoid multiple adjustments if script runs frequently
       // delete highValueKeywords[keywordText];
    }
  }
  Logger.log('Bid adjustments for high-value lead keywords complete.');
}
// Helper function to read keywords from Google Sheet
function getHighValueKeywordsFromSheet_() {
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = spreadsheet.getSheetByName(SHEET_NAME);
  var data = sheet.getDataRange().getValues();
  var headers = data.shift(); // Get header row
  var keywordColIndex = headers.indexOf(KEYWORD_COLUMN);
  var valueColIndex = headers.indexOf(LEAD_VALUE_COLUMN);
  var dateColIndex = headers.indexOf(DATE_COLUMN);
  if (keywordColIndex === -1 || valueColIndex === -1 || dateColIndex === -1) {
    throw new Error('Required columns not found in sheet: ' + KEYWORD_COLUMN + ', ' + LEAD_VALUE_COLUMN + ', ' + DATE_COLUMN);
  }
  var highValueKeywords = {};
  var cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - LOOKBACK_DAYS);
  for (var i = 0; i < data.length; i++) {
    var row = data[i];
    var leadDate = new Date(row[dateColIndex]);
    var keyword = row[keywordColIndex];
    var leadValue = row[valueColIndex];
    if (keyword && leadValue === HIGH_VALUE_LEAD_STATUS && leadDate >= cutoffDate) {
      highValueKeywords[keyword] = true; // Store keyword text as key
    }
  }
  return highValueKeywords;
}


9. Automatically pause any ad group where the Cost Per Acquisition (CPA) over the last 30 days exceeds $100

What the code exactly does: 

Calculates the CPA for each enabled ad group over the past 30 days. If an ad group's CPA is higher than the $100 threshold, the script pauses the ad group.
 

The code:

// Google Ads Script: Pause Ad Groups with High CPA
var CPA_THRESHOLD = 100.00; // Maximum allowed CPA
var DATE_RANGE = 'LAST_30_DAYS';
var MIN_CONVERSIONS_FOR_PAUSE = 1; // Only pause if at least one conversion occurred (to avoid pausing based on $0 cost / 0 conversions)
function main() {
  Logger.log('Checking for ad groups with CPA > $' + CPA_THRESHOLD + ' in ' + DATE_RANGE);
  var adGroupIterator = AdsApp.adGroups()
      .withCondition('Status = ENABLED')
      .withCondition('CampaignStatus = ENABLED')
      .withCondition('Conversions >= ' + MIN_CONVERSIONS_FOR_PAUSE) // Ensure CPA is meaningful
      .forDateRange(DATE_RANGE)
      .get();
  var pausedAdGroups = 0;
  while (adGroupIterator.hasNext()) {
    var adGroup = adGroupIterator.next();
    var stats = adGroup.getStatsFor(DATE_RANGE);
    var cost = stats.getCost();
    var conversions = stats.getConversions();
    if (conversions > 0) { // Double check conversions
       var actualCpa = cost / conversions;
       if (actualCpa > CPA_THRESHOLD) {
          adGroup.pause();
          Logger.log('Paused Ad Group: "' + adGroup.getName() + '" in Campaign: "' + adGroup.getCampaign().getName() +
                     '". CPA: $' + actualCpa.toFixed(2) + ' (Threshold: $' + CPA_THRESHOLD + ')');
          pausedAdGroups++;
       }
    }
  }
  Logger.log('Finished. Paused ' + pausedAdGroups + ' ad groups due to high CPA.');
}


10. Automatically enable ads labeled "Weekend Sale" every Friday at 5 PM and pause them every Sunday at 11 PM

What the code exactly does: 

This script needs to be scheduled appropriately. When run (e.g., Friday evening), it enables ads with the label "Weekend Sale". When run (e.g., Sunday night), it pauses ads with the same label. It relies on correct scheduling within Google Ads Scripts.
 

The code (needs two versions or logic based on run time):
 

Script to RUN Friday 5 PM (Enable Ads):

// Google Ads Script: Enable Weekend Sale Ads (Run Fri PM)
var WEEKEND_LABEL_NAME = 'Weekend Sale'; // Label applied to weekend sale ads
function main() {
  Logger.log('Enabling ads with label: ' + WEEKEND_LABEL_NAME);
  var label = AdsApp.labels().withCondition("Name = '" + WEEKEND_LABEL_NAME + "'").get().next(); // Get the label object
  if (label) {
     var adIterator = label.ads().withCondition('Status = PAUSED').get();
     var enabledCount = 0;
     while (adIterator.hasNext()) {
        var ad = adIterator.next();
        // Optional: Check if parent Ad Group and Campaign are enabled
        if (ad.getAdGroup().isEnabled() && ad.getCampaign().isEnabled()) {
           ad.enable();
           enabledCount++;
        }
     }
     Logger.log('Enabled ' + enabledCount + ' ads with label "' + WEEKEND_LABEL_NAME + '".');
  } else {
     Logger.log('Label "' + WEEKEND_LABEL_NAME + '" not found.');
  }
}

Script to RUN Sunday 11 PM (Pause Ads):

// Google Ads Script: Pause Weekend Sale Ads (Run Sun PM)
var WEEKEND_LABEL_NAME = 'Weekend Sale'; // Label applied to weekend sale ads
function main() {
  Logger.log('Pausing ads with label: ' + WEEKEND_LABEL_NAME);
   var label = AdsApp.labels().withCondition("Name = '" + WEEKEND_LABEL_NAME + "'").get().next(); // Get the label object
   if (label) {
     var adIterator = label.ads().withCondition('Status = ENABLED').get();
     var pausedCount = 0;
     while (adIterator.hasNext()) {
        var ad = adIterator.next();
        ad.pause();
        pausedCount++;
     }
     Logger.log('Paused ' + pausedCount + ' ads with label "' + WEEKEND_LABEL_NAME + '".');
   } else {
      Logger.log('Label "' + WEEKEND_LABEL_NAME + '" not found.');
   }
}


11. Analyze hourly performance data for the past week. Increase bid adjustments by +15% for hours with a conversion rate above 4% and decrease by -10% for hours below 1.5%

What the code exactly does: 

Retrieves campaign performance segmented by hour of the day for the last 7 days. It calculates the conversion rate for each hour across all campaigns (or specific ones). Based on thresholds, it sets positive or negative bid adjustments for those hours in the campaigns' ad schedules. Requires existing ad schedules covering all hours. (Note: Applying hourly bid adjustments requires careful handling of existing ad schedules. The sample code provides a simplified approach.)
 

The code:

// Google Ads Script: Optimize Hourly Bid Adjustments Based on Conv Rate
var HIGH_CONV_RATE_THRESHOLD = 0.04; // 4%
var LOW_CONV_RATE_THRESHOLD = 0.015; // 1.5%
var BID_ADJUSTMENT_INCREASE = 1.15; // +15%
var BID_ADJUSTMENT_DECREASE = 0.90; // -10%
var DATE_RANGE = 'LAST_7_DAYS';
// Specify campaigns or leave empty for all
var TARGET_CAMPAIGN_NAMES = []; // Example: ['Campaign A', 'Campaign B']
function main() {
  Logger.log('Analyzing hourly performance for bid adjustments (' + DATE_RANGE + ')');
  var hourlyStats = {}; // Store stats per hour { hour: {clicks: X, conversions: Y} }
  var report = AdsApp.report(
      'SELECT HourOfDay, Clicks, Conversions ' +
      'FROM CAMPAIGN_PERFORMANCE_REPORT ' +
      (TARGET_CAMPAIGN_NAMES.length > 0 ? 'WHERE CampaignName IN ["' + TARGET_CAMPAIGN_NAMES.join('","') + '"] ' : '') +
      'DURING ' + DATE_RANGE);
  var rows = report.rows();
  while (rows.hasNext()) {
    var row = rows.next();
    var hour = parseInt(row['HourOfDay'], 10);
    if (!hourlyStats[hour]) {
      hourlyStats[hour] = { clicks: 0, conversions: 0 };
    }
    hourlyStats[hour].clicks += parseInt(row['Clicks'], 10);
    hourlyStats[hour].conversions += parseInt(row['Conversions'], 10);
  }
   Logger.log('Hourly Stats Collected: ' + JSON.stringify(hourlyStats));
  // Apply adjustments to campaigns
  var campaignIterator = AdsApp.campaigns()
      .withCondition('Status = ENABLED')
      .(TARGET_CAMPAIGN_NAMES.length > 0 ? .withCondition('Name IN ["' + TARGET_CAMPAIGN_NAMES.join('","') + '"] ') : '')
      .get();
   while (campaignIterator.hasNext()) {
       var campaign = campaignIterator.next();
       Logger.log('Processing campaign: ' + campaign.getName());
       var adSchedules = campaign.targeting().adSchedules().get();
       var scheduleMap = {}; // Map existing schedule criteria for easy lookup
       while(adSchedules.hasNext()){
           var schedule = adSchedules.next();
           // Key could be day + start hour + start min + end hour + end min
           var key = schedule.getDayOfWeek() + '_' + schedule.getStartHour() + '_' + schedule.getStartMinute() + '_' + schedule.getEndHour() + '_' + schedule.getEndMinute();
           scheduleMap[key] = schedule;
       }
       for (var hour = 0; hour < 24; hour++) {
           var adjustment = 1.0; // Default: no adjustment
           if (hourlyStats[hour] && hourlyStats[hour].clicks > 0) {
               var convRate = hourlyStats[hour].conversions / hourlyStats[hour].clicks;
               if (convRate > HIGH_CONV_RATE_THRESHOLD) {
                   adjustment = BID_ADJUSTMENT_INCREASE;
                   Logger.log('Hour ' + hour + ': High Conv Rate (' + (convRate*100).toFixed(2) + '%). Setting adj: ' + adjustment);
               } else if (convRate < LOW_CONV_RATE_THRESHOLD) {
                   adjustment = BID_ADJUSTMENT_DECREASE;
                    Logger.log('Hour ' + hour + ': Low Conv Rate (' + (convRate*100).toFixed(2) + '%). Setting adj: ' + adjustment);
               } else {
                    Logger.log('Hour ' + hour + ': Conv Rate (' + (convRate*100).toFixed(2) + '%) within thresholds. No change.');
               }
           } else {
               Logger.log('Hour ' + hour + ': No significant data. No change.');
           }
           // Find and update matching ad schedule criteria for this hour
           // This part is complex as schedules might span hours or not exist
           // A robust implementation would need to carefully match/create schedule criteria.
           // Simplified: Assume a schedule exists for each hour block (00:00-01:00, 01:00-02:00 etc.) on all days
           // THIS IS A SIMPLIFICATION - real schedules vary!
           var daysOfWeek = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];
           daysOfWeek.forEach(function(day) {
               // Attempt to find a schedule criterion that *includes* this hour for the day
               // This logic needs refinement for real-world schedule complexity
               for (var key in scheduleMap) {
                   var parts = key.split('_'); // day_starth_startm_endh_endm
                   var scheduleDay = parts[0];
                   var startHour = parseInt(parts[1]);
                   var endHour = parseInt(parts[3]);
                   // Simple check if the hour falls within this schedule block for the day
                   if (scheduleDay === day && hour >= startHour && hour < endHour) {
                       var schedule = scheduleMap[key];
                       // Avoid setting 0% adjustment (which is 1.0) unless explicitly intended
                       if (adjustment !== 1.0) {
                            schedule.setBidModifier(adjustment);
                            Logger.log('Updated bid modifier for ' + day + ' ' + startHour + ':00-' + endHour + ':00 to ' + adjustment.toFixed(2));
                       } else if (schedule.getBidModifier() !== 1.0) {
                           // Optional: Reset to 1.0 if performance is average now but wasn't before
                           // schedule.setBidModifier(1.0);
                           // Logger.log('Reset bid modifier for ' + day + ' ' + startHour + ':00-' + endHour + ':00 to 1.0');
                       }
                       break; // Assume one schedule covers the hour
                   }
               }
           });
       }
   }
  Logger.log('Hourly bid adjustment optimization complete.');
}


12. Monitor account-level spend daily. If total spend exceeds $950 for the day, pause all active campaigns labeled "Non-Essential"

What the code exactly does: 

Calculates the total cost across the entire account for the current day. If this cost exceeds $950, it finds all active campaigns that have the label "Non-Essential" applied and pauses them.
 

The code:

// Google Ads Script: Pause Non-Essential Campaigns if Daily Budget Nears Limit
var DAILY_SPEND_THRESHOLD = 950.00;
var LABEL_TO_PAUSE = 'Non-Essential'; // Label for campaigns to pause
function main() {
  var accountStats = AdsApp.currentAccount().getStatsFor('TODAY');
  var todaySpend = accountStats.getCost();
  Logger.log('Current spend today: ' \+ todaySpend\.toFixed\(2\)\);
if \(todaySpend \> DAILY\_SPEND\_THRESHOLD\) \{
Logger\.log\('Daily spend threshold \(' + DAILY_SPEND_THRESHOLD + ') exceeded. Pausing campaigns with label: ' + LABEL_TO_PAUSE);
    var label = AdsApp.labels().withCondition("Name = '" + LABEL_TO_PAUSE + "'").get().next();
    if (label) {
      var campaignIterator = label.campaigns()
          .withCondition('Status = ENABLED') // Only pause enabled campaigns
          .get();
      var pausedCount = 0;
      while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        campaign.pause();
        Logger.log('Paused campaign: "' + campaign.getName() + '"');
        pausedCount++;
      }
      Logger.log('Paused ' + pausedCount + ' campaigns with label "' + LABEL_TO_PAUSE + '".');
    } else {
      Logger.log('Label "' + LABEL_TO_PAUSE + '" not found. No campaigns paused.');
    }
  } else {
    Logger.log('Daily spend is within threshold. No action taken.');
  }
}


13. Update ad copy in specific ad groups to include the current price for a product listed in a Google Sheet data feed

What the code exactly does: 

Reads product data (Product ID, Price, Target Ad Group) from a Google Sheet. It then finds ads within the specified ad groups (possibly identified by a label or naming convention linked to the Product ID) and updates a placeholder (e.g., "{Price}") in the ad description with the current price from the sheet. (Note: Updating ads often requires creating a new ad and pausing the old one.)
 

The code:

// Google Ads Script: Update Ad Prices from Google Sheet Feed
var SPREADSHEET_URL = 'YOUR_PRODUCT_FEED_SHEET_URL'; // Replace with Sheet URL
var SHEET_NAME = 'Product Prices'; // Replace with Sheet name
var PRODUCT_ID_COLUMN = 'ProductID'; // Column for unique product ID
var PRICE_COLUMN = 'Price'; // Column for current price (e.g., $19.99)
var ADGROUP_NAME_COLUMN = 'TargetAdGroupName'; // Column specifying the ad group to update
var AD_LABEL_FOR_UPDATE = 'PriceUpdateAd'; // Apply this label to ads you want updated

function main() {
  var productData = getProductDataFromSheet_();
  Logger.log('Processing ' + Object.keys(productData).length + ' products from sheet.');

  var adIterator = AdsApp.ads()
    .withCondition('Status = ENABLED')
    .withCondition('AdGroupStatus = ENABLED')
    .withCondition('CampaignStatus = ENABLED')
    .withCondition("LabelNames CONTAINS_ANY ['" + AD_LABEL_FOR_UPDATE + "']") // Target specific ads
    .get();

  var updatedCount = 0;
  while (adIterator.hasNext()) {
    var ad = adIterator.next();
    var adGroup = ad.getAdGroup();
    var adGroupName = adGroup.getName();

    // Find product data matching this ad's ad group
    var priceToUpdate = null;
    for (var productId in productData) {
        if (productData[productId].adGroupName === adGroupName) {
           priceToUpdate = productData[productId].price;
           break; // Assuming one product per ad group for simplicity
        }
    }


    if (priceToUpdate !== null) {
       // Assuming Expanded Text Ad for simplicity; adapt for RSA
       if (ad.isType().expandedTextAd()) {
           var eta = ad.asExpandedTextAd();
           var d1 = eta.getDescription1();
           var d2 = eta.getDescription2();
           var newD1 = d1.replace(/{Price}/gi, priceToUpdate); // Replace placeholder
           var newD2 = d2.replace(/{Price}/gi, priceToUpdate); // Replace placeholder

           if (d1 !== newD1 || d2 !== newD2) {
               // Need to create a new ad and remove the old one
               var adBuilder = adGroup.newAd().expandedTextAdBuilder();
               adBuilder.withHeadlinePart1(eta.getHeadlinePart1())
                        .withHeadlinePart2(eta.getHeadlinePart2())
                        // Include Headline 3 if it exists
                        .withDescription1(newD1)
                        .withDescription2(newD2)
                        .withPath1(eta.getPath1())
                        .withPath2(eta.getPath2())
                        .withFinalUrl(eta.urls().getFinalUrl());
                // Check for Headline 3
                if (eta.getHeadlinePart3()) {
                    adBuilder.withHeadlinePart3(eta.getHeadlinePart3());
                }
                // Check for Description 3? (Not in standard ETA)

               var newAdOperation = adBuilder.build();
               if (newAdOperation.isSuccessful()) {
                    ad.remove(); // Remove the old ad
                    Logger.log('Updated price in ad for Ad Group: ' + adGroupName + '. New Price: ' + priceToUpdate);
                    updatedCount++;
               } else {
                    Logger.log('Error updating ad in Ad Group: ' + adGroupName + ' - ' + newAdOperation.getErrors().join(', '));
               }
           }
       } else {
           // Add logic for Responsive Search Ads (RSAs) if needed - more complex due to asset combinations
           Logger.log('Skipping non-ETA ad in Ad Group: ' + adGroupName);
       }
    }
  }
  Logger.log('Finished price updates. Updated ' + updatedCount + ' ads.');
}

// Helper function to read product data from Google Sheet
function getProductDataFromSheet_() {
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = spreadsheet.getSheetByName(SHEET_NAME);
  var data = sheet.getDataRange().getValues();
  var headers = data.shift();

  var idCol = headers.indexOf(PRODUCT_ID_COLUMN);
  var priceCol = headers.indexOf(PRICE_COLUMN);
  var adGroupCol = headers.indexOf(ADGROUP_NAME_COLUMN);

  if (idCol === -1 || priceCol === -1 || adGroupCol === -1) {
     throw new Error('Required columns not found in sheet: ' + PRODUCT_ID_COLUMN + ', ' + PRICE_COLUMN + ', ' + ADGROUP_NAME_COLUMN);
  }

  var productData = {};
  for (var i = 0; i < data.length; i++) {
    var row = data[i];
    var productId = row[idCol];
    var price = row[priceCol]; // Assumes price is formatted correctly (e.g., "$19.99")
    var adGroupName = row[adGroupCol];
    if (productId && price && adGroupName) {
      productData[productId] = { price: price, adGroupName: adGroupName };
    }
  }
  return productData;
}


14. Identify the best performing headline (based on CTR over last 30 days) within Responsive Search Ads (RSAs) in a specific campaign and create a new RSA variant pinning that headline to position 1

What the code exactly does: 

Analyzes asset performance (specifically headlines) for RSAs within a target campaign over the last 30 days. It identifies the headline with the highest CTR. Then, for each ad group containing an RSA, it attempts to create a new RSA where this top-performing headline is pinned to the first position (if not already pinned). (Note: Accessing detailed RSA asset performance and programmatically creating/modifying RSAs with pinning via Scripts can be challenging.)
 

The code:

// Google Ads Script: Pin Top Performing RSA Headline
var TARGET_CAMPAIGN_NAME = 'Your Target Campaign Name'; // Replace with campaign name
var DATE_RANGE = 'LAST_30_DAYS';
var MIN_IMPRESSIONS_FOR_ASSET = 50; // Minimum impressions for reliable CTR

function main() {
  Logger.log('Analyzing RSA headline performance in campaign: ' + TARGET_CAMPAIGN_NAME);

  // 1. Find the best performing headline asset text across the campaign
  var bestHeadlineText = '';
  var highestCtr = 0;

  // Reporting on individual RSA asset performance via scripts is limited.
  // The 'ASSET_PERFORMANCE_REPORT' might provide some data, but joining it back to specific ads/text is hard.
  // This requires Google Ads API or manual analysis typically.
  // Placeholder: Assume we identified the best headline text manually or via API.
  bestHeadlineText = 'Your Best Headline Text Here'; // ** REPLACE MANUALLY OR VIA API **

  if (!bestHeadlineText) {
      Logger.log('Could not determine the best performing headline text. Exiting.');
      return;
  }
  Logger.log('Identified best performing headline (manual/API): "' + bestHeadlineText + '"');


  // 2. Iterate through Ad Groups and RSAs in the campaign
  var adGroupIterator = AdsApp.adGroups()
      .withCondition('CampaignName = "' + TARGET_CAMPAIGN_NAME + '"')
      .withCondition('Status = ENABLED')
      .get();

  while (adGroupIterator.hasNext()) {
    var adGroup = adGroupIterator.next();
    var adIterator = adGroup.ads()
        .withCondition('Type = RESPONSIVE_SEARCH_AD')
        .withCondition('Status = ENABLED')
        .get();

    while (adIterator.hasNext()) {
        var ad = adIterator.next();
        var rsa = ad.asResponsiveSearchAd();
        var headlines = rsa.getHeadlines(); // Array of AdTextAsset objects
        var descriptions = rsa.getDescriptions(); // Array of AdTextAsset objects

        var isPinnedInPos1 = false;
        var currentHeadlinesInfo = []; // Store text and pinned field

        headlines.forEach(function(h) {
            currentHeadlinesInfo.push({text: h.getText(), pinnedField: h.getPinnedField()});
            if (h.getText() === bestHeadlineText && h.getPinnedField() === 'HEADLINE_1') {
                isPinnedInPos1 = true;
            }
        });

        // If the best headline exists but isn't pinned to position 1, create a new variant
        var headlineExists = currentHeadlinesInfo.some(function(h) { return h.text === bestHeadlineText; });

        if (headlineExists && !isPinnedInPos1) {
            Logger.log('Found RSA in Ad Group "' + adGroup.getName() + '" where best headline "' + bestHeadlineText + '" is not pinned to H1. Attempting to create pinned variant.');

            // Build new RSA - This is complex and syntax may vary. Check official docs.
            var newRsaBuilder = adGroup.newAd().responsiveSearchAdBuilder();

            // Add existing headlines, pinning the best one to H1
            currentHeadlinesInfo.forEach(function(hInfo) {
                if (hInfo.text === bestHeadlineText) {
                    newRsaBuilder.addHeadline(newRsaBuilder.newHeadlineBuilder().withText(hInfo.text).withPinnedField('HEADLINE_1').build());
                } else {
                    // Re-add other headlines without pinning or maintaining original pinning (choose one approach)
                     newRsaBuilder.addHeadline(newRsaBuilder.newHeadlineBuilder().withText(hInfo.text).build()); // Example: add unpinned
                }
            });

            // Add existing descriptions
            descriptions.forEach(function(d) {
                newRsaBuilder.addDescription(newRsaBuilder.newDescriptionBuilder().withText(d.getText()).build()); // Add unpinned
            });

            // Set paths if they exist
            if (rsa.getPath1()) newRsaBuilder.withPath1(rsa.getPath1());
            if (rsa.getPath2()) newRsaBuilder.withPath2(rsa.getPath2());

            // Set final URL
             newRsaBuilder.withFinalUrl(ad.urls().getFinalUrl()); // Get final URL from original ad

            // Build the new RSA
            var buildOp = newRsaBuilder.build();

            if (buildOp.isSuccessful()) {
                 Logger.log('Successfully created new RSA variant with pinned headline in Ad Group: ' + adGroup.getName());
                 // Optionally pause the original RSA ad here: ad.pause();
            } else {
                 Logger.log('Error creating new RSA variant in Ad Group: ' + adGroup.getName() + ' Errors: ' + buildOp.getErrors().join(', '));
            }

        } else if (isPinnedInPos1) {
            Logger.log('Best headline already pinned to H1 in Ad Group: ' + adGroup.getName());
        } else {
             Logger.log('Best headline "' + bestHeadlineText + '" not found in existing RSA assets for Ad Group: ' + adGroup.getName());
        }
    }
  }
  Logger.log('RSA headline pinning process complete.');
}


15. Run an A/B test by creating a duplicate of an existing ad with one modified headline. After 14 days or 1000 impressions (whichever comes first), pause the ad variant with the lower CTR

What the code exactly does: 

Identifies a target ad (e.g., by label "Ad A/B Test Original"). Creates a duplicate ad ("Ad A/B Test Variant") with a single headline changed. It then monitors both ads. Once either 14 days pass or the variant reaches 1000 impressions, it compares their CTRs and pauses the one performing worse. (Note: This is a complex, multi-stage script requiring careful scheduling and state management using labels or external sheets.)
 

The code  (Conceptual - requires multiple runs and state):

// Google Ads Script: A/B Test Ad Copy and Pause Loser (Conceptual)
// --- Stage 1: Setup (Run Once) ---
var ORIGINAL_AD_LABEL = 'Ad A/B Test Original';
var VARIANT_AD_LABEL = 'Ad A/B Test Variant';
var NEW_HEADLINE_TEXT = 'Your New Test Headline'; // The headline text for the variant
var HEADLINE_TO_REPLACE = 1; // Which headline to replace (1, 2, or 3)

function setupAdTest() {
  Logger.log('Setting up A/B test...');
  var labelOriginal = getOrCreateLabel_(ORIGINAL_AD_LABEL);
  var labelVariant = getOrCreateLabel_(VARIANT_AD_LABEL);

  var adIterator = AdsApp.ads()
      // Find the ad to test (e.g., based on text, ID, or existing label)
      .withCondition("LabelNames CONTAINS_NONE ['" + VARIANT_AD_LABEL + "','" + ORIGINAL_AD_LABEL + "']") // Example: Find an unlabelled ad
      .withCondition('Status = ENABLED').get();

  if (adIterator.hasNext()) {
    var originalAd = adIterator.next();
    var adGroup = originalAd.getAdGroup();
    Logger.log('Found ad to test in Ad Group: ' + adGroup.getName());

    // Label the original ad
    originalAd.applyLabel(ORIGINAL_AD_LABEL);
    // Set start date using a temporary label or external sheet
    originalAd.applyLabel('TestStartDate_' + Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd'));

    // Create the variant (assuming ETA for simplicity)
     if (originalAd.isType().expandedTextAd()) {
         var etaOriginal = originalAd.asExpandedTextAd();
         var builder = adGroup.newAd().expandedTextAdBuilder()
             .withHeadlinePart1(HEADLINE_TO_REPLACE === 1 ? NEW_HEADLINE_TEXT : etaOriginal.getHeadlinePart1())
             .withHeadlinePart2(HEADLINE_TO_REPLACE === 2 ? NEW_HEADLINE_TEXT : etaOriginal.getHeadlinePart2())
             // Add H3 logic if needed
             .withDescription1(etaOriginal.getDescription1())
             .withDescription2(etaOriginal.getDescription2())
             .withPath1(etaOriginal.getPath1())
             .withPath2(etaOriginal.getPath2())
             .withFinalUrl(etaOriginal.urls().getFinalUrl());

        // Copy H3 if it exists and wasn't the one replaced
        if (etaOriginal.getHeadlinePart3() && HEADLINE_TO_REPLACE !== 3) {
             builder.withHeadlinePart3(etaOriginal.getHeadlinePart3());
        } else if (HEADLINE_TO_REPLACE === 3) {
             builder.withHeadlinePart3(NEW_HEADLINE_TEXT);
        }


         var op = builder.build();
         if (op.isSuccessful()) {
             var newAd = op.getResult();
             newAd.applyLabel(VARIANT_AD_LABEL);
              newAd.applyLabel('TestStartDate_' + Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd')); // Mark start date
             Logger.log('Created variant ad.');
         } else {
             Logger.log('Error creating variant ad: ' + op.getErrors().join(', '));
             originalAd.removeLabel(ORIGINAL_AD_LABEL); // Clean up original label if variant failed
         }
     } else {
         Logger.log('Original ad is not an Expanded Text Ad. Skipping A/B test setup.');
         originalAd.removeLabel(ORIGINAL_AD_LABEL); // Clean up
     }
  } else {
    Logger.log('No suitable ad found to start A/B test.');
  }
}

// --- Stage 2: Check and Conclude (Run Daily) ---
var TEST_DURATION_DAYS = 14;
var MIN_IMPRESSIONS_THRESHOLD = 1000;

function checkAdTestResults() {
   Logger.log('Checking A/B test results...');
   var labelOriginal = getOrCreateLabel_(ORIGINAL_AD_LABEL);
   var labelVariant = getOrCreateLabel_(VARIANT_AD_LABEL);

   // Find ads involved in tests (check for start date label presence)
   var adsInTest = {}; // {adGroupId: {originalAd: ad, variantAd: ad, startDate: date}}

   // Iterate through original ads to find pairs
   var originalAdIterator = labelOriginal.ads().get();
   while (originalAdIterator.hasNext()) {
       var ad = originalAdIterator.next();
       var startDate = getStartDateFromLabel_(ad); // Helper to extract date from label
       if (!startDate) continue;

       var adGroupId = ad.getAdGroup().getId();
        if (!adsInTest[adGroupId]) adsInTest[adGroupId] = {};
        adsInTest[adGroupId].originalAd = ad;
        adsInTest[adGroupId].startDate = startDate;
   }
    // Iterate through variant ads to complete pairs
   var variantAdIterator = labelVariant.ads().get();
    while (variantAdIterator.hasNext()) {
       var ad = variantAdIterator.next();
        var startDate = getStartDateFromLabel_(ad); // Helper to extract date from label
        if (!startDate) continue;
       var adGroupId = ad.getAdGroup().getId();
        if (adsInTest[adGroupId] && !adsInTest[adGroupId].variantAd) { // Ensure original was found
           adsInTest[adGroupId].variantAd = ad;
        }
    }


   // Evaluate each test pair
   var today = new Date();
   for (var adGroupId in adsInTest) {
       var test = adsInTest[adGroupId];
       if (!test.originalAd || !test.variantAd) {
           Logger.log('Incomplete test pair in Ad Group ID: ' + adGroupId + '. Skipping.');
           continue; // Incomplete pair
       }

       var daysElapsed = (today.getTime() - test.startDate.getTime()) / (1000 * 3600 * 24);
       var variantStats = test.variantAd.getStatsFor('ALL_TIME'); // Get stats since creation
       var variantImpressions = variantStats.getImpressions();

       if (daysElapsed >= TEST_DURATION_DAYS || variantImpressions >= MIN_IMPRESSIONS_THRESHOLD) {
           Logger.log('Test concluding for Ad Group ID: ' + adGroupId);
           var originalStats = test.originalAd.getStatsFor('ALL_TIME'); // Compare stats over the same period

           // Handle potential division by zero
           var originalCtr = originalStats.getClicks() > 0 ? originalStats.getClicks() / originalStats.getImpressions() : 0;
           var variantCtr = variantStats.getClicks() > 0 ? variantStats.getClicks() / variantStats.getImpressions() : 0;

           Logger.log(' Original CTR: ' + originalCtr.toFixed(4) + ', Variant CTR: ' + variantCtr.toFixed(4));

           var loserAd = null;
           var winnerAd = null;

           if (variantCtr > originalCtr) {
               loserAd = test.originalAd;
               winnerAd = test.variantAd;
               Logger.log(' Winner: Variant Ad');
           } else { // Includes tie, pause variant by default in tie
               loserAd = test.variantAd;
               winnerAd = test.originalAd;
                Logger.log(' Winner: Original Ad (or tie)');
           }

           loserAd.pause();
           // Clean up labels
           loserAd.removeLabel(loserAd === test.originalAd ? ORIGINAL_AD_LABEL : VARIANT_AD_LABEL);
           winnerAd.removeLabel(winnerAd === test.originalAd ? ORIGINAL_AD_LABEL : VARIANT_AD_LABEL);
           // Remove date labels
           removeDateLabel_(loserAd);
           removeDateLabel_(winnerAd);

           Logger.log('Paused losing ad and cleaned labels for Ad Group ID: ' + adGroupId);

       } else {
           Logger.log('Test ongoing for Ad Group ID: ' + adGroupId + '. Days: ' + daysElapsed.toFixed(1) + ', Impressions: ' + variantImpressions);
       }
   }
}

// Helper Functions (Implement these)
function getOrCreateLabel_(labelName) {
  var labelIterator = AdsApp.labels().withCondition("Name = '" + labelName + "'").get();
  if (labelIterator.hasNext()) {
    return labelIterator.next();
  } else {
    AdsApp.createLabel(labelName);
    // Need to re-fetch after creation
    return AdsApp.labels().withCondition("Name = '" + labelName + "'").get().next();
  }
}
function getStartDateFromLabel_(ad) {
    var labels = ad.labels().get();
    while(labels.hasNext()){
        var label = labels.next();
        if(label.getName().startsWith('TestStartDate_')){
            var dateStr = label.getName().split('_')[1]; // YYYYMMDD
            return new Date(dateStr.substring(0,4), parseInt(dateStr.substring(4,6))-1, dateStr.substring(6,8));
        }
    }
    return null;
}
 function removeDateLabel_(ad) {
     var labels = ad.labels().get();
    while(labels.hasNext()){
        var label = labels.next();
        if(label.getName().startsWith('TestStartDate_')){
             ad.removeLabel(label.getName());
             return;
        }
    }
 }

// You would call setupAdTest() once, then schedule checkAdTestResults() to run daily.


16. Generate a monthly PDF report summarizing key account metrics (Cost, Conversions, CPA, CTR) and email it to '[email protected]'

What the code exactly does: 

Fetches account-level performance data for the previous month. It formats this data into a simple summary string. It then sends an email containing this summary to the specified address. (Generating a formatted PDF directly is not a standard feature of basic Google Ads Scripts; this example sends a text summary.)
 

The code:

// Google Ads Script: Monthly Account Summary Email Report
var RECIPIENT_EMAIL = '[email protected]'; // Replace with recipient email
var EMAIL_SUBJECT = 'Monthly Google Ads Performance Summary';
var DATE_RANGE = 'LAST_MONTH';

function main() {
  var account = AdsApp.currentAccount();
  var stats = account.getStatsFor(DATE_RANGE);

  var cost = stats.getCost();
  var conversions = stats.getConversions();
  var clicks = stats.getClicks();
  var impressions = stats.getImpressions();

  var cpa = (conversions > 0) ? (cost / conversions) : 0;
  var ctr = (impressions > 0) ? (clicks / impressions) : 0;

  // Get month name for reporting period
  var lastMonth = new Date();
  lastMonth.setDate(0); // Go to last day of previous month
  var monthName = lastMonth.toLocaleString('default', { month: 'long' });
  var year = lastMonth.getFullYear();

  var emailBody = 'Google Ads Performance Summary for ' + monthName + ' ' + year + ':\n\n' +
                  'Account: ' + account.getName() + ' (' + account.getAccountId() + ')\n\n' +
                  'Cost: $' + cost.toFixed(2) + '\n' +
                  'Conversions: ' + conversions.toFixed(2) + '\n' + // Use toFixed(2) for potential fractional conversions
                  'Cost Per Acquisition (CPA): $' + cpa.toFixed(2) + '\n' +
                  'Clicks: ' + clicks + '\n' +
                  'Impressions: ' + impressions + '\n' +
                  'Click-Through Rate (CTR): ' + (ctr * 100).toFixed(2) + '%\n\n' +
                  'Report generated on: ' + new Date();

  MailApp.sendEmail(RECIPIENT_EMAIL, EMAIL_SUBJECT, emailBody);
  Logger.log('Monthly summary email sent to ' + RECIPIENT_EMAIL);

  // For more complex reports (like PDF), export data to Google Sheets and potentially use App Script for formatting/PDF generation.
  Logger.log('Note: Direct PDF generation is not standard in Ads Scripts. Consider Google Sheets + App Script for PDF reports.');
}


17. Monitor daily campaign spend. If any campaign's spend increases by more than 50% compared to the previous day, send an email alert

What the code exactly does: 

Compares the cost of each active campaign today versus yesterday. If any campaign's cost has increased by more than 50%, it sends an immediate email alert detailing the campaign and the percentage increase.
 

The code:

// Google Ads Script: Daily Spend Spike Alert
var SPEND_INCREASE_THRESHOLD = 0.50; // 50% increase
var MIN_SPEND_FOR_ALERT = 10.00; // Minimum spend yesterday to avoid alerts on tiny amounts
var ALERT_EMAIL = '[email protected]'; // Email for alerts

function main() {
  Logger.log('Checking for significant daily spend increases...');
  var campaignsToCheck = AdsApp.campaigns()
      .withCondition('Status = ENABLED')
      .get();

  var alertsToSend = [];

  while (campaignsToCheck.hasNext()) {
    var campaign = campaignsToCheck.next();
    var statsToday = campaign.getStatsFor('TODAY');
    var statsYesterday = campaign.getStatsFor('YESTERDAY');

    var costToday = statsToday.getCost();
    var costYesterday = statsYesterday.getCost();

    if (costYesterday >= MIN_SPEND_FOR_ALERT && costToday > costYesterday * (1 + SPEND_INCREASE_THRESHOLD)) {
       var increasePercent = ((costToday - costYesterday) / costYesterday) * 100;
       var alertMsg = 'Campaign Spend Alert: Campaign "' + campaign.getName() +
                      '" spend increased by ' + increasePercent.toFixed(1) +
                      '%. Yesterday: $' + costYesterday.toFixed(2) +
                      ', Today (so far): $' + costToday.toFixed(2);
       Logger.log(alertMsg);
       alertsToSend.push(alertMsg);
    }
  }

  if (alertsToSend.length > 0) {
     var emailSubject = 'Google Ads Alert: Significant Campaign Spend Increase Detected';
     var emailBody = 'The following campaigns showed a spend increase of over ' + (SPEND_INCREASE_THRESHOLD * 100) + '% compared to yesterday:\n\n' +
                     alertsToSend.join('\n\n');
     MailApp.sendEmail(ALERT_EMAIL, emailSubject, emailBody);
     Logger.log('Spend increase alert email sent to ' + ALERT_EMAIL);
  } else {
     Logger.log('No significant spend increases detected.');
  }
}


18. Check product prices in a Google Merchant Center feed (accessed via a supplemental feed in Google Sheets) against the prices in Google Shopping ads daily. Log any discrepancies to a separate sheet

What the code exactly does: 

Reads product IDs and prices from a Google Sheet acting as a supplemental feed. It then iterates through enabled products in Google Shopping campaigns, retrieving their current price attribute. If the price in the Shopping campaign differs from the price in the Google Sheet, it logs the Product ID, Shopping Ad Price, and Sheet Price to a "Discrepancy Log" sheet. (Note: Directly accessing the current price attribute of a specific product within a Shopping ad via Google Ads Scripts is limited.)
 

The code:

// Google Ads Script: Sync Shopping Prices with Google Sheet Feed
var FEED_SHEET_URL = 'YOUR_SUPPLEMENTAL_FEED_SHEET_URL'; // Sheet with ID and Price
var FEED_SHEET_NAME = 'FeedData';
var LOG_SHEET_URL = 'YOUR_PRICE_DISCREPANCY_LOG_SHEET_URL'; // Sheet for logging errors
var LOG_SHEET_NAME = 'DiscrepancyLog';
var PRODUCT_ID_COLUMN = 'id'; // Column name for Product ID in Sheet
var PRICE_COLUMN = 'price';   // Column name for Price in Sheet

function main() {
  var feedPrices = getFeedPricesFromSheet_(); // { 'productId': price (as string e.g., "19.99 USD") }
  var logSheet = SpreadsheetApp.openByUrl(LOG_SHEET_URL).getSheetByName(LOG_SHEET_NAME);
  // Ensure header row exists in log sheet
   if (logSheet.getLastRow() < 1) {
       logSheet.appendRow(['Timestamp', 'Product ID', 'Shopping Ad Price', 'Feed Sheet Price', 'Campaign', 'Ad Group']);
   }
   var discrepanciesFound = 0;

  Logger.log('Checking Shopping product prices against feed sheet...');

  var productIterator = AdsApp.shoppingProducts()
      .withCondition('Status = ENABLED')
      .withCondition('AdGroupStatus = ENABLED')
      .withCondition('CampaignStatus = ENABLED')
      .get();

  while (productIterator.hasNext()) {
    var product = productIterator.next();
    var dimensions = product.getDimensions(); // Includes OfferId (product ID)
    var productId = dimensions.offerId; // Get product ID

    if (feedPrices[productId]) {
      var feedPrice = feedPrices[productId]; // Price from sheet (string)
      var adPriceValue = null;
      var adPriceCurrency = null;

       // Get product's current price attribute (this can be complex)
       // Using .getStats() won't work. Need to access product item attributes.
       // Direct access to shopping item attributes like price is limited in Ads Scripts.
       // A common workaround is using Labels or checking landing pages, or assuming consistency with feed.
       // This example logs a limitation message.

       // --- Placeholder: Log limitation ---
       // Logger.log('Direct price attribute check for product ' + productId + ' is limited in Ads Scripts.');
       // Alternative: Check if a 'Price Mismatch' label exists, applied by another process?

       // --- Conceptual check (if price *could* be accessed): ---
       /*
       var shoppingAd = product.getAdGroup(). // ... somehow get the specific Shopping Ad object related to this product group... This mapping is complex.
       // Hypothetical access:
       // var adPriceObj = shoppingAd.getPrice(); // If such a method existed
       // if (adPriceObj) {
       //    adPriceValue = adPriceObj.getAmount();
       //    adPriceCurrency = adPriceObj.getCurrencyCode();
       // }
       // var adPriceString = adPriceValue + ' ' + adPriceCurrency; // e.g., "19.99 USD"

       // Normalize feed price string if necessary (e.g., remove currency)
       var normalizedFeedPrice = feedPrice.split(' ')[0];
       var normalizedAdPrice = adPriceValue; // Assuming numeric

       if (normalizedFeedPrice != normalizedAdPrice) { // Compare values
         Logger.log('Price discrepancy found for Product ID: ' + productId);
         logSheet.appendRow([new Date(), productId, adPriceString, feedPrice, product.getCampaign().getName(), product.getAdGroup().getName()]);
         discrepanciesFound++;
       }
       */
    }
  }

   // Log summary message about limitations
   if (discrepanciesFound === 0) { // Only log this if no *actual* checks were possible
       Logger.log('Price comparison finished. NOTE: Direct comparison of Shopping Ad price attribute with feed via Ads Scripts is limited. Consider using the Content API or manual checks for full accuracy.');
   } else {
       Logger.log('Price comparison finished. Found ' + discrepanciesFound + ' potential discrepancies (based on hypothetical access). Check log sheet.');
   }
}

// Helper to get prices from Sheet
function getFeedPricesFromSheet_() {
    var ss = SpreadsheetApp.openByUrl(FEED_SHEET_URL);
    var sheet = ss.getSheetByName(FEED_SHEET_NAME);
    var data = sheet.getDataRange().getValues();
    var headers = data.shift();
    var idCol = headers.indexOf(PRODUCT_ID_COLUMN);
    var priceCol = headers.indexOf(PRICE_COLUMN);
    var prices = {};

    if(idCol === -1 || priceCol === -1) {
        throw new Error('Could not find Product ID or Price columns in feed sheet.');
    }

    data.forEach(function(row) {
        var id = row[idCol];
        var price = row[priceCol]; // Keep as string as format might include currency
        if (id && price) {
            prices[id.toString().trim()] = price.toString().trim();
        }
    });
    return prices;
}


19. Check the landing page for each active Shopping product daily. If the landing page contains the text "Out of Stock", pause the corresponding product partition (ad group criteria)

What the code exactly does: 

Iterates through active product partitions in Shopping campaigns. For each product, it fetches the final URL (landing page). It then fetches the content of that URL and checks if it contains the phrase "Out of Stock" (case-insensitive). If found, it pauses the product partition (or the ad group if structure is simple). (Note: Fetching external URLs is subject to quotas and timeouts. Reliably parsing diverse website structures can be challenging.)
 

The code:

// Google Ads Script: Pause Out of Stock Shopping Products by Checking Landing Page
var OUT_OF_STOCK_TEXT = 'Out of Stock'; // Case-insensitive check
var MAX_PRODUCTS_TO_CHECK = 100; // Limit checks to avoid exceeding URL Fetch quota/time limits
var CHECKED_LABEL_NAME = 'OOS_Checked_Today'; // Optional: Label products checked today to avoid re-checking if script runs multiple times

function main() {
  Logger.log('Checking shopping product landing pages for "' + OUT_OF_STOCK_TEXT + '" text...');
  var checkedCount = 0;
  var pausedCount = 0;
  // Optional: Create/get label
  var checkedLabel = getOrCreateLabel_(CHECKED_LABEL_NAME);
  var todayDate = Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd');


  var productIterator = AdsApp.shoppingProductGroups()
    // Target leaf nodes (actual products)
    .withCondition("Type = UNIT")
    .withCondition('Status = ENABLED')
    .withCondition('AdGroupStatus = ENABLED')
    .withCondition('CampaignStatus = ENABLED')
    // Optional: Avoid checking already labelled items today
    // .withCondition("LabelNames CONTAINS_NONE ['" + CHECKED_LABEL_NAME + "_" + todayDate + "']")
    .get();

  while (productIterator.hasNext() && checkedCount < MAX_PRODUCTS_TO_CHECK) {
    var productGroup = productIterator.next(); // This is ShoppingProductGroup (a criterion)
    var finalUrl = productGroup.urls().getFinalUrl(); // Get the landing page URL

    if (!finalUrl) {
      Logger.log('Skipping product group in Ad Group "' + productGroup.getAdGroup().getName() + '" - No Final URL found.');
      continue;
    }

    checkedCount++;
    // Optional: Apply today's checked label
    // productGroup.applyLabel(CHECKED_LABEL_NAME + "_" + todayDate);

    var isOutOfStock = false;
    try {
      var response = UrlFetchApp.fetch(finalUrl, { muteHttpExceptions: true }); // Fetch page content
      var responseCode = response.getResponseCode();
      var content = response.getContentText();

      if (responseCode === 200) {
        if (content.toLowerCase().indexOf(OUT_OF_STOCK_TEXT.toLowerCase()) !== -1) {
          isOutOfStock = true;
        }
      } else {
        Logger.log('Warning: Could not fetch URL (' + responseCode + ') for product in Ad Group "' + productGroup.getAdGroup().getName() + '": ' + finalUrl);
      }
    } catch (e) {
      Logger.log('Error fetching URL for product in Ad Group "' + productGroup.getAdGroup().getName() + '": ' + finalUrl + ' - Error: ' + e);
    }

    if (isOutOfStock) {
      productGroup.pause(); // Pause the specific product group (criterion)
      pausedCount++;
       var productId = productGroup.getDimensions().offerId || productGroup.getDimensions().productType || 'N/A'; // Get some identifier
      Logger.log('Paused product group for "' + productId + '" in Ad Group "' + productGroup.getAdGroup().getName() + '" - Found "' + OUT_OF_STOCK_TEXT + '" at URL: ' + finalUrl);
    }
  }

   Logger.log('Finished checking landing pages. Checked: ' + checkedCount + ', Paused: ' + pausedCount);
   // Optional: Clean up old date labels from previous days
}

 // Helper function (same as in A/B test example)
function getOrCreateLabel_(labelName) {
  var labelIterator = AdsApp.labels().withCondition("Name = '" + labelName + "'").get();
  if (labelIterator.hasNext()) {
    return labelIterator.next();
  } else {
    AdsApp.createLabel(labelName);
    Utilities.sleep(1000); // Short pause after creation
    return AdsApp.labels().withCondition("Name = '" + labelName + "'").get().next();
  }
}


20. Apply a +20% bid adjustment to remarketing audiences (in specific campaigns) if their conversion rate over the last 30 days is 25% higher than the campaign average

What the code exactly does: 

Iterates through specified campaigns. For each campaign, it calculates the average conversion rate. Then, it examines the performance of attached remarketing audiences (user lists) over the last 30 days. If a remarketing audience's conversion rate is at least 25% better than the campaign average, its bid adjustment is set to +20%.
 

The code:

// Google Ads Script: Increase Bid Adjustment for High-Converting Remarketing Audiences
var CONV_RATE_LIFT_THRESHOLD = 1.25; // Audience CR must be 25% higher than campaign avg
var BID_ADJUSTMENT_TO_SET = 1.20;   // Set bid adjustment to +20%
var DATE_RANGE = 'LAST_30_DAYS';
var MIN_AUDIENCE_CLICKS = 10; // Minimum clicks for audience stat significance
// Specify campaigns or leave empty for all with audiences
var TARGET_CAMPAIGN_NAMES = ['Your RLSA Campaign 1', 'Your RLSA Campaign 2'];

function main() {
  Logger.log('Adjusting remarketing audience bid adjustments based on performance (' + DATE_RANGE + ')');

  var campaignIterator = AdsApp.campaigns()
      .withCondition('Status = ENABLED')
      .(TARGET_CAMPAIGN_NAMES.length > 0 ? .withCondition('Name IN ["' + TARGET_CAMPAIGN_NAMES.join('","') + '"] ') : '')
      .get();

  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    var campaignStats = campaign.getStatsFor(DATE_RANGE);
    var campaignClicks = campaignStats.getClicks();
    var campaignConversions = campaignStats.getConversions();

    if (campaignClicks === 0) {
      Logger.log('Skipping campaign "' + campaign.getName() + '" - No clicks in date range.');
      continue;
    }
    var campaignConvRate = campaignConversions / campaignClicks;
     Logger.log('Campaign "' + campaign.getName() + '" Avg Conv Rate: ' + (campaignConvRate * 100).toFixed(2) + '%');


    // Get audiences attached to this campaign
    var audienceIterator = campaign.targeting().audiences()
        .withCondition('Status = ENABLED') // Look at enabled audience criteria
        .forDateRange(DATE_RANGE) // Get stats for the audience in this campaign
        .withCondition('Clicks >= ' + MIN_AUDIENCE_CLICKS) // Ensure some data
        .get();

    while (audienceIterator.hasNext()) {
      var campaignAudience = audienceIterator.next(); // This is CampaignAudience object
      var audienceStats = campaignAudience.getStats(); // Stats for this audience in this campaign
      var audienceClicks = audienceStats.getClicks();
      var audienceConversions = audienceStats.getConversions();

      // Check if it's a UserList (remarketing list) - may need refinement based on API object structure
       var audienceId = campaignAudience.getAudienceId(); // Gets criteria ID, need user list ID if possible
       var audienceName = campaignAudience.getName(); // Often reflects list name
       // var userList = campaignAudience.getUserList(); // Hypothetical direct access

       // Assuming it's a user list based on context or naming convention
       Logger.log(' Checking Audience: "' + audienceName + '" in Campaign "' + campaign.getName() + '"');


      if (audienceClicks > 0) {
         var audienceConvRate = audienceConversions / audienceClicks;
          Logger.log('  Audience Conv Rate: ' + (audienceConvRate * 100).toFixed(2) + '%');

         if (campaignConvRate > 0 && audienceConvRate >= campaignConvRate * CONV_RATE_LIFT_THRESHOLD) {
             // Check if current adjustment is already correct
            var currentAdjustment = campaignAudience.bidding().getBidModifier();
            if (currentAdjustment !== BID_ADJUSTMENT_TO_SET) {
               campaignAudience.bidding().setBidModifier(BID_ADJUSTMENT_TO_SET);
               Logger.log('  >>> Setting Bid Adjustment to ' + BID_ADJUSTMENT_TO_SET.toFixed(2) + ' for Audience ID ' + audienceId + ' (' + audienceName + ') in Campaign "' + campaign.getName() + '". (Conv Rate: ' + (audienceConvRate * 100).toFixed(2) + '%)');
            } else {
                Logger.log('  Bid adjustment already set correctly for Audience ID ' + audienceId);
            }

         } else if (campaignConvRate === 0 && audienceConversions > 0) {
             // Campaign has 0 conv rate, but audience has conversions - potentially increase bid
              var currentAdjustment = campaignAudience.bidding().getBidModifier();
               if (currentAdjustment !== BID_ADJUSTMENT_TO_SET) {
                  campaignAudience.bidding().setBidModifier(BID_ADJUSTMENT_TO_SET);
                  Logger.log('  >>> Setting Bid Adjustment to ' + BID_ADJUSTMENT_TO_SET.toFixed(2) + ' for Audience ID ' + audienceId + ' (' + audienceName + ') in Campaign "' + campaign.getName() + '". (Only audience converted)');
               } else {
                   Logger.log('  Bid adjustment already set correctly for Audience ID ' + audienceId);
               }

         } else {
             Logger.log('  Audience performance does not meet threshold for bid increase.');
             // Optional: Add logic to decrease bids for underperforming audiences
         }
      }
    }
  }
  Logger.log('Remarketing audience bid adjustment process complete.');
}

By following the steps outlined above and utilizing or adapting the script examples provided, you can start harnessing the power of Google Ads Scripts. They offer a pathway to automate tedious tasks, implement sophisticated custom strategies, gain efficiencies, and potentially unlock new levels of performance in your advertising efforts. Begin with simpler scripts that address clear pain points in your workflow and gradually explore more complex automation possibilities as you become more comfortable with JavaScript and the Google Ads Scripts environment.

Back to How To
  • Share
  • facebook
  • icon_linkedin
  • icon_x