Introducing the PPC Ponderings Podcast. Delivering a thoughtful, investigative take on key PPC themes. Learn More.
Kirk Williams

The Google Shopping Script to Prevent Query Bleed in Priority-Segmented Campaigns

The Google Shopping Script to Prevent Query Bleed in Priority-Segmented Campaigns

10/25/19 UPDATE: Hello Facebook Agency Visitor Person!  We’re delighted to have you visit this awesome post. About a year ago, ZATO stopped offering Facebook Ads solutions so we could focus solely on what we do best: Google Ads. Because of this, we’re always interested in partnerships with great Social Advertising agencies (like yourself, wink wink!) and we offer referral fees for signed clients!  Anyway, back to it, and happy reading…

Post Summary

Hola! This amazing script is from the brain of Richard Fergie. He created it for ZATO, and I'm happy to share it with you. Hop onto Twitter to thank him! @RichardFergieI first began using this script exactly a year ago (April 20, 2016!), and it's been doing its job nicely ever since!

The History & Purpose of the Stop-Shopping-Query-Bleed Script

The basic purpose of the script, is to fit in nicely with the query filtering strategy as introduced by Martin Roettgerding, and detailed step-by-step by me here:A Step-By-Step Guide To Query-Level Bidding In Google Shopping

As quick background, this strategy allows you to utilize priority campaign settings, bid adjustments, shared budgets, and negative keywords to send queries into the correct campaign so you can bid according to buying intent in Shopping Ads (Let's Make Keyword Targeting Great Again, amirite?).

I've run this strategy in multiple accounts now for quite awhile and I have begun to notice a recurring problem. At times, queries would bleed over from my general campaign into my Brand or SKU campaigns and throw off my budget and bidding. Now, this will naturally happen if you don't have a shared budget connecting your campaign group, or if you have your bids in your high priority, general queries campaign set too low (this was discovered by Andreas Reiffen, it can actually remove that campaign from auction so it shifts the next campaign... your higher bid brand campaign... into the generl query auction). But those were not the case for me, sometimes, regardless of my setup supernatural shopping forces would allow for query bleed-over and destroy my bidding in 1 or 2 brands in specific clients.

Thus, I approached my friend, Richard, and asked if he would build this script for me. He graciously agreed, and in his spare time, proceeded to build it. I was hoping to introduce it at Hero Conf 2016 during my Shopping presentation, but unfortunately we were still testing it at that time.

Cautions and Generally Serious Notes

The primary concern I will note, is that this script is the *nuclear option*. You do not want to throw this into your accounts willy-nilly, as it could end up excluding far more than you intended. I would recommend ensuring all other aspects of your Shopping campaign are correct.

  1. You have setup up shared budgets for that campaign grouping
  2. You have not bid the high priority, low bid general terms campaign too low (otherwise it will drop from the auction)
  3. You have been adding general campaign-level-negative keywords to *ALL* shopping campaigns within that specific grouping

If you have done these things and are still experiencing query-bleed, then give this a try.Instructions:

  • Add the specific name of the campaign on Line 2 to which you want this script to apply.
  • Add the words that you want to *KEEP* in that campaign on Line 8.  Once it is run, the script will exclude all queries that *DO NOT INCLUDE* the words you have added in Line 8.  So if you are running this on a Nike Brand Shopping campaign, you would keep "Nike".
  • You can add as many words to *KEEP* as you want.

// enter the name of the campaignvar name_of_campaign = 'Shopping - Nike - BR - US'// enter the words you want to *keep*// all search queries that DO NOT contain one of these words// will be added as negativesvar keywords = ["nike","nikey","ETC"]// eventually, both of the above should be populated through// sheets// function that splits up a list of keywords into a list of// lists of words// For example// tokenise_list(["hello","novice scripter"]) -> [["hello"],["novice","scripter"]]// This is important when looking at negative keywords because we want to match// the whole of a word, not just a part of itfunction tokenise_list(ls) {var accumulator = []for (i in ls) {var keyword = ls[i];var tokenised_kwd = keyword.split(" ")accumulator.push(tokenised_kwd)}return(accumulator)}function arrayEqual(a, b) { if (a === b) return true; if (a == null || b == null) return false; if (a.length != b.length) return false;// If you don't care about the order of the elements inside // the array, you should sort both arrays here.for (var i = 0; i < a.length; ++i) { if (a[i] !== b[i]) return false; } return true; }// returns true if query matches tokenised list // e.g. match_tokenised("buzz buzz",[["buzz","buzz"],["blah"]]) = true function match_tokenised(query,tokenised_list) { var tokenised_query = query.split(" ") var n = 0;for (i in tokenised_list) { var testkwd = tokenised_list[i] var nwords = testkwd.lengthfor (var i = 0; i < (tokenised_query.length-nwords+1); i++) { var candidate = tokenised_query.slice(i,i+nwords) if (arrayEqual(candidate,testkwd)) { return true; } }}return false }function main() { var target_list = tokenise_list(keywords) var negatives = []// SQ Report. YESTERDAY has no data. LAST_7_DAY works var report = 'SELECT Query' + ' FROM SEARCH_QUERY_PERFORMANCE_REPORT ' + ' WHERE CampaignName = "' + name_of_campaign + '"' + ' DURING LAST_7_DAYS'); var rows = report.rows();// for each query in the report while (rows.hasNext()) { row =;// get the search query out of the row object var q = row["Query"]if(!match_tokenised(q,target_list)) { negatives.push(q) }}// negative keyword list is now complete Logger.log("Adding the following exact match negatives:") for (i in negatives) { Logger.log(negatives[i]) }// time to add them to the campaign var campaigns = AdWordsApp.shoppingCampaigns().withCondition('CampaignName = "'+name_of_campaign+'"').get() while (campaigns.hasNext()) { Logger.log("Adding negatives keywords") var campaign =; for (i in negatives) { var negativekwd = negatives[i] Logger.log("Adding negative: "+negativekwd) // add [] to make exact match campaign.createNegativeKeyword("["+negativekwd+"]"); } } }

Want more free content like this delivered directly to your inbox?
Subscribe Here
Kirk Williams
Owner & Chief Pondering Officer

Kirk is the owner of ZATO, his Paid Search & Social PPC micro-agency of experts, and has been working in Digital Marketing since 2009. His personal motto (perhaps unhealthily so), is "let's overthink this some more."  He even wrote a book recently on philosophical PPC musings that you can check out here: Ponderings of a PPC Professional.

He has been named one of the Top 25 Most Influential PPCers in the world by PPC Hero 6 years in a row (2016-2021), has written articles for many industry publications (including Shopify, Moz, PPC Hero, Search Engine Land, and Microsoft), and is a frequent guest on digital marketing podcasts and webinars.

Kirk currently resides in Billings, MT with his wife, six children, books, Trek Bikes, Taylor guitar, and little sleep.

Kirk is an avid "discusser of marketing things" on Twitter, as well as an avid conference speaker, having traveled around the world to talk about Paid Search (especially Shopping Ads).  Kirk has booked speaking engagements in London, Dublin, Sydney, Milan, NYC, Dallas, OKC, Milwaukee, and more and has been recognized through reviews as one of the Top 10 conference presentations on more than one occasion.

You can connect with Kirk on Twitter, and Linkedin, or follow his marketing song parodies on TikTok.

Continue reading

Find what you're looking for here: