Account Audit Helper v1.8

Save time on your Google Ads account audits with automated data collection across key account areas.

Hey, before I leave you to your script, consider hiring us to save 100s of hours on manual work via custom automations.

You can book us for a one-off project, or on a monthly basis. We offer:

  • Up to Unlimited requests

  • Unlimited revisions

  • Free technical project analysis 

  • Tailored recommendations

  • High priority support

  • Full access to the source code

  • Whitelabeling

  • 30 days money back guarantee

This is perfect for you if your business relies heavily on advertising for revenue, but lacks technical capabilities to automate tasks to increase productivity. Ideal for mid to large Ecommerce, startups that raised funding, agencies, or corporates with small marketing teams.

Platforms we automate: Google Ads, Meta Ads, Linkedin Ads, Native, & more.

The tools we use: Custom scripts & APIs, Zapier, Make, n8n, Supermetrics, Funnel, & more.

If you’re you’re looking to take your ads game to the next level 🚀  fill in the form, you'll get the link to schedule your FREE strategy session on the Thank You page.

Account Audit Helper v1.8 - Why Use This Script

Manual account auditing requires navigating through multiple Google Ads interfaces to gather basic information, often taking hours of repetitive work. This script automates the data collection process, helping you:

  • Save 1+ hours of manual checks on account structure, campaign settings, and more

  • Focus more time on strategic analysis

  • Get organized data about campaign settings, extensions, audiences

  • Track recent automated changes

  • Monitor Performance Max configurations

What the Script Does

The script collects account data and organizes it into seven key reports:

  • Campaign Analysis: Basic campaign settings, targeting configurations, and extension coverage

  • Ad Group Analysis: Ad counts and keyword quality score metrics

  • Audience Analysis: List sizes, eligibility status, and types

  • Conversion Tracking: Action configurations and categories

  • Auto-Applied Recommendations: 30-day change history tracking

  • Extension Analysis: Coverage across account, campaign, and ad group levels

  • Performance Max Analysis: Basic campaign settings and configurations

How It Works

The script connects to your Google Ads account and collects available data.

It organizes the information into seven different sheets within a specified Google Spreadsheet.

A smart sheet gets populated with all the Audit Checks and insights gathered by the script.

Each report can be individually enabled or disabled through a control panel. Particularly handy for large accounts.

How to Use It

  1. Create a copy of this Google Sheet template

  2. In your Google Ads account, create a new script and paste the code below

  3. Replace the SPREADSHEET_URL variable in the code with your copy of the template URL

  4. Configure the Controls sheet to enable/disable specific reports:
    - Set each report to "ON" or "OFF" in column C
    - Use "Include Paused" setting to control campaign, ad groups & assets status filtering

  5. Run the script or schedule it to run automatically at your preferred intervals

Reviewing the Results

After running the script, review the Audit Checks sheet and/or dive in to the indivitual report sheets. You'll have organized data to help you:

  • Review campaign settings and targeting configurations

  • Check extension implementation across different levels

  • Verify audience sizes and eligibility

  • Monitor conversion tracking setup

  • Track automated changes

  • Analyze Performance Max campaign settings

Remember that while this script provides comprehensive data, always combine these insights with your expertise and business goals when making optimization decisions.

Credits

Special thanks to Bob & Miles from PPC Mastery, whose Google Ads Audit Template inspired several of the checks in this script. You can find their resource right here. 🙏

Customize this script

Want an MCC version? Maybe customize the reports, or specific filters? We got you covered!

Fill in the form, you'll get the link to schedule your FREE strategy session on the Thank You page.

The code

/**
 * Account Audit Helper v1.8 (Single Account)
 * 
 * Created by Francesco Cifardi
 * For issues or questions reach out on Linkedin (https://www.linkedin.com/in/francescocifardi/)
 * Free to use and share, a mention would be appreciated :-)
 * 
 * This script helps streamline Google Ads account audits by automatically collecting
 * and organizing key account data. It saves 1+ hours of manual checks by gathering
 * information across seven areas:
 * 
 * - Campaign Analysis (settings, targeting, extensions)
 * - Ad Group Analysis (ads, keywords, quality scores)
 * - Audience Analysis (list sizes and eligibility)
 * - Conversion Tracking (action configurations)
 * - Auto-Applied Recommendations (30-day history)
 * - Extension Analysis (account/campaign/ad group coverage)
 * - Performance Max Analysis (campaign settings)
 * 
 * The data is organized into separate sheets in a Google Spreadsheet, with an
 * additional Audit Checks sheet that provides automated insights. Each report
 * can be individually enabled/disabled through a control panel.
 * 
 * Setup:
 * 1. Create a copy of the template spreadsheet: https://docs.google.com/spreadsheets/d/1jJme71tj5mrXPX90ZZd45FWhIqBgh4QRVQPqJ11ueVY/copy
 * 2. Replace SPREADSHEET_URL with your copy's URL
 * 3. Configure which reports to run in the Controls sheet
 * 4. Run the script or schedule it for regular updates
 * 
 * Note: This script is designed to assist with audits by automating data collection.
 * Always combine these insights with your expertise when making optimization decisions.
 */

// Spreadsheet URL - replace this with your spreadsheet URL
var SPREADSHEET_URL = "YOUR_SHEET_URL"; // Template spreadsheet: https://docs.google.com/spreadsheets/d/1jJme71tj5mrXPX90ZZd45FWhIqBgh4QRVQPqJ11ueVY/copy

// Don't change any code below this line
function main() {
    Logger.log("Script started");
    
    var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
    
    // Get control settings
    var controls = getControlSettings(spreadsheet);
    
    // Process each report based on control settings
    if (controls['Search & Display'] === 'ON') {
        processSearchAndDisplayData(spreadsheet);
    }
    
    if (controls['Ad groups'] === 'ON') {
        processAdGroupData(spreadsheet);
    }
    
    if (controls['Audiences'] === 'ON') {
        processAudienceData(spreadsheet);
    }
    
    if (controls['Conversions'] === 'ON') {
        processConversionData(spreadsheet);
    }
    
    if (controls['Auto-applied'] === 'ON') {
        processAutoAppliedRecommendations(spreadsheet);
    }
    
    if (controls['Extensions'] === 'ON') {
        processExtensionData(spreadsheet);
    }
    
    if (controls['Performance Max'] === 'ON') {
        processPerformanceMaxData(spreadsheet);
    }
    
    Logger.log("Script completed");
}

function getControlSettings(spreadsheet) {
    // Get control settings from the Controls sheet to determine which reports to run
    var controlSheet = getOrCreateSheet(spreadsheet, "Controls");
    
    // Map specific cell locations to control settings
    var controls = {
        'Conversions': controlSheet.getRange("C3").getValue(),
        'Audiences': controlSheet.getRange("C4").getValue(),
        'Performance Max': controlSheet.getRange("C5").getValue(),
        'Search & Display': controlSheet.getRange("C6").getValue(),
        'Ad groups': controlSheet.getRange("C7").getValue(),
        'Extensions': controlSheet.getRange("C8").getValue(),
        'Auto-applied': controlSheet.getRange("C9").getValue(),
        'Include Paused': controlSheet.getRange("C10").getValue()
    };
    
    Logger.log("Control settings loaded: " + JSON.stringify(controls));
    return controls;
}

function getCampaignSelector(spreadsheet) {
    // Create a campaign selector with date range and status filters based on control settings
    var controls = getControlSettings(spreadsheet);
    var selector = AdsApp.campaigns()
        .forDateRange("LAST_30_DAYS");
    
    // Apply campaign status filter based on Include Paused setting
    if (controls['Include Paused'] === 'ON') {
        selector = selector.withCondition("Status IN ['ENABLED', 'PAUSED']");
    } else {
        selector = selector.withCondition("Status = ENABLED");
    }
    
    return selector;
}

function processSearchAndDisplayData(spreadsheet) {
    Logger.log("Starting campaign analysis");
    
    var sheet = getOrCreateSheet(spreadsheet, "Search & Display");
    sheet.getDataRange().clearContent();
    
    // Updated headers - added "Number of" to extension headers (A1:T1)
    sheet.getRange("A1:T1").setValues([[
        "Campaign Name",
        "Campaign Type", 
        "Number of Excluded Placement Lists",
        "Number of Negative Keywords Lists",
        "Number of Targeted Audiences",
        "Number of Excluded Audiences",
        "Number of Targeted Locations",
        "Number of Excluded Locations",
        "Location Targeting",
        "Bidding Strategy",
        "Target",
        "Has Ad Schedule",
        // Extension columns with "Number of" prefix
        "Number of Sitelinks",
        "Number of Callouts",
        "Number of Snippets",
        "Number of Call Extensions",
        "Number of Price Extensions",
        "Number of Location Extensions",
        "Number of Promotion Extensions",
        "Number of Image Extensions"
    ]]);
    
    // Pass spreadsheet to getCampaignSelector
    var campaigns = getCampaignSelector(spreadsheet).get();
    
    var row = 2;
    var campaignCount = 0;
    
    while (campaigns.hasNext()) {
        var campaign = campaigns.next();
        campaignCount++;
        
        var locationSettings = campaign.targeting().getTargetingSetting('USER_INTEREST_AND_LIST');
        var targetingMode = (locationSettings === 'TARGET_ALL_TRUE') ? 'Interest' :
            (locationSettings === 'TARGET_ALL_FALSE') ? 'Presence' :
            locationSettings;
        
        // Count excluded placement lists
        var excludedListsCount = 0;
        var placementLists = campaign.excludedPlacementLists().get();
        while (placementLists.hasNext()) {
            placementLists.next();
            excludedListsCount++;
        }
        
        // Count negative keyword lists
        var negKeywordListsCount = 0;
        var keywordLists = campaign.negativeKeywordLists().get();
        while (keywordLists.hasNext()) {
            keywordLists.next();
            negKeywordListsCount++;
        }
        
        // Count targeted audiences
        var targetedAudiencesCount = 0;
        var targetedAudiences = campaign.targeting().audiences().get();
        while (targetedAudiences.hasNext()) {
            targetedAudiences.next();
            targetedAudiencesCount++;
        }
        
        // Count excluded audiences
        var excludedAudiencesCount = 0;
        var excludedAudiences = campaign.targeting().excludedAudiences().get();
        while (excludedAudiences.hasNext()) {
            excludedAudiences.next();
            excludedAudiencesCount++;
        }
        
        // Get location targeting information
        var targetedLocations = campaign.targeting().targetedLocations().get();
        var excludedLocations = campaign.targeting().excludedLocations().get();
        
        // Get bidding strategy and target information
        var biddingStrategy = campaign.getBiddingStrategyType();
        var target = "None";
        
        // Determine target based on bidding strategy
        switch(biddingStrategy) {
            case "MANUAL_CPC":
                target = "Max CPC";
                break;
            case "TARGET_ROAS":
                try {
                    target = campaign.getBiddingStrategyConfiguration().getTargetRoas() * 100 + "% ROAS";
                } catch(e) {
                    target = "tROAS";
                }
                break;
            case "TARGET_CPA":
                try {
                    target = campaign.getBiddingStrategyConfiguration().getTargetCpa().getMicroAmount() / 1000000 + " CPA";
                } catch(e) {
                    target = "tCPA";
                }
                break;
        }
        
        // Check for ad schedule using the correct method
        var hasAdSchedule = false;
        var adSchedules = campaign.targeting().adSchedules().get();
        if (adSchedules && adSchedules.length > 0) {
            hasAdSchedule = true;
        }
        
        // Get extension counts
        var extensionCounts = getExtensionCounts(campaign, spreadsheet);
        
        sheet.getRange(row, 1, 1, 20).setValues([[
            campaign.getName(),
            campaign.getAdvertisingChannelType(),
            excludedListsCount,
            negKeywordListsCount,
            targetedAudiencesCount,
            excludedAudiencesCount,
            targetedLocations.totalNumEntities(),
            excludedLocations.totalNumEntities(),
            targetingMode,
            biddingStrategy,
            target,
            hasAdSchedule,
            // Extension counts
            extensionCounts.sitelinks,
            extensionCounts.callouts,
            extensionCounts.snippets,
            extensionCounts.call,
            extensionCounts.price,
            extensionCounts.location,
            extensionCounts.promotion,
            extensionCounts.image
        ]]);
        row++;
    }

    Logger.log("Completed campaign analysis for " + campaignCount + " campaigns");
}

function getExtensionCounts(campaign, spreadsheet) {
    var controls = getControlSettings(spreadsheet);
    var counts = {
        sitelinks: 'N/A',
        callouts: 'N/A',
        snippets: 'N/A',
        call: 'N/A',
        price: 'N/A',
        location: 'N/A',
        promotion: 'N/A',
        image: 'N/A'
    };
    
    try {
        // Add status condition to query
        const statusCondition = controls['Include Paused'] === 'ON' 
            ? "campaign_asset.status IN ('ENABLED', 'PAUSED')"
            : "campaign_asset.status = 'ENABLED'";
            
        const query = 
            'SELECT campaign.id, asset.type ' +
            'FROM campaign_asset ' +
            'WHERE campaign.id = "' + campaign.getId() + '" ' +
            'AND ' + statusCondition;
        
        const results = AdsApp.search(query);
        
        // Reset counts to 0 before counting
        Object.keys(counts).forEach(key => counts[key] = 0);
        
        // Count each type of extension
        while (results.hasNext()) {
            const row = results.next();
            switch (row.asset.type) {
                case 'SITELINK':
                    counts.sitelinks++;
                    break;
                case 'CALLOUT':
                    counts.callouts++;
                    break;
                case 'STRUCTURED_SNIPPET':
                    counts.snippets++;
                    break;
                case 'CALL':
                    counts.call++;
                    break;
                case 'PRICE':
                    counts.price++;
                    break;
                case 'LOCATION':
                    counts.location++;
                    break;
                case 'PROMOTION':
                    counts.promotion++;
                    break;
                case 'IMAGE':
                    counts.image++;
                    break;
            }
        }
    } catch (e) {
        Logger.log("Error getting extension counts for campaign: " + campaign.getName() + " - " + e);
    }
    
    return counts;
}

function processAdGroupData(spreadsheet) {
    Logger.log("Starting ad group analysis");
    
    var sheet = getOrCreateSheet(spreadsheet, "Ad groups");
    sheet.getDataRange().clearContent();
    
    // Set headers 
    sheet.getRange("A1:H1").setValues([["Campaign Name", "Campaign Type", "Ad Group Name", 
                                       "Number of Ads", "Number of Keywords", 
                                       "Average Quality Score", "Keywords with QS", 
                                       "Keywords without QS"]]);
    
    var controls = getControlSettings(spreadsheet);
    var campaigns = getCampaignSelector(spreadsheet).get();
    var row = 2;
    var totalAdGroups = 0;
    
    while (campaigns.hasNext()) {
        var campaign = campaigns.next();
        
        // Apply status filter to ad groups
        var adGroupSelector = campaign.adGroups();
        if (controls['Include Paused'] === 'ON') {
            adGroupSelector = adGroupSelector.withCondition("Status IN ['ENABLED', 'PAUSED']");
        } else {
            adGroupSelector = adGroupSelector.withCondition("Status = ENABLED");
        }
        
        var adGroups = adGroupSelector.get();
        while (adGroups.hasNext()) {
            var adGroup = adGroups.next();
            totalAdGroups++;
            
            var stats = getAdGroupStats(adGroup, controls);
            
            sheet.getRange(row, 1, 1, 8).setValues([[
                campaign.getName(),
                campaign.getAdvertisingChannelType(),
                adGroup.getName(),
                stats.adCount,
                stats.keywordCount,
                stats.averageQualityScore || "No QS data",
                stats.keywordsWithQS,
                stats.keywordsWithoutQS
            ]]);
            
            row++;
        }
    }
    
    Logger.log("Completed ad group analysis for " + totalAdGroups + " ad groups");
}

function getAdGroupStats(adGroup, controls) {
    // Count ads in the ad group with status filter
    var adCount = 0;
    var adSelector = adGroup.ads();
    if (controls['Include Paused'] === 'ON') {
        adSelector = adSelector.withCondition("Status IN ['ENABLED', 'PAUSED']");
    } else {
        adSelector = adSelector.withCondition("Status = ENABLED");
    }
    var ads = adSelector.get();
    while (ads.hasNext()) {
        ads.next();
        adCount++;
    }
    
    // Analyze keywords and quality scores with status filter
    var keywordCount = 0;
    var qualityScoreSum = 0;
    var keywordsWithQS = 0;
    var keywordsWithoutQS = 0;
    
    var keywordSelector = adGroup.keywords();
    if (controls['Include Paused'] === 'ON') {
        keywordSelector = keywordSelector.withCondition("Status IN ['ENABLED', 'PAUSED']");
    } else {
        keywordSelector = keywordSelector.withCondition("Status = ENABLED");
    }
    var keywords = keywordSelector.get();
    
    while (keywords.hasNext()) {
        var keyword = keywords.next();
        keywordCount++;
        
        var qualityScore = keyword.getQualityScore();
        if (qualityScore !== null) {
            qualityScoreSum += qualityScore;
            keywordsWithQS++;
        } else {
            keywordsWithoutQS++;
        }
    }
    
    var averageQualityScore = keywordsWithQS > 0 ? (qualityScoreSum / keywordsWithQS).toFixed(2) : null;
    
    return { 
        adCount: adCount, 
        keywordCount: keywordCount,
        averageQualityScore: averageQualityScore,
        keywordsWithQS: keywordsWithQS,
        keywordsWithoutQS: keywordsWithoutQS
    };
}

function processAudienceData(spreadsheet) {
    Logger.log("Starting audience analysis");
    
    var sheet = getOrCreateSheet(spreadsheet, "Audiences");
    sheet.getDataRange().clearContent();
    
    // Set headers for audience data
    sheet.getRange("A1:G1").setValues([[
        "Name",
        "Description",
        "Type",
        "Size for Display",
        "Size for Search",
        "Eligible for Display",
        "Eligible for Search"
    ]]);
    
    var userlists = AdsApp.userlists().get();
    var row = 2;
    var listCount = 0;
    
    while (userlists.hasNext()) {
        try {
            var userList = userlists.next();
            listCount++;
            
            // Safely get values with error handling
            var sizeForDisplay = 0;
            var sizeForSearch = 0;
            var eligibleForDisplay = false;
            var eligibleForSearch = false;
            
            try { sizeForDisplay = userList.getSizeForDisplay() || 0; } catch(e) {}
            try { sizeForSearch = userList.getSizeForSearch() || 0; } catch(e) {}
            try { eligibleForDisplay = userList.isEligibleForDisplay() || false; } catch(e) {}
            try { eligibleForSearch = userList.isEligibleForSearch() || false; } catch(e) {}
            
            sheet.getRange(row, 1, 1, 7).setValues([[
                userList.getName() || "Unknown",
                userList.getDescription() || "No description",
                userList.getType() || "Unknown",
                sizeForDisplay,
                sizeForSearch,
                eligibleForDisplay,
                eligibleForSearch
            ]]);
            row++;
        } catch(e) {
            Logger.log("Error processing audience: " + e.toString());
            continue;
        }
    }
    
    Logger.log("Completed audience analysis for " + listCount + " audiences");
}

function processConversionData(spreadsheet) {
    Logger.log("Starting conversion actions analysis");
    
    var sheet = getOrCreateSheet(spreadsheet, "Conversions");
    sheet.getDataRange().clearContent();
    
    // Set headers for conversion data with clear categorization
    sheet.getRange("A1:E1").setValues([[
        "Conversion Name",
        "Conversion Type",
        "Conversion Category", // Primary or Secondary
        "Action Category",
        "Counting Type"
    ]]);
    
    // Query to get conversion action data with status and configuration
    const query = `
        SELECT 
            conversion_action.name, 
            conversion_action.type, 
            conversion_action.include_in_conversions_metric,
            conversion_action.category,
            conversion_action.counting_type
        FROM conversion_action`;
    
    const report = AdsApp.report(query);
    const rows = report.rows();
    var rowNum = 2;
    var conversionCount = 0;
    
    while (rows.hasNext()) {
        const row = rows.next();
        conversionCount++;
        
        // Convert include_in_conversions_metric to Primary/Secondary
        const category = row['conversion_action.include_in_conversions_metric'] ? 'Primary' : 'Secondary';
        
        sheet.getRange(rowNum, 1, 1, 5).setValues([[
            row['conversion_action.name'],
            row['conversion_action.type'],
            category,
            row['conversion_action.category'],
            row['conversion_action.counting_type']
        ]]);
        rowNum++;
    }
    
    Logger.log("Completed conversion analysis for " + conversionCount + " conversion actions");
}

function processAutoAppliedRecommendations(spreadsheet) {
    Logger.log("Starting auto-applied recommendations analysis");
    
    var sheet = getOrCreateSheet(spreadsheet, "Auto-applied");
    sheet.getDataRange().clearContent();
    
    // Set headers for tracking automated changes
    sheet.getRange("A1:M1").setValues([[
        "Campaign",
        "Ad Group",
        "Change Date",
        "Change Resource Type",
        "Changed Fields",
        "Client Type",
        "Feed",
        "Feed Item",
        "New Resource",
        "Old Resource",
        "Operation",
        "Resource Name",
        "Changed By"
    ]]);
    
    // Calculate date range for last 29 days (API limitation)
    const today = new Date();
    const daysAgo = new Date(today);
    daysAgo.setDate(today.getDate() - 29);
    
    const formattedStart = daysAgo.toISOString().split('T')[0];
    const formattedEnd = today.toISOString().split('T')[0];
    
    const query = `
        SELECT 
            campaign.name,
            ad_group.name,
            change_event.change_date_time,
            change_event.change_resource_type,
            change_event.changed_fields,
            change_event.client_type,
            change_event.feed,
            change_event.feed_item,
            change_event.new_resource,
            change_event.old_resource,
            change_event.resource_change_operation,
            change_event.resource_name,
            change_event.user_email
        FROM change_event
        WHERE change_event.change_date_time >= '${formattedStart}'
            AND change_event.change_date_time <= '${formattedEnd}'
            AND change_event.client_type = 'GOOGLE_ADS_RECOMMENDATIONS_SUBSCRIPTION'
        ORDER BY change_event.change_date_time DESC
        LIMIT 9999`;
    
    const report = AdsApp.report(query);
    const rows = report.rows();
    var rowNum = 2;
    var changeCount = 0;
    
    while (rows.hasNext()) {
        const row = rows.next();
        changeCount++;
        
        sheet.getRange(rowNum, 1, 1, 13).setValues([[
            row['campaign.name'] || 'N/A',
            row['ad_group.name'] || 'N/A',
            row['change_event.change_date_time'],
            row['change_event.change_resource_type'] || '',
            row['change_event.changed_fields'] || '',
            row['change_event.client_type'],
            row['change_event.feed'] || '',
            row['change_event.feed_item'] || '',
            row['change_event.new_resource'] || '',
            row['change_event.old_resource'] || '',
            row['change_event.resource_change_operation'] || '',
            row['change_event.resource_name'] || '',
            row['change_event.user_email'] || 'Auto-applied'
        ]]);
        rowNum++;
    }
    
    if (changeCount > 0) {
        Logger.log("Found " + changeCount + " changes in the last 29 days");
    } else {
        Logger.log("No changes detected in the last 29 days");
    }
}

function getAccountExtensionCounts() {
    Logger.log("Starting extension analysis");
    
    // Initialize counters for all extension types across levels
    var allExtensionCounts = {
        'Account': {
            'Callouts': 0,
            'Mobile Apps': 0,
            'Phone Numbers': 0,
            'Prices': 0,
            'Sitelinks': 0,
            'Snippets': 0,
            'Call Extensions': 0,
            'Location Extensions': 0,
            'Promotion Extensions': 0,
            'Image Extensions': 0
        },
        'Campaign': {
            'Callouts': 0,
            'Mobile Apps': 0,
            'Phone Numbers': 0,
            'Prices': 0,
            'Sitelinks': 0,
            'Snippets': 0,
            'Call Extensions': 0,
            'Location Extensions': 0,
            'Promotion Extensions': 0,
            'Image Extensions': 0
        },
        'Ad Group': {
            'Callouts': 0,
            'Mobile Apps': 0,
            'Phone Numbers': 0,
            'Prices': 0,
            'Sitelinks': 0,
            'Snippets': 0,
            'Call Extensions': 0,
            'Location Extensions': 0,
            'Promotion Extensions': 0,
            'Image Extensions': 0
        }
    };

    try {
        // Query account-level extensions
        const accountQuery = `
            SELECT 
                asset.type
            FROM asset
            WHERE asset.type IN (
                'CALLOUT',
                'MOBILE_APP',
                'CALL',
                'PRICE',
                'SITELINK',
                'STRUCTURED_SNIPPET',
                'LOCATION',
                'PROMOTION',
                'IMAGE'
            )
        `;

        var accountResults = AdsApp.search(accountQuery);
        while (accountResults.hasNext()) {
            var row = accountResults.next();
            var assetType = row.asset.type;
            updateExtensionCounts(allExtensionCounts['Account'], assetType);
        }
        
        // Campaign-level query
        const campaignQuery = `
            SELECT 
                campaign.name,
                asset.type
            FROM campaign_asset
            WHERE asset.type IN (
                'CALLOUT',
                'MOBILE_APP',
                'CALL',
                'PRICE',
                'SITELINK',
                'STRUCTURED_SNIPPET',
                'LOCATION',
                'PROMOTION',
                'IMAGE'
            )
        `;

        var campaignResults = AdsApp.search(campaignQuery);
        while (campaignResults.hasNext()) {
            var row = campaignResults.next();
            var assetType = row.asset.type;
            updateExtensionCounts(allExtensionCounts['Campaign'], assetType);
        }
        
        // Ad Group-level query
        const adGroupQuery = `
            SELECT 
                ad_group.name,
                asset.type
            FROM ad_group_asset
            WHERE asset.type IN (
                'CALLOUT',
                'MOBILE_APP',
                'CALL',
                'PRICE',
                'SITELINK',
                'STRUCTURED_SNIPPET',
                'LOCATION',
                'PROMOTION',
                'IMAGE'
            )
        `;

        var adGroupResults = AdsApp.search(adGroupQuery);
        while (adGroupResults.hasNext()) {
            var row = adGroupResults.next();
            var assetType = row.asset.type;
            updateExtensionCounts(allExtensionCounts['Ad Group'], assetType);
        }
        
        Logger.log("Extension analysis completed successfully");
    } catch (e) {
        Logger.log("Error retrieving extension counts: " + e);
        return {};
    }

    return allExtensionCounts;
}

function updateExtensionCounts(counts, assetType) {
    // Update extension counts based on asset type
    switch (assetType) {
        case 'CALLOUT':
            counts['Callouts']++;
            break;
        case 'MOBILE_APP':
            counts['Mobile Apps']++;
            break;
        case 'CALL':
            counts['Call Extensions']++;
            break;
        case 'PRICE':
            counts['Prices']++;
            break;
        case 'SITELINK':
            counts['Sitelinks']++;
            break;
        case 'STRUCTURED_SNIPPET':
            counts['Snippets']++;
            break;
        case 'LOCATION':
            counts['Location Extensions']++;
            break;
        case 'PROMOTION':
            counts['Promotion Extensions']++;
            break;
        case 'IMAGE':
            counts['Image Extensions']++;
            break;
    }
}

function processExtensionData(spreadsheet) {
    Logger.log("Starting extension data processing");
    
    var sheet = getOrCreateSheet(spreadsheet, "Extensions");
    sheet.getDataRange().clearContent();
    
    // Set headers for extension data
    sheet.getRange("A1:C1").setValues([["Extension Type", "Count", "Level"]]);
    
    var extensionCounts = getAccountExtensionCounts();
    var row = 2;
    
    // Process all levels of extensions
    ['Account', 'Campaign', 'Ad Group'].forEach(level => {
        for (var type in extensionCounts[level]) {
            if (extensionCounts[level][type] > 0) {
                sheet.getRange(row, 1, 1, 3).setValues([[
                    type, 
                    extensionCounts[level][type],
                    level
                ]]);
                row++;
            }
        }
    });
    
    Logger.log("Extension data processing completed");
}

function processPerformanceMaxData(spreadsheet) {
    Logger.log("Starting Performance Max campaign analysis");
    
    var sheet = getOrCreateSheet(spreadsheet, "Performance Max");
    sheet.getDataRange().clearContent();
    
    // Set headers for Performance Max data
    sheet.getRange("A1:G1").setValues([[
        "Campaign Name",
        "Bidding Strategy",
        "Asset Group Count",
        "Shopping Campaign",
        "Merchant ID",
        "Number of Targeted Locations",
        "Number of Excluded Locations"
    ]]);
    
    // Get control settings
    var controls = getControlSettings(spreadsheet);
    
    // Modify the status condition based on Include Paused setting
    var statusCondition = controls['Include Paused'] === 'ON' 
        ? "campaign.status IN ('ENABLED', 'PAUSED')"
        : "campaign.status = 'ENABLED'";
    
    // Updated GAQL query with status condition
    const query = `
        SELECT 
            campaign.name,
            campaign.bidding_strategy_type,
            campaign.id,
            campaign.shopping_setting.merchant_id
        FROM campaign
        WHERE campaign.advertising_channel_type = "PERFORMANCE_MAX"
        AND ${statusCondition}
        ORDER BY campaign.name ASC`;
    
    try {
        const results = AdsApp.search(query);
        var row = 2;
        var campaignCount = 0;
        
        while (results.hasNext()) {
            const result = results.next();
            campaignCount++;
            
            // Get asset group count
            const assetGroupQuery = `
                SELECT asset_group.id 
                FROM asset_group 
                WHERE campaign.id = ${result.campaign.id}`;
            const assetGroupResults = AdsApp.search(assetGroupQuery);
            let assetGroupCount = 0;
            while (assetGroupResults.hasNext()) {
                assetGroupResults.next();
                assetGroupCount++;
            }
            
            // Get merchant ID info
            const merchantId = result.campaign.shoppingSetting?.merchantId || "N/A";
            const isShoppingCampaign = merchantId !== "N/A" ? "Yes" : "No";
            
            // Get location targeting counts using GAQL
            const locationQuery = `
                SELECT 
                    campaign_criterion.location.geo_target_constant 
                FROM campaign_criterion 
                WHERE campaign.id = ${result.campaign.id} 
                AND campaign_criterion.type = "LOCATION"`;
            
            const excludedLocationQuery = `
                SELECT 
                    campaign_criterion.location.geo_target_constant 
                FROM campaign_criterion 
                WHERE campaign.id = ${result.campaign.id} 
                AND campaign_criterion.type = "LOCATION"
                AND campaign_criterion.negative = TRUE`;
            
            let targetedLocations = 0;
            let excludedLocations = 0;
            
            // Count targeted locations
            const targetedResults = AdsApp.search(locationQuery);
            while (targetedResults.hasNext()) {
                targetedResults.next();
                targetedLocations++;
            }
            
            // Count excluded locations
            const excludedResults = AdsApp.search(excludedLocationQuery);
            while (excludedResults.hasNext()) {
                excludedResults.next();
                excludedLocations++;
            }
            
            sheet.getRange(row, 1, 1, 7).setValues([[
                result.campaign.name,
                result.campaign.biddingStrategyType,
                assetGroupCount,
                isShoppingCampaign,
                merchantId,
                targetedLocations,
                excludedLocations
            ]]);
            row++;
        }
        
        Logger.log(`Completed Performance Max analysis for ${campaignCount} campaigns`);
    } catch (e) {
        Logger.log("Error processing Performance Max campaigns: " + e);
        Logger.log(e.stack);
    }
}

function getOrCreateSheet(spreadsheet, sheetName) {
    // Helper function to get or create a sheet if it doesn't exist
    var sheet = spreadsheet.getSheetByName(sheetName);
    if (!sheet) {
        sheet = spreadsheet.insertSheet(sheetName);
    }
    return sheet;
}

Have questions or feedback? Hit reply. Loved it? Share it! 🙂

And if this was forwarded to you, but you’re not subscribed yet…