ATTN.
← Back to Blog

2026-03-12

Advanced Google Ads Scripts for DTC Automation and Optimization

Advanced Google Ads Scripts for DTC Automation and Optimization

Google Ads Scripts provide powerful automation capabilities that can transform how DTC brands manage their advertising campaigns. From automated bid adjustments to performance monitoring and budget optimization, scripts enable sophisticated campaign management that would be impossible to execute manually at scale.

This comprehensive guide covers advanced Google Ads Scripts specifically designed for DTC brands, including custom automation strategies, performance optimization techniques, and scalable management solutions.

Essential Automation Scripts for DTC Brands

1. Automated Bid Management Script

function automatedBidManagement() {
  // Configuration
  const CONFIG = {
    TARGET_ROAS: 4.0,
    BID_ADJUSTMENT_THRESHOLD: 0.15,
    MIN_CONVERSIONS: 5,
    LOOKBACK_DAYS: 14,
    MAX_BID_INCREASE: 0.5,
    MAX_BID_DECREASE: 0.3
  };
  
  // Get campaigns with Smart Bidding
  const campaigns = AdsApp.campaigns()
    .withCondition('Status = ENABLED')
    .withCondition('BiddingStrategyType = TARGET_ROAS')
    .get();
  
  while (campaigns.hasNext()) {
    const campaign = campaigns.next();
    optimizeCampaignBids(campaign, CONFIG);
  }
}

function optimizeCampaignBids(campaign, config) {
  const stats = campaign.getStatsFor('LAST_' + config.LOOKBACK_DAYS + '_DAYS');
  
  if (stats.getConversions() < config.MIN_CONVERSIONS) {
    Logger.log(`Campaign ${campaign.getName()} - Insufficient data for optimization`);
    return;
  }
  
  const currentRoas = stats.getConversionValue() / stats.getCost();
  const targetRoas = config.TARGET_ROAS;
  const roasGap = (currentRoas - targetRoas) / targetRoas;
  
  if (Math.abs(roasGap) < config.BID_ADJUSTMENT_THRESHOLD) {
    Logger.log(`Campaign ${campaign.getName()} - ROAS within acceptable range`);
    return;
  }
  
  // Adjust bids for keywords
  adjustKeywordBids(campaign, roasGap, config);
  
  // Log optimization action
  Logger.log(`Campaign ${campaign.getName()} - ROAS Gap: ${roasGap.toFixed(2)}, Adjustments made`);
}

function adjustKeywordBids(campaign, roasGap, config) {
  const keywords = campaign.keywords()
    .withCondition('Status = ENABLED')
    .withCondition('Conversions > 0')
    .get();
  
  while (keywords.hasNext()) {
    const keyword = keywords.next();
    const stats = keyword.getStatsFor('LAST_' + config.LOOKBACK_DAYS + '_DAYS');
    const keywordRoas = stats.getConversionValue() / stats.getCost();
    
    if (keywordRoas > config.TARGET_ROAS * 1.2) {
      // Increase bid for high-performing keywords
      const currentBid = keyword.bidding().getCpc();
      const newBid = currentBid * (1 + Math.min(config.MAX_BID_INCREASE, roasGap));
      keyword.bidding().setCpc(newBid);
      
    } else if (keywordRoas < config.TARGET_ROAS * 0.8) {
      // Decrease bid for poor-performing keywords
      const currentBid = keyword.bidding().getCpc();
      const newBid = currentBid * (1 - Math.min(config.MAX_BID_DECREASE, Math.abs(roasGap)));
      keyword.bidding().setCpc(newBid);
    }
  }
}

2. Dynamic Budget Allocation Script

function dynamicBudgetAllocation() {
  const CONFIG = {
    TOTAL_DAILY_BUDGET: 5000,
    MIN_BUDGET_PER_CAMPAIGN: 50,
    PERFORMANCE_LOOKBACK_DAYS: 7,
    BUDGET_ADJUSTMENT_FREQUENCY: 'DAILY'
  };
  
  const campaignPerformance = analyzeCampaignPerformance(CONFIG);
  const budgetAllocations = calculateOptimalBudgetAllocation(campaignPerformance, CONFIG);
  
  implementBudgetChanges(budgetAllocations);
}

function analyzeCampaignPerformance(config) {
  const campaigns = AdsApp.campaigns()
    .withCondition('Status = ENABLED')
    .get();
  
  const performanceData = [];
  
  while (campaigns.hasNext()) {
    const campaign = campaigns.next();
    const stats = campaign.getStatsFor('LAST_' + config.PERFORMANCE_LOOKBACK_DAYS + '_DAYS');
    
    const performance = {
      campaign: campaign,
      name: campaign.getName(),
      spend: stats.getCost(),
      conversions: stats.getConversions(),
      conversionValue: stats.getConversionValue(),
      roas: stats.getConversionValue() / stats.getCost(),
      cpa: stats.getCost() / stats.getConversions(),
      impressionShare: campaign.getSearchImpressionShare(),
      currentBudget: campaign.getBudget().getAmount()
    };
    
    performanceData.push(performance);
  }
  
  return performanceData;
}

function calculateOptimalBudgetAllocation(performanceData, config) {
  // Calculate efficiency scores
  performanceData.forEach(campaign => {
    campaign.efficiencyScore = calculateEfficiencyScore(campaign);
  });
  
  // Sort by efficiency score
  performanceData.sort((a, b) => b.efficiencyScore - a.efficiencyScore);
  
  // Allocate budget based on performance and opportunity
  let remainingBudget = config.TOTAL_DAILY_BUDGET;
  const allocations = [];
  
  performanceData.forEach(campaign => {
    let allocatedBudget = Math.max(
      config.MIN_BUDGET_PER_CAMPAIGN,
      remainingBudget * (campaign.efficiencyScore / getTotalEfficiencyScore(performanceData))
    );
    
    // Consider impression share opportunity
    if (campaign.impressionShare < 0.8 && campaign.roas > 3.0) {
      allocatedBudget *= 1.2; // 20% boost for high-opportunity campaigns
    }
    
    allocations.push({
      campaign: campaign.campaign,
      currentBudget: campaign.currentBudget,
      newBudget: Math.min(allocatedBudget, remainingBudget),
      change: allocatedBudget - campaign.currentBudget,
      reason: `Efficiency Score: ${campaign.efficiencyScore.toFixed(2)}`
    });
    
    remainingBudget -= allocatedBudget;
  });
  
  return allocations;
}

function calculateEfficiencyScore(campaign) {
  // Multi-factor efficiency calculation
  const roasScore = Math.min(campaign.roas / 4.0, 2.0); // Cap at 2x for ROAS > 8
  const volumeScore = Math.log(campaign.conversions + 1) / 10;
  const opportunityScore = (1 - campaign.impressionShare) * 2;
  
  return roasScore * 0.6 + volumeScore * 0.2 + opportunityScore * 0.2;
}

3. Automated Keyword Harvesting Script

function automatedKeywordHarvesting() {
  const CONFIG = {
    MIN_IMPRESSIONS: 100,
    MIN_CLICKS: 5,
    MAX_CPC_MULTIPLIER: 1.5,
    SEARCH_TERMS_LOOKBACK: 30,
    AUTO_ADD_THRESHOLD: {
      conversions: 2,
      ctr: 0.05,
      conversionRate: 0.02
    }
  };
  
  const campaigns = AdsApp.campaigns()
    .withCondition('Status = ENABLED')
    .get();
  
  while (campaigns.hasNext()) {
    const campaign = campaigns.next();
    harvestKeywordsFromCampaign(campaign, CONFIG);
  }
}

function harvestKeywordsFromCampaign(campaign, config) {
  const searchTermsQuery = `
    SELECT SearchTermView.search_term,
           metrics.impressions,
           metrics.clicks,
           metrics.conversions,
           metrics.cost_micros,
           metrics.ctr
    FROM search_term_view
    WHERE campaign.id = ${campaign.getId()}
      AND metrics.impressions >= ${config.MIN_IMPRESSIONS}
      AND metrics.clicks >= ${config.MIN_CLICKS}
      AND segments.date DURING LAST_${config.SEARCH_TERMS_LOOKBACK}_DAYS
  `;
  
  const searchTermsReport = AdsApp.report(searchTermsQuery);
  const rows = searchTermsReport.rows();
  
  const keywordsToAdd = [];
  const negativeKeywordsToAdd = [];
  
  while (rows.hasNext()) {
    const row = rows.next();
    const searchTerm = row['SearchTermView.search_term'];
    const impressions = parseInt(row['metrics.impressions']);
    const clicks = parseInt(row['metrics.clicks']);
    const conversions = parseInt(row['metrics.conversions']);
    const cost = parseInt(row['metrics.cost_micros']) / 1000000;
    const ctr = parseFloat(row['metrics.ctr']);
    
    // Check if search term is already a keyword
    if (isExistingKeyword(campaign, searchTerm)) {
      continue;
    }
    
    // Evaluate for keyword addition
    if (shouldAddAsKeyword(conversions, ctr, config)) {
      const suggestedBid = calculateSuggestedBid(cost, clicks, config);
      
      keywordsToAdd.push({
        text: searchTerm,
        matchType: 'EXACT',
        finalUrl: campaign.getUrls().getFinalUrl(),
        maxCpc: suggestedBid
      });
      
    } else if (shouldAddAsNegative(conversions, ctr, cost, config)) {
      negativeKeywordsToAdd.push({
        text: searchTerm,
        matchType: 'PHRASE'
      });
    }
  }
  
  // Add positive keywords
  if (keywordsToAdd.length > 0) {
    const keywordOperation = campaign.newKeywordBuilder()
      .withText(keywordsToAdd[0].text)
      .withCpc(keywordsToAdd[0].maxCpc)
      .build();
      
    Logger.log(`Added keyword "${keywordsToAdd[0].text}" to campaign ${campaign.getName()}`);
  }
  
  // Add negative keywords
  if (negativeKeywordsToAdd.length > 0) {
    campaign.createNegativeKeyword(negativeKeywordsToAdd[0].text);
    Logger.log(`Added negative keyword "${negativeKeywordsToAdd[0].text}" to campaign ${campaign.getName()}`);
  }
}

function shouldAddAsKeyword(conversions, ctr, config) {
  return conversions >= config.AUTO_ADD_THRESHOLD.conversions &&
         ctr >= config.AUTO_ADD_THRESHOLD.ctr;
}

function shouldAddAsNegative(conversions, ctr, cost, config) {
  return conversions === 0 && cost > 50 && ctr < 0.01;
}

4. Performance Monitoring and Alerting Script

function performanceMonitoringAndAlerting() {
  const CONFIG = {
    ALERT_THRESHOLDS: {
      roasDecline: 0.2,      // 20% decline
      costIncrease: 0.3,     // 30% increase
      conversionDrop: 0.25,  // 25% drop
      cpaIncrease: 0.4       // 40% increase
    },
    COMPARISON_PERIOD: 7,    // Compare last 7 days vs previous 7
    EMAIL_RECIPIENTS: ['marketing@company.com'],
    SLACK_WEBHOOK_URL: 'your_slack_webhook_url'
  };
  
  const performanceAlerts = checkPerformanceAlerts(CONFIG);
  
  if (performanceAlerts.length > 0) {
    sendAlertNotifications(performanceAlerts, CONFIG);
  }
}

function checkPerformanceAlerts(config) {
  const alerts = [];
  
  const campaigns = AdsApp.campaigns()
    .withCondition('Status = ENABLED')
    .get();
  
  while (campaigns.hasNext()) {
    const campaign = campaigns.next();
    
    const currentStats = campaign.getStatsFor(`LAST_${config.COMPARISON_PERIOD}_DAYS`);
    const previousStats = campaign.getStatsFor(
      `LAST_${config.COMPARISON_PERIOD * 2}_DAYS`,
      `LAST_${config.COMPARISON_PERIOD + 1}_DAYS`
    );
    
    // Check ROAS decline
    const currentRoas = currentStats.getConversionValue() / currentStats.getCost();
    const previousRoas = previousStats.getConversionValue() / previousStats.getCost();
    
    if (previousRoas > 0 && (currentRoas - previousRoas) / previousRoas < -config.ALERT_THRESHOLDS.roasDecline) {
      alerts.push({
        type: 'ROAS_DECLINE',
        campaign: campaign.getName(),
        current: currentRoas,
        previous: previousRoas,
        change: ((currentRoas - previousRoas) / previousRoas * 100).toFixed(1) + '%',
        severity: 'HIGH'
      });
    }
    
    // Check cost increase
    const costChange = (currentStats.getCost() - previousStats.getCost()) / previousStats.getCost();
    if (costChange > config.ALERT_THRESHOLDS.costIncrease) {
      alerts.push({
        type: 'COST_INCREASE',
        campaign: campaign.getName(),
        current: currentStats.getCost(),
        previous: previousStats.getCost(),
        change: (costChange * 100).toFixed(1) + '%',
        severity: 'MEDIUM'
      });
    }
    
    // Check conversion drop
    const conversionChange = (currentStats.getConversions() - previousStats.getConversions()) / previousStats.getConversions();
    if (conversionChange < -config.ALERT_THRESHOLDS.conversionDrop) {
      alerts.push({
        type: 'CONVERSION_DROP',
        campaign: campaign.getName(),
        current: currentStats.getConversions(),
        previous: previousStats.getConversions(),
        change: (conversionChange * 100).toFixed(1) + '%',
        severity: 'HIGH'
      });
    }
  }
  
  return alerts;
}

function sendAlertNotifications(alerts, config) {
  // Email alert
  const emailBody = generateEmailAlert(alerts);
  MailApp.sendEmail({
    to: config.EMAIL_RECIPIENTS.join(','),
    subject: `Google Ads Performance Alert - ${alerts.length} issues detected`,
    htmlBody: emailBody
  });
  
  // Slack notification
  if (config.SLACK_WEBHOOK_URL) {
    sendSlackNotification(alerts, config.SLACK_WEBHOOK_URL);
  }
  
  Logger.log(`Sent ${alerts.length} performance alerts`);
}

5. Competitor Analysis Script

function competitorAnalysisScript() {
  const CONFIG = {
    COMPETITOR_DOMAINS: [
      'competitor1.com',
      'competitor2.com',
      'competitor3.com'
    ],
    ANALYSIS_PERIOD_DAYS: 30,
    IMPRESSION_SHARE_THRESHOLD: 0.1
  };
  
  const competitorInsights = analyzeCompetitorMetrics(CONFIG);
  const opportunityReport = generateOpportunityReport(competitorInsights);
  
  logCompetitorInsights(opportunityReport);
}

function analyzeCompetitorMetrics(config) {
  const auctionInsightsQuery = `
    SELECT
      auction_insight_search_impression_share_report.domain,
      metrics.impression_share,
      metrics.overlap_rate,
      metrics.position_above_rate,
      metrics.position_below_rate,
      metrics.top_of_page_rate,
      metrics.absolute_top_impression_share
    FROM auction_insight_search_impression_share_report
    WHERE segments.date DURING LAST_${config.ANALYSIS_PERIOD_DAYS}_DAYS
      AND metrics.impression_share >= ${config.IMPRESSION_SHARE_THRESHOLD}
  `;
  
  const report = AdsApp.report(auctionInsightsQuery);
  const rows = report.rows();
  
  const competitorData = {};
  
  while (rows.hasNext()) {
    const row = rows.next();
    const domain = row['auction_insight_search_impression_share_report.domain'];
    
    if (config.COMPETITOR_DOMAINS.includes(domain)) {
      competitorData[domain] = {
        impressionShare: parseFloat(row['metrics.impression_share']),
        overlapRate: parseFloat(row['metrics.overlap_rate']),
        positionAboveRate: parseFloat(row['metrics.position_above_rate']),
        positionBelowRate: parseFloat(row['metrics.position_below_rate']),
        topOfPageRate: parseFloat(row['metrics.top_of_page_rate']),
        absoluteTopShare: parseFloat(row['metrics.absolute_top_impression_share'])
      };
    }
  }
  
  return competitorData;
}

function generateOpportunityReport(competitorData) {
  const opportunities = [];
  
  Object.keys(competitorData).forEach(domain => {
    const competitor = competitorData[domain];
    
    // High overlap, low position above rate = opportunity to outbid
    if (competitor.overlapRate > 0.3 && competitor.positionAboveRate < 0.5) {
      opportunities.push({
        type: 'BIDDING_OPPORTUNITY',
        competitor: domain,
        description: `High overlap (${(competitor.overlapRate * 100).toFixed(1)}%) but appearing below competitor ${(competitor.positionAboveRate * 100).toFixed(1)}% of the time`,
        recommendation: 'Consider increasing bids for overlapping keywords'
      });
    }
    
    // Low impression share competitor with high top-of-page rate
    if (competitor.impressionShare < 0.15 && competitor.topOfPageRate > 0.7) {
      opportunities.push({
        type: 'MARKET_EXPANSION',
        competitor: domain,
        description: `Competitor has low impression share but high top-of-page rate`,
        recommendation: 'Opportunity to capture additional market share'
      });
    }
  });
  
  return opportunities;
}

Advanced Optimization Scripts

Shopping Campaign Optimization

function shoppingCampaignOptimization() {
  const CONFIG = {
    MIN_SPEND_THRESHOLD: 100,
    TARGET_ROAS: 4.0,
    NEGATIVE_KEYWORD_COST_THRESHOLD: 50,
    PRODUCT_GROUP_SPLIT_THRESHOLD: 20
  };
  
  const shoppingCampaigns = AdsApp.shoppingCampaigns()
    .withCondition('Status = ENABLED')
    .get();
  
  while (shoppingCampaigns.hasNext()) {
    const campaign = shoppingCampaigns.next();
    optimizeShoppingCampaign(campaign, CONFIG);
  }
}

function optimizeShoppingCampaign(campaign, config) {
  const stats = campaign.getStatsFor('LAST_30_DAYS');
  
  if (stats.getCost() < config.MIN_SPEND_THRESHOLD) {
    Logger.log(`Campaign ${campaign.getName()} - Insufficient spend for optimization`);
    return;
  }
  
  // Analyze product group performance
  const productGroups = campaign.productGroups().get();
  const performanceData = [];
  
  while (productGroups.hasNext()) {
    const productGroup = productGroups.next();
    const pgStats = productGroup.getStatsFor('LAST_30_DAYS');
    
    if (pgStats.getCost() > 0) {
      performanceData.push({
        productGroup: productGroup,
        roas: pgStats.getConversionValue() / pgStats.getCost(),
        cost: pgStats.getCost(),
        conversions: pgStats.getConversions()
      });
    }
  }
  
  // Optimize high-performing product groups
  performanceData
    .filter(pg => pg.roas > config.TARGET_ROAS * 1.2)
    .forEach(pg => {
      // Increase bid for high performers
      const currentBid = pg.productGroup.bidding().getCpc();
      const newBid = currentBid * 1.1;
      pg.productGroup.bidding().setCpc(newBid);
    });
  
  // Reduce bids for poor performers
  performanceData
    .filter(pg => pg.roas < config.TARGET_ROAS * 0.8 && pg.cost > config.NEGATIVE_KEYWORD_COST_THRESHOLD)
    .forEach(pg => {
      const currentBid = pg.productGroup.bidding().getCpc();
      const newBid = currentBid * 0.9;
      pg.productGroup.bidding().setCpc(newBid);
    });
}

Quality Score Optimization

function qualityScoreOptimization() {
  const CONFIG = {
    LOW_QS_THRESHOLD: 6,
    HIGH_COST_THRESHOLD: 100,
    MIN_IMPRESSIONS: 1000
  };
  
  const campaigns = AdsApp.campaigns()
    .withCondition('Status = ENABLED')
    .get();
  
  const lowQualityKeywords = [];
  
  while (campaigns.hasNext()) {
    const campaign = campaigns.next();
    
    const keywords = campaign.keywords()
      .withCondition('Status = ENABLED')
      .withCondition(`Impressions >= ${CONFIG.MIN_IMPRESSIONS}`)
      .get();
    
    while (keywords.hasNext()) {
      const keyword = keywords.next();
      const qualityScore = keyword.getQualityScore();
      const stats = keyword.getStatsFor('LAST_30_DAYS');
      
      if (qualityScore <= CONFIG.LOW_QS_THRESHOLD && stats.getCost() >= CONFIG.HIGH_COST_THRESHOLD) {
        lowQualityKeywords.push({
          keyword: keyword,
          campaign: campaign.getName(),
          qualityScore: qualityScore,
          cost: stats.getCost(),
          ctr: stats.getCtr(),
          conversions: stats.getConversions()
        });
      }
    }
  }
  
  // Generate optimization recommendations
  generateQualityScoreReport(lowQualityKeywords);
}

function generateQualityScoreReport(lowQualityKeywords) {
  lowQualityKeywords.forEach(item => {
    const recommendations = [];
    
    if (item.ctr < 0.02) {
      recommendations.push('Improve ad copy relevance and call-to-action');
    }
    
    if (item.qualityScore <= 4) {
      recommendations.push('Consider pausing and recreating with exact match');
    }
    
    if (item.conversions === 0 && item.cost > 200) {
      recommendations.push('Consider adding as negative keyword');
    }
    
    Logger.log(`
      Keyword: ${item.keyword.getText()}
      Campaign: ${item.campaign}
      Quality Score: ${item.qualityScore}
      Cost: $${item.cost.toFixed(2)}
      CTR: ${(item.ctr * 100).toFixed(2)}%
      Recommendations: ${recommendations.join('; ')}
    `);
  });
}

Script Management and Deployment

Script Scheduling Best Practices

// Daily optimization script - run every morning at 6 AM
function dailyOptimizationRoutine() {
  Logger.log('Starting daily optimization routine...');
  
  try {
    automatedBidManagement();
    performanceMonitoringAndAlerting();
    
    Logger.log('Daily optimization completed successfully');
  } catch (error) {
    Logger.log('Error in daily optimization: ' + error.toString());
    // Send error notification
    sendErrorAlert(error);
  }
}

// Weekly deep analysis - run every Monday at 8 AM
function weeklyAnalysisRoutine() {
  Logger.log('Starting weekly analysis routine...');
  
  try {
    dynamicBudgetAllocation();
    competitorAnalysisScript();
    qualityScoreOptimization();
    
    Logger.log('Weekly analysis completed successfully');
  } catch (error) {
    Logger.log('Error in weekly analysis: ' + error.toString());
    sendErrorAlert(error);
  }
}

// Monthly comprehensive review - run first Monday of each month
function monthlyReviewRoutine() {
  Logger.log('Starting monthly review routine...');
  
  try {
    automatedKeywordHarvesting();
    shoppingCampaignOptimization();
    generateMonthlyPerformanceReport();
    
    Logger.log('Monthly review completed successfully');
  } catch (error) {
    Logger.log('Error in monthly review: ' + error.toString());
    sendErrorAlert(error);
  }
}

function sendErrorAlert(error) {
  MailApp.sendEmail({
    to: 'admin@company.com',
    subject: 'Google Ads Script Error Alert',
    body: `
      An error occurred in Google Ads Script execution:
      
      Error: ${error.toString()}
      Time: ${new Date().toString()}
      
      Please investigate and resolve the issue.
    `
  });
}

Implementation Strategy

Phase 1: Foundation Scripts (Week 1-2)

  • Deploy basic performance monitoring
  • Implement automated bid management
  • Set up error handling and alerting

Phase 2: Optimization Scripts (Week 3-4)

  • Add keyword harvesting automation
  • Implement dynamic budget allocation
  • Deploy quality score optimization

Phase 3: Advanced Analytics (Week 5-6)

  • Add competitor analysis capabilities
  • Implement shopping campaign optimization
  • Create comprehensive reporting dashboards

Phase 4: Custom Solutions (Week 7-8)

  • Develop brand-specific optimization logic
  • Implement advanced attribution tracking
  • Create predictive optimization models

Conclusion

Google Ads Scripts provide powerful automation capabilities that can transform DTC advertising performance and efficiency. By implementing these advanced scripts, brands can achieve superior results while reducing manual management overhead.

Success requires starting with foundational monitoring and optimization scripts, then gradually implementing more sophisticated automation based on specific business needs and performance goals.


Ready to implement advanced Google Ads automation for your DTC brand? ATTN Agency specializes in custom Google Ads Scripts and automation strategies. Contact us to discuss your automation opportunities.

Related Articles

Additional Resources


Ready to Grow Your Brand?

ATTN Agency helps DTC and e-commerce brands scale profitably through paid media, email, SMS, and more. Whether you're looking to optimize your current strategy or launch something new, we'd love to chat.

Book a Free Strategy Call or Get in Touch to learn how we can help your brand grow.