Google Display Exclusion Script [Updated 2024]

Google Ads Display Exclusion Script Header Image

As a full-time digital marketing consultant, I sometimes run Google Display campaigns for my clients for brand awareness or retargeting.

The annoying thing about Google’s Display Network(GDN) is that you’re almost guaranteed to show ads on really sketchy websites. Many of those websites have common top-level domains (TLDs) like .xyz or .buzz.

Google does not natively let you exclude by TLD, but you can do that with a script which I created.

I found an old version of this script from 2017 that no longer worked and ran it through a modern LLM (Claude) to help beef it up.

Here’s how it works

Exclude TLDs from Google Display Campaigns

The script looks for a list of TLDs, or even just words/phrases, and checks them against your placement list.

When it finds a placement with that specific word or TLD in it, it excludes that placement, after first checking to make sure it’s not already excluded.

That’s it! You can run this every day to better optimize your Display campaigns.

Here’s a list of the TLDs I exclude based on my experience.

  • .one
  • .buzz
  • .xyz
  • .ee
  • .cloud
  • .info

Here’s the script, just load it up in the Scripts section of your Google Ads Account.

/**
*
* GDN Domain Exclusion Script by Nick Lafferty
* The scripts plots reviews the domain placements of your Display campaigns and excludes domains with words in the stringsToExclude field.
* It automatically excludes those placements after first checking that they're not alraedy excluded.
* Version: 1.0
*
**/

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

function main() {
  var currentSetting = {
  // The list of text strings to check in placement domains. If the placement domain contains ANY of these, 
  // we WILL attempt to exclude it from the campaign
    stringsToExclude: [".one", ".buzz", ".xyz", ".ee", ".cloud", ".info"],
    dateRange: "LAST_30_DAYS",
    reportType: "AUTOMATIC_PLACEMENTS_PERFORMANCE_REPORT",
    metricsColumns: [
      'DisplayName', 'Domain', 'CampaignName', 'CampaignId',
      'AdGroupName', 'AdGroupId', 'Impressions', 'Clicks', 'Cost', 'CostPerConversion'
    ],
    whereConditions: ['WHERE', 'Impressions', '>=', '1'],
    matchedCampaigns: {},
    batchSize: 10000,  // Process this many rows before yielding
    yieldTime: 1000    // Yield for this many milliseconds
  };

  var DEBUG = 0;

  try {
    processReport(currentSetting);
    excludePlacements(currentSetting);
  } catch (e) {
    Logger.log("An error occurred: " + e.message);
    if (DEBUG) {
      Logger.log("Stack trace: " + e.stack);
    }
  }
}

function processReport(currentSetting) {
  var columnsStr = currentSetting.metricsColumns.join(',');
  var whereClause = currentSetting.whereConditions.join(" ");

  var query = 'SELECT ' + columnsStr + ' ' +
    'FROM ' + currentSetting.reportType + ' ' +
    whereClause + ' ' +
    'DURING ' + currentSetting.dateRange;

  var report = AdsApp.report(query);
  var reportIterator = report.rows();
  var rowsProcessed = 0;

  while (reportIterator.hasNext()) {
    var row = reportIterator.next();
    var displayName = row['DisplayName'];
    var domain = row['Domain'];
    var campaignId = row['CampaignId'];

    var result = containsAny(displayName, currentSetting.stringsToExclude);
    if (result) {
      if (!currentSetting.matchedCampaigns[campaignId]) {
        currentSetting.matchedCampaigns[campaignId] = [];
      }
      currentSetting.matchedCampaigns[campaignId].push(domain);
    }

    rowsProcessed++;
    if (rowsProcessed % currentSetting.batchSize === 0) {
      Utilities.sleep(currentSetting.yieldTime);
    }
  }
}

function excludePlacements(currentSetting) {
  for (var campaignId in currentSetting.matchedCampaigns) {
    var campaignExcludedPlacementCounter = 0;
    var campaign;
    
    try {
      campaign = AdsApp.campaigns().withIds([campaignId]).get().next();
    } catch (e) {
      Logger.log("Error accessing campaign " + campaignId + ": " + e.message);
      continue;
    }
    
    var campaignName = campaign.getName();
    Logger.log("For Campaign: " + campaignName);

    var matchedDomains = currentSetting.matchedCampaigns[campaignId];
    for (var i = 0; i < matchedDomains.length; i++) {
      var domain = matchedDomains[i];
      
      try {
        if (!isAlreadyExcluded(campaign, domain)) {
          campaign.display().newPlacementBuilder()
            .withUrl(domain)
            .exclude();
          campaignExcludedPlacementCounter++;
          Logger.log("  " + campaignExcludedPlacementCounter + ") excluding the domain: " + domain);
        } else {
          Logger.log("  Domain already excluded: " + domain);
        }
      } catch (e) {
        Logger.log("Error excluding domain " + domain + " for campaign " + campaignName + ": " + e.message);
      }

      if (i % 50 === 0) {  // Yield every 50 exclusions
        Utilities.sleep(1000);
      }
    }
    Logger.log(" -> " + campaignExcludedPlacementCounter + " new negative placements added to campaign " + campaignName + ".");
    Logger.log("");
  }
}

function isAlreadyExcluded(campaign, domain) {
  var existingExclusions = campaign.display().excludedPlacements().get();
  while (existingExclusions.hasNext()) {
    var exclusion = existingExclusions.next();
    if (exclusion.getUrl() === domain) {
      return true;
    }
  }
  return false;
}

function containsAny(str, substrings) {
  for (var i = 0; i < substrings.length; i++) {
    if (str.indexOf(substrings[i]) !== -1) {
      return substrings[i];
    }
  }
  return null;
}

See Also