Brianlabs is hiring

Sorry Google, we’ve written a script to reverse your ‘Close Variant Matching’

Sorry Google, we’ve written a script to reverse your ‘Close Variant Matching’

Google 0 – 1 Brainlabs

You may have heard that Google are removing exact match at the end of September. They’re telling us that it will give us “control with less complexity” which is really patronising for those sophisticated advertisers who have built systems and workflows around precise match types. It’s just a way of collecting more cash. People are going bonkers about it – there’s even a petition to make Google reverse this decision.

But instead of moaning and waiting for a change that will never come, we’ve written a script to reverse all this “close variant matching”. If you’ve not run a script before please read our Introduction to AdWords Scripts. For more open-source fun check out our AdWords Scripts directory. And if you want to use some of our technology that’s arrived back from the future then sign up for one of our paid plans on the Brainlabs Tech Stack.


 * Adds as campaign or AdGroup negatives search queries which have triggered exact keywords
 * Version: 1.1
 * Updated: 2015-10-26
 * Authors: Visar Shabi & Daniel Gilbert
function main() {


  //Choose whether to add your negative exact keywords at campaign or AdGroup level.
  //Set variable as "true" to add or as "false" to not add.
  var AddAdGroupNegative = true;  // true or false
  var AddCampaignNegative = true; // true of false

  //Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.
  //Leave blank, i.e. "", if you want this script to run over all campaigns and AdGroups.
  var campaignNameContains = "";
  var adGroupNameContains = "";


  var campaigns = {};
  var adGroups = {};

  var exactKeywords = [];

  //Pull a list of all exact match keywords in the account

  var report =
    "SELECT AdGroupId, Id " +
    "WHERE Impressions > 0 AND KeywordMatchType = EXACT " +

  var rows = report.rows();
  while (rows.hasNext()) {
    var row =;
    var keywordId = row['Id'];
    var adGroupId = row['AdGroupId'];
    exactKeywords.push(adGroupId + "#" + keywordId);

  //Pull a list of all exact (close variant) search queries

  var report =
    "SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, QueryMatchTypeWithVariant " +
    "WHERE CampaignName CONTAINS_IGNORE_CASE '" + campaignNameContains + "' " +
    "AND AdGroupName CONTAINS_IGNORE_CASE  '" + adGroupNameContains + "' " +

  var rows = report.rows();
  while (rows.hasNext()) {
    var row =;
    var adGroupId = parseInt(row['AdGroupId']);
    var campaignId = parseInt(row['CampaignId']);
    var keywordId = parseInt(row['KeywordId']);
    var searchQuery = row['Query'].toLowerCase();
    var keyword = row['KeywordTextMatchingQuery'].toLowerCase();
    var matchType = row['QueryMatchTypeWithVariant'].toLowerCase();
    if(keyword !== searchQuery && matchType.indexOf("exact (close variant)") !== -1){

        campaigns[campaignId] = [[], []];

      campaigns[campaignId][1].push(adGroupId + "#" + keywordId);

        adGroups[adGroupId] = [[], []];

      adGroups[adGroupId][1].push(adGroupId + "#" + keywordId);

  //Parse data correctly

  var adGroupIds = [];
  var campaignIds = [];
  var adGroupNegatives = [];
  var campaignNegatives = [];

  for(var x in campaigns){
    for(var y = 0; y < campaigns[x][0].length; y++){
      var keywordId = campaigns[x][1][y];
      var keywordText = campaigns[x][0][y];
      if(exactKeywords.indexOf(keywordId) !== -1){

  for(var x in adGroups){
    for(var y = 0; y < adGroups[x][0].length; y++){
      var keywordId = adGroups[x][1][y];
      var keywordText = adGroups[x][0][y];
      if(exactKeywords.indexOf(keywordId) !== -1){

  //Create the new negative exact keywords

  var campaignResults = {};
  var adGroupResults = {};

    var campaignIterator = AdWordsApp.campaigns()
      var campaign =;
      var campaignId = campaign.getId();
      var campaignName = campaign.getName();
      var campaignIndex = campaignIds.indexOf(campaignId);
      for(var i = 0; i < campaignNegatives[campaignIndex].length; i++){
        campaign.createNegativeKeyword("[" + campaignNegatives[campaignIndex][i] + "]")
          campaignResults[campaignName] = [];

    var adGroupIterator = AdWordsApp.adGroups()
      var adGroup =;
      var adGroupId = adGroup.getId();
      var adGroupName = adGroup.getName();
      var adGroupIndex = adGroupIds.indexOf(adGroupId);
      for(var i = 0; i < adGroupNegatives[adGroupIndex].length; i++){
        adGroup.createNegativeKeyword("[" + adGroupNegatives[adGroupIndex][i] + "]");
          adGroupResults[adGroupName] = [];

  //Format the results

  var resultsString = "The following negative keywords have been added to the following campaigns:";

  for(var x in campaignResults){
    resultsString += "\n\n" + x + ":\n" + campaignResults[x].join("\n");

  resultsString += "\n\n\n\nThe following negative keywords have been added to the following AdGroups:";

  for(var x in adGroupResults){
    resultsString += "\n\n" + x + ":\n" + adGroupResults[x].join("\n");




Share this post

Comments (16)

  • Jeff

    That’s worrying to hear that Google are oing away with exacy match. What exactly do you do with this code to make it work?

    September 22, 2014 at 11:19 am
  • Dewaldt Huysamen

    Thanks gents

    September 24, 2014 at 5:18 am
  • Richard Farrell

    Thanks very much Daniel
    1 question if i may, does any of this script need changing for the account, or can we add it as it is?

    September 24, 2014 at 1:59 pm
    • Daniel Gilbert

      Hi Richard. Should work with most accounts but difficult to say without seeing your precise structure!

      What I’d say is that you should look at the first portion of the script where there are configuration options:

      //Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.

      //Leave blank, i.e. “”, if you want this script to run over all campaigns and AdGroups.

      var campaignNameContains = “”;

      var adGroupNameContains = “”;

      So if you only want to run the script on certain campaigns then insert the name(s) between the speechmarks in the var campaignNameContains = “”.

      One tip: if you just load the script and click ‘Preview’ then you’ll see the changes that are going to be made. Double check these and report back if it’s not as expected. You can also log all changes and get them emailed to you – more on this in my Econsultancy post here:

      I can just add this to the script if there’s demand for a log and send functionality.

      September 24, 2014 at 2:37 pm
      • Richard Farrell

        HI Daniel.
        Thanks for your reply
        I’ll admit i’m a complete newbie when it comes to reading scripts :-S

        One more question (sorry)
        Is it possible to ammend the script to also include it to look up and compare phrase match?
        for example, the phrase match keyword “business ecards” to stop matching out to a search queries containing “business cards” appearing as a near phrase search query.

        September 25, 2014 at 10:01 am
        • Daniel Gilbert

          No worries Richard!

          The script as it is will work for phrase match as well – it will work for all “close variant” matches. To check that it’s working as you’d expect, just load up the script and specify in the config options the name of a campaign where you have some sample phrase match terms. Run a preview and all should be good!

          September 26, 2014 at 6:10 pm
  • Pingback: An AdWords Script To Make Exact Match, Well...Exact

  • Pingback: AdWords Exact Match Skript - AdWords-Kosten sparen

  • Kimberley Harwood

    This is brilliant, thanks Daniel!
    Quick question though – if I have similar exact keywords in the same ad group e.g. [cheap product] and [cheap products], and they cross-match to each other due to being “close” variants, is there a risk I could end up with [cheap products] as a negative in said ad group?
    Thanks again

    October 3, 2014 at 3:21 pm
    • Daniel Gilbert

      Hi Kimberley – no worries!

      You raise a valid concern. My first step would be to run a Preview of the script to check this as every account structure is different.

      Our structure has one keyword per adgroup so this is not a problem! Probably what you’d need to do is customise the script with a rule to say “if the keyword is already on exact within the same adgroup/campaign then don’t add as a negative”. We’ll have a go at this later and send on any updates!

      October 6, 2014 at 7:50 am
  • Nick Murden

    So essentially we are creating an automatically generated list of close variants to then add as exact negatives?

    October 23, 2014 at 11:24 am
  • Darryn

    To answer Kimberley’s question, the script will work fine for her example. Those keywords will not be added but flagged as errors instead (cannot target and exclude the same keyword).

    Very handy script btw. Thanks.

    November 6, 2014 at 5:10 pm
  • SearchEngine Pro

    Would love some help on implementing this script – Thx

    January 11, 2015 at 9:14 pm
  • Pingback: Keywords Are Back For Google Shopping Campaigns!

  • Davy van Otterdijk

    First of all, thank you very much for this script. We’ve been using it for about 6 months and were very happy with it at first, but are now facing some issues. In two accounts we’ve seen the following happen: Keyword is [domain nl], and the close variant match is [domain n l], so with an extra space between n and l. The script adds [domain n l] as a negative keyword in the adgroup but this also means we no longer see impressions on [domain nl]. In other words; the negative keyword [domain n l] also excludes the keyword [domain nl]. I think it is very weird that an exact negative keyword that does not exactly match the exact positive keyword prevents it from getting impressions.

    Have you heard of this before and do you have a solution for this?

    March 30, 2016 at 11:49 am

Comments are closed.