It All Started With An Idea

Min & Max Automating Bid Management Script: Enhancing Campaign Performance

Overview of the Script

In the intricate world of digital advertising, efficiency is paramount. Enter the Google Ads script—an unsung hero designed to streamline bid management within campaigns. Its mission? To optimize ad groups by leveraging historical data on conversions, Click-Through Rate (CTR), and Click Share to set a Min or Max bid within the constraint you set + its FREE. Let’s dissect its core functions:

  1. Initialization: The script checks for the existence of a label (‘Processing’)—a backstage pass for ad groups currently under scrutiny. If the label isn’t there, it creates one, ensuring a smooth performance.
  2. Bid Adjustment: Picture the script as a seasoned conductor. It processes ad groups across specific time frames (last 30, 14, and 7 days), deftly adjusting bids. Upward or downward, it fine-tunes each note, aiming for harmony.
  3. Cleanup: After the grand performance, the script steps out of the limelight. It removes the ‘Processing’ label from all ad groups, leaving the stage pristine for future acts.

Key Functions Unveiled

  • adjustBidsAcrossTimeFrames(): Our time-traveler. It hops between epochs, recalibrating bids based on real-world metrics. Think of it as a savvy historian, learning from the past to shape the future.
  • processAdGroups(): The talent scout. It seeks out ad groups with the right moves—conversions, clicks—and whispers, “You’re the star!” Then, it adjusts bids, lifting them toward stardom or humbling them gently.
  • optimizeCpcBid(): The budget whisperer. It calculates the perfect Cost Per Click (CPC), never overshooting or underselling. Like a financial advisor, it balances risk and reward.
  • calculateClickShare(): The popularity gauge. It measures an ad group’s click share—how loudly its applause echoes in the campaign hall. A socialite, it knows who’s trending.
  • predictCtrIncrease(): The fortune teller. It predicts CTR improvements, nudging bids toward destiny. “Raise the curtain!” it cries, revealing hidden potential.

Expected Impact on Managing Bids

  • Efficiency: Automation saves time, allowing marketers to focus on strategy. Bid adjustments happen seamlessly, like choreography in motion.
  • Performance Optimization: Data-driven decisions lead to better ad placements and potentially increased ROI. It’s like tuning an instrument for a flawless symphony.
  • Scalability: The script scales effortlessly, handling multiple ad groups and time frames. No backstage chaos—just synchronized performance.
  • Insights and Reporting: Detailed logs provide transparency. Marketers can track bid adjustments, learn from outcomes, and refine their strategy.

So, my friend, next time you sip your coffee, raise it to our unsung hero—the backstage wizard optimizing bids, one click at a time. 🎩✨

var ABSOLUTE_MAX_BID = 6.00; // Maximum bid allowed var ABSOLUTE_MIN_BID = 4.25; // Minimum bid allowed var MIN_CONVERSIONS = 5; // Minimum number of conversions required to make a bid increase. var LABEL_PROCESSING = ‘Processing’; function main() { checkLabelExists(LABEL_PROCESSING, “AdWords Scripts label used to process bids”); applyProcessingLabel(); Logger.log(‘\n***** Adjusting Bids Over Time Periods *****’); adjustBidsAcrossTimeFrames(); cleanup(); } function adjustBidsAcrossTimeFrames() { var timeFrames = [“LAST_30_DAYS”, “LAST_14_DAYS”, “LAST_7_DAYS”]; timeFrames.forEach(function(timeFrame) { Logger.log(‘\n***** Processing Time Frame: ‘ + timeFrame + ‘ *****’); adjustBidsForTimeFrame(timeFrame); }); } function adjustBidsForTimeFrame(timeFrame) { setAdGroupsToMax(timeFrame); decreaseHighCostAdGroups(timeFrame); } function setAdGroupsToMax(dateRange) { processAdGroups(dateRange, true); } function decreaseHighCostAdGroups(dateRange) { processAdGroups(dateRange, false); } function processAdGroups(dateRange, isIncreasing) { var condition = isIncreasing ? “Conversions > ” + (MIN_CONVERSIONS – 1) : “Conversions < ” + MIN_CONVERSIONS; var adGroupTypes = ; adGroupTypes.forEach(function(adGroupType) { var adGroupIterator = adGroupType .forDateRange(dateRange) .withCondition(“LabelNames CONTAINS_ANY [‘” + LABEL_PROCESSING + “‘]”) .withCondition(condition) .withCondition(“Clicks > 0”) .get(); while (adGroupIterator.hasNext()) { var adGroup = adGroupIterator.next(); var currentCpc = adGroup.bidding().getCpc(); var optimizedCpc = optimizeCpcBid(adGroup, dateRange, currentCpc); adGroup.bidding().setCpc(optimizedCpc); } }); } function optimizeCpcBid(adGroup, dateRange, currentCpc) { var stats = adGroup.getStatsFor(dateRange); var clicks = stats.getClicks(); var impressions = stats.getImpressions(); var conversions = stats.getConversions(); var ctr = clicks / impressions; var currentClickShare = calculateClickShare(adGroup, dateRange); var predictedCtrIncrease = predictCtrIncrease(ctr, conversions, clicks); var optimalBid = currentCpc * predictedCtrIncrease; var adjustedCpc = Math.min(Math.max(optimalBid, ABSOLUTE_MIN_BID), ABSOLUTE_MAX_BID); Logger.log(‘Optimized CPC for AdGroup ID ‘ + adGroup.getId() + ‘ from ‘ + currentCpc.toFixed(2) + ‘ to ‘ + adjustedCpc.toFixed(2) + ‘. Expected CTR improvement: ‘ + predictedCtrIncrease.toFixed(2) + ‘ times. ‘ + ‘Current Click Share: ‘ + currentClickShare.toFixed(2) + ‘, Expected impact on Click Share not directly predictable.’); return adjustedCpc; } function predictCtrIncrease(ctr, conversions, clicks) { return 1 + (conversions / 1000 + ctr / 10); } function calculateClickShare(adGroup, dateRange) { var totalClicks = AdsApp.report( “SELECT Clicks ” + “FROM CAMPAIGN_PERFORMANCE_REPORT ” + “WHERE CampaignId = ‘” + adGroup.getCampaign().getId() + “‘ ” + “DURING ” + dateRange ).rows().next()[‘Clicks’]; var adGroupClicks = adGroup.getStatsFor(dateRange).getClicks(); return adGroupClicks / totalClicks; } function checkLabelExists(labelName, description) { var labelIterator = AdsApp.labels() .withCondition(“Name = ‘” + labelName + “‘”) .get(); if (!labelIterator.hasNext()) { AdsApp.createLabel(labelName, description); Logger.log(“Label created: ” + labelName); } else { Logger.log(“Label exists: ” + labelName); } } function applyProcessingLabel() { var adGroups = AdsApp.adGroups().get(); while (adGroups.hasNext()) { var adGroup = adGroups.next(); adGroup.applyLabel(LABEL_PROCESSING); } Logger.log(“Processing label applied to all ad groups”); } function cleanup() { var cleanupList = ; cleanupList.forEach(function(list) { var iterator = list.withCondition(“LabelNames CONTAINS_ANY [‘” + LABEL_PROCESSING + “‘]”).get(); while (iterator.hasNext()) { var adGroup = iterator.next(); adGroup.removeLabel(LABEL_PROCESSING); } }); Logger.log(“Cleanup complete, labels removed”); }

About the author

John M. Williams — Founder, It All Started With An Idea. Senior paid media specialist with 15+ years and $350M+ in managed ad spend across Google, Meta, LinkedIn, and Amazon. Creator of Buddy, the open-source AI Google Ads agent. Hero Conf 2025 + 2026 speaker.

More about John · LinkedIn ↗ · GitHub ↗

Working notes

More from the blog.

Read the blog  Get a free audit