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
- Go to Tools & Settings > Bulk actions > Scripts.
- Click + to create a script and paste the code below.
- Click Authorize, then Preview to inspect the logs.
- Save and schedule it Daily or Weekly.
- 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