A very popular way to structure Google Ads accounts is separating keywords by match types into different campaigns and/or ad groups. You can have the same keyword with different match types is a different campaign/ad group.

If you are using this type of account structure, it is prone to match type mistakes; exact match might end up in your broad match campaings or vice versa. 

This script makes sure your keyword match types are consistent with the match type you indicate in the naming of your campaigns/ad groups. As a bonus it also checks to make sure your keywords are formatted correctly for the specific match types (no dangling ' + ' signs).

Settings

In the script you should adjust the following things:

  • SPREADSHEET_URL: insert a new blank spreadsheet url
  • EMAIL_ADDRESS: insert your email
  • CAMPAIGN_LABEL: insert label to select campaigns by using a label
  • EXACT_MATCH_IDENTIFIER: text in your campaign names and/or ad group names to indicate exact match type
  • BROAD_MATCH_IDENTIFIER: text in your campaign names and/or ad group names to indicate broad match type
  • BROAD_MODIFIED_MATCH_IDENTIFIER : text in your campaign names and/or ad group names to indicate modified broad match type

Scheduling: Let's go daily!

Script

// Copyright 2019, Nils Rooijmans, All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


var SPREADSHEET_URL = "";  //insert a new blank spreadsheet url

var EMAIL_ADDRESS = ""; //insert your email
var EMAIL_SUBJECT = "[ALERT] - Keyword Match Type Issues";
    
var CAMPAIGN_LABEL = ""; // Leave blank for all campaigns 

// text in your campaign names and/or ad group names to indicate match type
var EXACT_MATCH_IDENTIFIER = '(exact)';
var BROAD_MATCH_IDENTIFIER = '(broad)';
var BROAD_MODIFIED_MATCH_IDENTIFIER = '(BMM)';

var LOG_ISSUE="yes" ; // Blank to stop use of Logger.log if lots of issues

var totalNrOfIssues = 0;
var issues =[];

function main() {
  
  //prepare the Spreadsheet
  var ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = ss.getActiveSheet();
  sheet.clear(); //remove earlies alerts
  var header = [
    "Account Name", 
    "Campaign Name",
    "Ad Group Name",
    "Keyword",
    "KeywordId",
    "Issue "
  ];
  sheet.appendRow(header);


  var campaignSelector = AdWordsApp.campaigns()
                          .withCondition("AdvertisingChannelType = SEARCH") // Search campaings only (no display/shopping, ...)    
                          .withCondition("CampaignTrialType = BASE") //skip Drafts and Experiments
                          .withCondition("Status = ENABLED") ;
    
  if (CAMPAIGN_LABEL!="") {
    campaignSelector=campaignSelector
       .withCondition("LabelNames CONTAINS_ANY ['"+CAMPAIGN_LABEL+"']");
  }
      
  var campaignIterator = campaignSelector.get();  
  var campaignIds=[] ;
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    campaignIds.push(campaign.getId()) ;
  }

 	checkKeywordMatchTypes(campaignIds);
  
  if (issues.length > 0) { // there is at least one issue
    var range = sheet.getRange(2, 1, issues.length, header.length);
    range.setValues(issues);
 
    var emailBody = 
      "Number of issues: " + issues.length + "\n" + 
      "See details: "+ SPREADSHEET_URL + "\n";
    MailApp.sendEmail(EMAIL_ADDRESS, EMAIL_SUBJECT, emailBody);
  }
}



function checkKeywordMatchTypes(campaignIds) {

  var accountName = AdWordsApp.currentAccount().getName();

  // Check Exact Match Ad Groups and Keywords 
  var awql="SELECT CampaignName, AdGroupName, Criteria, Id, KeywordMatchType FROM KEYWORDS_PERFORMANCE_REPORT WHERE CampaignId IN ["+campaignIds.join(",")+"] AND AdGroupStatus='ENABLED' AND Status='ENABLED'" ;  
  
  var rows=AdWordsApp.report(awql).rows() ;
    
  while (rows.hasNext()) {
    var row = rows.next();
    var campaignName = row['CampaignName'].toString().trim();
    var adGroupName = row['AdGroupName'].toString().trim();
    var keyword = row['Criteria'].toString().trim();
    var keywordId = row['Id'].toString().trim();
    var keywordMatchType=row['KeywordMatchType'].toString().trim() ;
    
    // Wrong matchtype in Exact match group
    if ( ( adGroupName.toString().toLowerCase().indexOf(EXACT_MATCH_IDENTIFIER.toLowerCase())>=0 || 
           campaignName.toString().toLowerCase().indexOf(EXACT_MATCH_IDENTIFIER.toLowerCase())>=0  
         ) && (keywordMatchType.toUpperCase() != 'EXACT') 
       ) {
     logIssue("Match type issue: "+adGroupName+" | "+keyword+" : KW has match type: "+row['KeywordMatchType']);
     issues.push([accountName, campaignName, adGroupName, keyword, keywordId, "Keyword Match Type issue"]);
    }
    else if ( adGroupName.toLowerCase().indexOf(BROAD_MODIFIED_MATCH_IDENTIFIER.toLowerCase())>=0 ||
              campaignName.toLowerCase().indexOf(BROAD_MODIFIED_MATCH_IDENTIFIER.toLowerCase())>=0
      ) {
      if (keywordMatchType.toUpperCase() != 'BROAD') {
        logIssue("Match type issue: "+adGroupName+" | "+keyword+" : KW has match type: "+row['KeywordMatchType']);
        issues.push([accountName, campaignName, adGroupName, keyword, keywordId, "Keyword Match Type issue"]);
      } else if (!isValidMBMKeyword(keyword)) {
        logIssue("Match type issue: "+adGroupName+" | "+keyword+" : KW is not a valid MBM keyword");
        issues.push([accountName, campaignName, adGroupName, keyword, keywordId, "MBM Keyword validation issue"]);
      }
    }
    else if ( adGroupName.toLowerCase().indexOf(BROAD_MATCH_IDENTIFIER.toLowerCase())>=0 ||
              campaignName.toLowerCase().indexOf(BROAD_MATCH_IDENTIFIER.toLowerCase())>=0
      ) { 
      if (keywordMatchType.toUpperCase() != 'BROAD')  {
        logIssue("Match type issue: "+adGroupName+" | "+keyword+" : KW has match type: "+row['KeywordMatchType']);
        issues.push([accountName, campaignName, adGroupName, keyword, keywordId, "Keyword Match Type issue"]);
      }
      else if (!isValidBroadKeyword(keyword)) {
        logIssue("Match type issue: "+adGroupName+" | "+keyword+" : KW is not a valid Broad keyword");
        issues.push([accountName, campaignName, adGroupName, keyword, keywordId, "Broad Keyword validation issue"]);
      }
    }
  }
  
  function isValidMBMKeyword(keyword) {
	
    if(!(keyword[0]=='+')) return false; // MBM keyword needs to start with a '+' 
    if(/\+ /.test(keyword)) return false; // MBM keyords can not have '+' followed by a space -> the '+' needs to be immediately in front of the terms in keyword
    if(/\w\+/.test(keyword)) return false; // MBM keyords can not have '+' that is immediately preceded by a " word character"  -> the '+' needs to be preceded by a space 
    if(/\s\w/.test(keyword)) return false; // All the terms in the keyword need to be preceded by a '+' , not a whitespace

    return true;
  }

  function isValidBroadKeyword(keyword) {
    if( (keyword.toString().indexOf("+") != -1) || 
        (keyword.toString().indexOf("[") != -1) ||
        (keyword.toString().indexOf("]") != -1) ||
        (keyword.toString().indexOf('\"') != -1) 
      ) {
      Logger.log("  * Keyword is Unvalid Broad type");
      return false;
    }
    return true;
  }
  
  function logIssue(msg) {
    if (LOG_ISSUE!="") {
      Logger.log(msg) ;
    }
  }
}

Source: https://nilsrooijmans.com/

Sharing knowledge

Adsscripts.com is all about sharing knowledge. In the current market, PPC specialists like to keep their knowledge and experience to themselves. We're convinced that sharing knowledge can ensure that everyone gets better at their work. We want to change this by sharing our knowledge about scripts with everyone.

Do you also want to contribute? We are open to new ideas and feedback on everything you find on Adsscripts.com.

Contact us

Nils Rooijmans
Google Ads Scripter
Water Cooler Topics
Nils Rooijmans, Google Ads Scripter
Bas Baudoin
Teamleader PPC
Happy Leads
Bas Baudoin, Teamleader PPC
Martijn Kraan
Freelance PPC Specialist
Brightstep
Martijn Kraan, Freelance PPC Specialist
Tibbe van Asten
PPC Specialist
Founder Adsscripts
Tibbe van Asten, Senior PPC Automation Specialist