Bad Placement Cleaner – Google Ads Mega Script

One thing where Google Ads excel is taking away your money by driving shitty traffic through its Display Network. What’s more Google makes it increasingly difficult to get rid of this garbage. This new script will help you minimize bad placements in Google Display Network and will save you tons of your marketing dollars especially if your accounts generate small number of conversions and you cannot afford to run tCPA bidding. The configuration of this script is a bit longer process but it’s definitely worth it.

The Idea Behind Bad Placement Cleaner

There are 4 areas which I’m trying to solve:

  1. Automation of placement exclusions based on domain endings – typically you’d want to exclude all placements with “.xyz” domain ending since most of the traffic is crap.
  2. Automation of bad placement exclusions in standard display campaigns
  3. Automation of bad placement exclusions in video campaigns
  4. Automation of mobile apps exclusions

Use Cases

If you are asking yourself any of these “how-to” questions then Bad Placement Cleaner help you:

  1. How do I automatically exclude all domains ending with “.info” or “.xyz” (or any ending) as soon as they get 1 impression?
  2. How do I automatically exclude bad placements based on metrics which I can define?
  3. How do I automatically exclude all placements in apps?


It would not be Google if there weren’t some serious limitations. Generally speaking, I’ve tried to implement everything which was possible to implement but the following items are still a problem:

  1. Google Ads Scripts cannot touch Smart Display Campaigns so the script is completely omitting them. You can complain to Google about this here.
  2. Google Ads Scripts cannot touch Smart Shopping Campaigns so the script is completely omitting them.
  3. Google Ads Scripts cannot add specific YouTube channels/profiles into exclusions lists. You can complain to Google about this here. So basically, the script can only exclude non-YouTube placements.
  4. Google Ads Scripts cannot touch specific URLs under a domain. So it sees “” but it does not see “”.
  5. If you use comma or quote signs in your campaign names the script will fail.

Script Operations Step by Step

Since the script is the most complicated beast I’ve ever created, you should know what’s actually happening when you run the thing. Here are the simplified steps.

  1. The script loads the values from you config spreadsheet.
  2. The script pulls campaign performance data. This table allows me to identify what’s a Display and what’s a Video campaign.
  3. The script runs placement performance report for “today” – this is for the domain ending exclusion module.
  4. The script identifies all placements matching all your unwanted domain endings
  5. The script runs 2 placement performance reports – one for Display, one for Video. The results are separately exported to your config spreadsheet.
  6. I aggregate results for each placement on account level – regardless of its campaign and adgroup. Again, if you click around the config sheet, you will see sheets with these aggregations.
  7. Once aggregations are done, I filter for bad placements according to your configuration. There are helper sheets for that in the config spreadsheet – you can check out what the results are after the script finishes running.
  8. The filtered tables finally give me the “bad placements”.
  9. The script creates 4 exclusion lists – 1 for Domain Endings, 1 for Display, 1 for Video, 1 for Apps.
  10. The exclusions lists are filled with bad placements (in case bad placements are found).
  11. Enabled Display campaigns are assigned with Domain Endings, Display, and App exclusion list.
  12. Enabled Video campaigns are assigned with Domain Endings, and Video exclusion list.
  13. Email goes out if changes are made. It contains information about new bad placements and about campaigns which newly got the exclusion lists.

Here is a video of the script in action:


Step 1: Setup of the Config Sheet

Unlike with my previous scripts where the configuration is done in the script itself, this mega script has a dedicated config spreadsheet which looks like this:

Bad Placement Cleaner - 1 - Config v2

You go basically row by row and enter your values/make selections. If you are not sure what any of the rows is doing, you can read the note in the D column.

When you run the script for the first time, do not fill in the license ID – it will get filled automatically during the first run.

The script has 4 modules. Module is basically a huge chunk of code which is or is not executed based on your settings in “Module enabled?” option. So for example, if you select “Yes” in Display Campaigns Module, it means the script will process your display campaign placements and add exclusions in case bad placements are found.

The modules are:

  1. Domain Endings Module
  2. Display Campaigns Module
  3. Video Campaigns Module
  4. Apps Module

The best practice is to enable only the modules which you really need as the script will run faster.

Obviously, the most important part for each module is the threshold definition. All the rules work on AND basis you still need to setup a threshold even for the metrics which you really don’t care about. See the following samples below.



Configuration Samples

So again, keep in mind that all metrics work on AND basis so even if you are interested only in 1 one metric, the remaining metric still need to fit into the rule. Here are my samples.

I want to exclude all placements ending with “.xyz”:

Bad Placement Cleaner - 3 - XYZ Config

I want to exclude all placements ending with “.xyz” OR with “.info”:

Bad Placement Cleaner - 3 - XYZ and INFO Config

I want to exclude all Display placements which have CTR above 90%:

Bad Placement Cleaner - 3 - Display Config

So in this case, I set CTR >= 90% and I keep the other metrics as >=0 since that filter will be always true.

I want to exclude all Apps as soon as they get 1 impression:

Bad Placement Cleaner - 4 - App Config

In this case, I set Impressions >= 1% and I keep the other metrics as >=0 since that filter will be always true.

I want to run 2 different rules based on combination of multiple metrics inside 1 module:

E.g. you want to run exclusion based CTR > 90% as one check and then you want to exclude Display placements based on a combination of Cost and CPA. In this case, you will have to setup 2 config sheets and run 2 instances of this script in Google Ads – each script pointing to a different config sheet.

Step 2: Setup of the Script

You have to start by downloading the script from this page.

Once your config spreadsheet is ready, you need to enter the spreadsheet ID into the script itself:
Bad Placement Cleaner - 1 - Config - Script

This is the only edit you have to do in the script itself.

Once your config sheet is ready AND you’ve setup the script in Google Ads,  you can finally run the script. I do not recommend to run the script in “Preview mode” since that will likely fail. At some point, the script assumes existence of exclusion lists which are not being created in the Preview mode. Don’t be afraid to run the script for real, if you are unhappy with the results, you’ll be easily able to revert – read further.



Seeing the Results

After the script finishes running, you need to go to the shared library in Google Ads and locate the 3 new placement exclusion lists.  You should see the placements there and the assigned campaigns.

Alternatively, you can go to your config spreadsheet and checkout these helper sheets:

  1. script_domain_endings_bad_placements
  2. script_display_filtered_bad_placements
  3. script_video_filtered_bad_placements
  4. script_apps_filtered_bad_placements

Those will show you the “bad placements” which were just added into the exclusion lists.


This script is fairly complex project with many variables. It’s possible you’ll get an error which I’ve never seen before. You can send me an email to and I will look into to. In an ideal world, you attach the copy of your log from the script console in the Google Ads.

5,000,000 Cells in Google Sheets Files

One of the errors you can encounter and which I cannot really fix is the cell limit in Google Sheets. I’m using the config spreadsheet as a place where I process all the raw data. The cell count limit for each spreadsheet is 5,000,000 cells. So if your account is huge, the script may fail. In this case the solution is to:

  1. Set higher number in “Minimum impressions for placement on ad group level to enter the evaluation process” at the top of the config sheet
  2. Use shorter date range – again, at the top of the config sheet.

Unfortunately, I cannot easily tell how many rows will be returned in the placement report so I (=the script) cannot act accordingly.

I Don’t Like What Script is Doing

If you are unhappy with the results, you can easily revert. Go to shared library in your Google Ads account, find the exclusion lists, remove all campaigns from them and then delete the lists.

Sheet ID XYZ Does Not Exist

Another error I’ve seen during the development process was “Sheet ID XYZ does not exists”. I do not know why it’s happening but re-running the script always helped.

Script is Timing Out

Your account is too big so try a shorter date range OR higher minimum impressions threshold.




This script is not perfect but it’s the best it can get at the moment. As soon as Google Ads starts supporting Smart Display Campaigns and Smart Shopping Campaigns, I will implement them into the script as well.

Nevertheless, I think even the current version can still save you thousands of dollars or at least hours of manual work.

So please… share this with your PPC buddies and let’s make Google cry for once!

Leave a Reply

Your email address will not be published. Required fields are marked *