← All scripts
Performance Max

Auto-exclude Performance Max site & app placements

This script queries performance_max_placement_view for placements with impressions in the last 30 days, deduplicates them, and adds website and mobile app placements as account-level exclusions using customerNegativeCriterionOperation. Website URLs are normalized by stripping protocols and paths before exclusion.

Setup

  1. Go to Tools & Settings > Bulk actions > Scripts.
  2. Click + to create a script and paste the code below.
  3. Click Authorize, then Preview to inspect the logs.
  4. Save and schedule it Daily or Weekly.
  5. Verify exclusions under Tools > Content suitability > Advanced settings > Account-level placement exclusions.

Script code

function main() {
  var minImpressions = 0;
  var badPlacements = [];

  // 1. Query Performance Max placement reporting
  var pmaxQuery =
    "SELECT performance_max_placement_view.placement, performance_max_placement_view.placement_type, metrics.impressions " +
    "FROM performance_max_placement_view " +
    "WHERE metrics.impressions > " + minImpressions + " " +
    "AND segments.date DURING LAST_30_DAYS";

  try {
    var report = AdsApp.report(pmaxQuery);
    var rows = report.rows();

    while (rows.hasNext()) {
      var row = rows.next();
      var placement = row["performance_max_placement_view.placement"];
      var type = row["performance_max_placement_view.placement_type"];

      if ((type === "WEBSITE" || type === "MOBILE_APPLICATION") && placement) {
        badPlacements.push({ placement: placement, type: type });
      }
    }
  } catch (e) {
    Logger.log("Error querying PMax placements: " + e);
  }

  // 2. Deduplicate and inject into Account-Level Exclusions
  if (badPlacements.length > 0) {
    var uniqueMap = {};
    var uniquePlacements = [];
    for (var k = 0; k < badPlacements.length; k++) {
      var item = badPlacements[k];
      if (!uniqueMap[item.placement]) {
        uniqueMap[item.placement] = true;
        uniquePlacements.push(item);
      }
    }

    Logger.log("=== EXPORTED PLACEMENTS (" + uniquePlacements.length + ") ===");
    for (var j = 0; j < uniquePlacements.length; j++) {
      Logger.log(uniquePlacements[j].placement + " (" + uniquePlacements[j].type + ")");
    }
    Logger.log("==========================================");

    var successCount = 0;
    var skippedCount = 0;
    Logger.log("Injecting placements into Account-Level Exclusions...");

    for (var i = 0; i < uniquePlacements.length; i++) {
      var item = uniquePlacements[i];
      var placementStr = item.placement;
      var type = item.type;
      var operation = null;

      // Handle Mobile App package IDs (e.g. 1-123456789 or 2-com.example.app)
      if (type === "MOBILE_APPLICATION" || isAppPackageId(placementStr)) {
        operation = {
          customerNegativeCriterionOperation: {
            create: {
              mobileApplication: {
                appId: placementStr
              }
            }
          }
        };
      }
      // Handle Web Domains
      else if (type === "WEBSITE" || isValidDomain(placementStr)) {
        var cleanUrl = placementStr.replace(/^https?:\/\//i, '').split('/')[0];
        operation = {
          customerNegativeCriterionOperation: {
            create: {
              placement: {
                url: cleanUrl
              }
            }
          }
        };
      } else {
        Logger.log("Skipping unrecognized placement format: " + placementStr);
        skippedCount++;
        continue;
      }

      var result = AdsApp.mutate(operation);
      if (result.isSuccessful()) {
        successCount++;
      } else {
        Logger.log("Failed to exclude " + placementStr + ": " + result.getErrorMessages().join(", "));
        skippedCount++;
      }
    }

    Logger.log("SUCCESS: Excluded " + successCount + " placements. (" + skippedCount + " skipped/failed).");
  } else {
    Logger.log("No placements met the criteria for exclusion today.");
  }
}

// Detects mobile app package ID format (1- iOS App ID, 2- Android Package Name)
function isAppPackageId(str) {
  return /^[12]-/.test(str);
}

// Validates domain syntax (supports subdomains like sub.domain.com and short 2-letter domains)
function isValidDomain(domain) {
  var clean = domain.replace(/^https?:\/\//i, '').split('/')[0];
  return /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(clean);
}

Want scripts like this built out for your own account?

Get started