Negative Keywords, Placements, and Shared Sets

Negative keywords prevent ads from showing on irrelevant search results. However, an incorrect match type in your negative keywords list can inadvertently block normal keyword matching, decreasing your campaign's effectiveness.

Negative placements prevent ads from showing alongside specific types of content or on specific websites, mobile apps, YouTube videos, or YouTube Channels on the Display Network.

This guide explains how to:

  • Automate the elimination of conflicting negative keywords using the AdWords API.
  • Use shared sets to manage negative keywords and placements.
  • Use negative customer criteria to exclude specific placements in all campaigns in your account.

Scenario

Assume you own a men's clothing store and want to increase traffic to your site by running a special winter holiday campaign for men's accessories. You recently ran a Search terms report, and noticed the ads in your campaign for men's accessories are also showing up when users search for women's silk scarves.

You have the broad match keywords "silk ties", "wool scarves", and "men's gifts" in your keywords list. You want to add "silk scarves" as a negative keyword, but which negative keyword match type should you choose?

The table below lists some search terms and indicates for each of the three negative match types whether an ad will be blocked.

Search term Negative keyword
-silk scarves
(negative broad)
-"silk scarves"
(negative phrase)
-[silk scarves]
(negative exact)
mens scarves
mens ties
scarves silk Circle-slash
silk ties
silk gift scarves Circle-slash
silk scarves Circle-slash Circle-slash Circle-slash
silk scarves gifts Circle-slash Circle-slash
silk ties wool scarves Circle-slash
womens silk scarves Circle-slash Circle-slash
wool scarves

Note how the broad match negative keyword -silk scarves blocks some valid search terms such as "silk ties wool scarves".

Identifying conflicting negative keywords at scale

It's easy to determine which negative keywords conflict with the positive keywords if you have only a few. It becomes significantly more difficult if you have thousands of keywords and hundreds of negative keywords in your account. Let's examine how to automate this process using the AdWords API.

To facilitate the demonstration, we'll restrict the scenario to positive keywords at the ad group level, and negative keywords at the campaign level.

Retrieving negative keywords

You retrieve the list of campaign-level negative keywords by running a Campaign Negative Keywords Performance report as follows:

def retrieve_negative_keywords(report_utils)
  report_definition = {
    :selector => {
      :fields => ['CampaignId', 'Id', 'KeywordMatchType', 'KeywordText']
    },
    :report_name => 'Negative campaign keywords',
    :report_type => 'CAMPAIGN_NEGATIVE_KEYWORDS_PERFORMANCE_REPORT',
    :download_format => 'CSV',
    :date_range_type => 'TODAY',
    :include_zero_impressions => true
  }

  campaigns = {}

  report = report_utils.download_report(report_definition)
  # Slice off the first row (report name).
  report.slice!(0..report.index("\n"))

  CSV.parse(report, { :headers => true }) do |row|
    campaign_id = row['Campaign ID']

    # Ignore totals row.
    if row[0] != 'Total'
      campaigns[campaign_id] ||= Campaign.new(campaign_id)
      negative = Negative.from_csv_row(row)
      campaigns[campaign_id].negatives << negative
    end
  end

  return campaigns
end

Retrieving positive keywords

Run a Keywords Performance report to retrieve the positive keywords list. The allowed_values parameter allows you to filter out campaigns, ad groups, and keywords by their status, and can accept the values PAUSED and REMOVED.

def retrieve_positive_keyword_report(report_utils, allowed_values)
  report_definition = {
    :selector => {
      :fields => ['CampaignId', 'CampaignName', 'AdGroupId', 'Id',
                  'KeywordMatchType', 'KeywordText'],
      :predicates => [
        {
          :field => 'CampaignStatus',
          :operator => 'IN',
          :values => allowed_values
        },
        {
          :field => 'AdGroupStatus',
          :operator => 'IN',
          :values => allowed_values
        },
        {
          :field => 'Status',
          :operator => 'IN',
          :values => allowed_values
        },
        {
          :field => 'IsNegative',
          :operator => 'IN',
          :values => ['false']
        }
      ]
    },
    :report_name => 'Ad group keywords',
    :report_type => 'KEYWORDS_PERFORMANCE_REPORT',
    :download_format => 'CSV',
    :date_range_type => 'TODAY',
    :include_zero_impressions => true
  }

  report = report_utils.download_report(report_definition)
  # Slice off the first row (report name).
  report.slice!(0..report.index("\n"))

  return report
end

Identifying conflicting negative keywords

Negative keywords work according to the following logic:

  • A negative keyword that is stricter than the search term will not block it. For example, -[silk scarves] will not block "red silk scarves".
  • A broad negative keyword blocks a search term if the search term contains ALL the words in the negative keyword. For example, -silk scarves will block "silk gift scarves" but not "wool scarves".
  • A phrase negative keyword blocks a search term if the search term contains all the words in the negative keyword as a single phrase. For example, -"silk scarves" will block "gift silk scarves" but not "silk gift scarves".
  • An exact negative keyword blocks a search term only if it exactly matches the search term. For example, -[silk scarves] will block "silk scarves" but not "red silk scarves".
  • Keywords are not case sensitive.
  • Close variant matches don't apply to negative keywords. For example, -silk scarves won't block "silk scarf", even though that keyword is a close variant match for "silk scarves".

The following method implements this logic:

def compare_keywords(negatives, positive)
  negatives.each do |negative|
    match_type = negative.match_type.downcase
    negative_text = negative.text.downcase
    positive_text = positive.text.downcase

    match = false

    # If the negative keyword is more strict than the positive one, it cannot
    # match.
    # E.g. a negative exact "cool shoe" will not prevent positive phrase
    # "cool shoe shine".
    positive_match_type = positive.match_type.downcase
    next if positive_match_type == 'broad' && match_type != 'broad'
    next if positive_match_type == 'phrase' && match_type == 'exact'

    # Exact matching with negative keywords triggers only when the full text of
    # the keywords is exactly the same.
    # E.g. a negative "silk scarves" will only match "silk scarves", not
    # "red silk scarves".
    if match_type == 'exact'
      match = (negative_text == positive_text)
    end

    # Phrase matching with negative keywords triggers when the negative phrase
    # is present in the target, completely unmodified.
    # E.g. a negative "silk scarves" will match "gift silk scarves", but not
    # "silk gift scarves".
    if match_type == 'phrase'
      negative_tokens = negative_text.split(' ')
      positive_tokens = positive_text.split(' ')

      positive_tokens.each_with_index do |positive_token, positive_index|
        # Iterate until the current token matches the first token in the
        # negative keyword.
        if positive_token == negative_tokens.first
          candidate_match = true
          # Do all of the subsequent tokens also match?
          negative_tokens[1..-1].each_with_index do |token, index|
            if token != positive_tokens[positive_index + index + 1]
              candidate_match = false
              break
            end
          end

          match = candidate_match
        end
      end
    end

    # Broad matching with negative keywords triggers when all of the words are
    # present and exactly the same.
    # E.g. a negative "silk scarves" will match "silk gift scarves", but not
    # "wool scarves".
    if match_type == 'broad'
      negative_tokens = negative_text.split(' ')
      positive_tokens = positive_text.split(' ')

      candidate_match = true

      negative_tokens.each do |token|
        if !positive_tokens.include?(token)
          candidate_match = false
          break
        end
      end

      match = candidate_match
    end

    negative.add_blocked(positive) if match
  end
end

Deleting conflicting negative keywords

Once you've identified the conflicting negative keywords, you can either:

  • Use the CampaignCriterionService to delete the conflicting negative keywords.
  • Generate a report and delete them manually.

You should periodically repeat this process to ensure that any changes to your lists of keywords or negative keywords have not introduced new conflicts.

Complete example code

The complete code examples used above are part of the AdWords API Ruby client library.

Shared sets

If you have a set of keywords or placements that gives you unwanted impressions or clicks across multiple campaigns, you can create a central list of these negative keywords or placements in AdWords and add it to all of your campaigns. This feature is known as shared sets.

Shared sets help manage your negative keywords and placement exclusions more efficiently by removing the need for duplication of entities across your account.

Support for creating shared sets in manager accounts was added in v201710. If you are using an earlier version of the AdWords API, you can create a shared set in your manager account using the UI, then manage its criteria and campaign associations using the AdWords API.

Shared set type Account type SharedSetService SharedCriterionService CampaignSharedSetService
Negative keywords Client Checkmark Checkmark Checkmark
Negative keywords Manager Starting with v201710 Checkmark Checkmark
Negative placements Client Checkmark Checkmark Checkmark
Negative placements Manager Circle-slash Circle-slash Circle-slash

This section explains how to use the AdWords API to create and work with shared sets. The example focuses on negative keywords. You can create shared sets of negative placements for client accounts using a similar approach.

The code examples use the AdWords API Java library. We have code examples for other supported client libraries as well.

Creating shared sets

To use a shared set of negative keywords, you must first create a shared set, and then populate it with the list of keywords you want to exclude.

The following code snippet creates a shared set of negative keywords using the SharedSetService.


// Create the operation.
SharedSetOperation operation = new SharedSetOperation();
operation.setOperator(Operator.ADD);

// Create the shared set.
SharedSet sharedSet = new SharedSet();
sharedSet.setName("API Negative keyword list for demo");

// Set the type of the shared set. This may be negative keywords or placements.
sharedSet.setType(SharedSetType.NEGATIVE_KEYWORDS);
operation.setOperand(sharedSet);

SharedSetReturnValue retval = sharedSetService.mutate(
    new SharedSetOperation[] {operation});
for (SharedSet set : retval.getValue()) {
  System.out.println("Shared set with id = " + set.getSharedSetId() + ", name = " +
      set.getName() + ", type = " + set.getType() + ", status = " + set.getStatus() +
      "was created.");
}

Once you've created the shared set, you can use the SharedCriterionService to add keywords to the newly created set. The following code snippet adds two new keywords to the shared set.

String[] keywordTexts = new String[] {"mars cruise", "mars hotels"};

List operations = new ArrayList();
for (String keywordText: keywordTexts) {
  // Create the shared criterion.
  Keyword keyword = new Keyword();
  keyword.setText(keywordText);
  keyword.setMatchType(KeywordMatchType.BROAD);

  SharedCriterion sharedCriterion = new SharedCriterion();
  sharedCriterion.setCriterion(keyword);
  sharedCriterion.setNegative(true);
  sharedCriterion.setSharedSetId(sharedSetId);

  SharedCriterionOperation operation = new SharedCriterionOperation();
  operation.setOperator(Operator.ADD);
  operation.setOperand(sharedCriterion);
  operations.add(operation);
}

SharedCriterionReturnValue retval = sharedCriterionService.mutate(operations.toArray(
    new SharedCriterionOperation[operations.size()]));
for (SharedCriterion sharedCriterion : retval.getValue()) {
  Keyword keyword = (Keyword) sharedCriterion.getCriterion();
  System.out.println("Added keyword with id = " + keyword.getId() + ", text = " +
      keyword.getText() + ", matchtype = " + keyword.getMatchType() + " to shared " +
      "set with id = " + sharedSetId + ".");
}

You can use the memberCount field to determine the number of keywords or placements in a shared set. Another useful field is referenceCount, which tells you how many campaigns a shared set is associated with.

Applying shared sets to a campaign

Once you've created a shared set, you can attach it to multiple campaigns using CampaignSharedSetService. The following code shows how to attach an existing shared set to a campaign:

// Create the campaign shared set
CampaignSharedSet campaignSharedSet = new CampaignSharedSet();
campaignSharedSet.setCampaignId(campaignId);
campaignSharedSet.setSharedSetId(sharedSetId);

CampaignSharedSetOperation operation = new CampaignSharedSetOperation();
operation.setOperator(Operator.ADD);
operation.setOperand(campaignSharedSet);

CampaignSharedSetReturnValue retval = campaignSharedSetService.mutate(
    new CampaignSharedSetOperation[] {operation});
for (CampaignSharedSet attachedCampaignSharedSet : retval.value) {
  System.out.println("Attached shared set with id = " +
      attachedCampaignSharedSet.getSharedSetId() + " to campaign id " +
      attachedCampaignSharedSet.getCampaignId() + ".");
}

You can use the CampaignSharedSetService.get() method to retrieve the shared sets that have been applied to an existing campaign, as shown below.

// Create the selector.
Selector selector = new Selector();
selector.setFields(new String[] {"SharedSetId", "CampaignId", "SharedSetName",
   "SharedSetType", "Status"});

// Filter your results by specific campaign id.
Predicate predicate = new Predicate();
predicate.setField("CampaignId");
predicate.setOperator(PredicateOperator.EQUALS);
predicate.setValues(new String[] {campaignId.toString()});

// Filter your results by the type of shared set.
Predicate predicate1 = new Predicate();
predicate1.setField("SharedSetType");
predicate1.setOperator(PredicateOperator.IN);
predicate1.setValues(new String[] {"NEGATIVE_KEYWORDS", "NEGATIVE_PLACEMENTS"});

selector.setPredicates(new Predicate[] {predicate});

CampaignSharedSetPage page = campaignSharedSetService.get(selector);
if (page.getEntries() != null) {
  for (CampaignSharedSet campaignSharedSet : page.getEntries()) {
    System.out.println("Shared set with id = " + campaignSharedSet.getSharedSetId() +
        ", name = " + campaignSharedSet.getSharedSetName() + ", type = " +
        campaignSharedSet.getSharedSetType() + " and status = " +
        campaignSharedSet.getStatus() + " was found.");
    }
  }
}

Reporting

Use the Shared Set report to retrieve reports data for shared sets. Use the Campaign Shared Set report to retrieve reports data for campaign shared sets. The Shared Set Criteria report provides a downloadable snapshot of shared set criteria.

Account-level negative criteria

Starting with v201710 of the AdWords API, you can use the CustomerNegativeCriterionService to exclude specific Display Network criteria across all campaigns in your client account.

The service supports the following types of negative criteria:

Adding account-level exclusion criteria is similar to the shared set services, with the following key differences:

  • Criteria created through CustomerNegativeCriterionService apply to all campaigns in your account. With the shared set approach, the criteria will only be excluded in the campaigns associated with the shared set.

  • Excluding a criterion at the account level is a one-step process: simply add the CustomerNegativeCriterion. In contrast, excluding a criterion from specific campaigns using shared sets involves multiple steps: create the shared set, add criteria to the shared set, and associate the shared set with one or more campaigns.

The following code snippet shows how to prevent all ads in your account from appearing alongside certain types of content or on a specific website:

Java

// Get the CustomerNegativeCriterionService.
CustomerNegativeCriterionServiceInterface customerNegativeCriterionService =
    adWordsServices.get(session, CustomerNegativeCriterionServiceInterface.class);

List<Criterion> criteria = new ArrayList<>();

// Exclude tragedy & conflict content.
ContentLabel tragedyContentLabel = new ContentLabel();
tragedyContentLabel.setContentLabelType(ContentLabelType.TRAGEDY);
criteria.add(tragedyContentLabel);

// Exclude a specific placement.
Placement placement = new Placement();
placement.setUrl("http://www.example.com");
criteria.add(placement);

// Additional criteria types are available for this service. See the types listed
// under Criterion here:
// https://developers.google.com/adwords/api/docs/reference/latest/CustomerNegativeCriterionService.Criterion

// Create operations to add each of the criteria above.
List<CustomerNegativeCriterionOperation> operations = new ArrayList<>();
for (Criterion criterion : criteria) {
  CustomerNegativeCriterion negativeCriterion = new CustomerNegativeCriterion();
  negativeCriterion.setCriterion(criterion);
  CustomerNegativeCriterionOperation operation = new CustomerNegativeCriterionOperation();
  operation.setOperator(Operator.ADD);
  operation.setOperand(negativeCriterion);
  operations.add(operation);
}

// Send the request to add the criteria.
CustomerNegativeCriterionReturnValue result =
    customerNegativeCriterionService.mutate(
        operations.toArray(new CustomerNegativeCriterionOperation[operations.size()]));

// Display the results.
for (CustomerNegativeCriterion negativeCriterion : result.getValue()) {
  System.out.printf(
      "Customer negative criterion with criterion ID %d and type '%s' was added.%n",
      negativeCriterion.getCriterion().getId(),
      negativeCriterion.getCriterion().getCriterionType());
}

Send feedback about...

AdWords API
AdWords API
Need help? Visit our support page.