The AdWords API will sunset on April 27, 2022. Migrate to the Google Ads API to take advantage of the latest Google Ads features.

Migration Samples

The code samples below provide examples of common migration functions using the AdWords API. Client Library.

#!/usr/bin/perl -w
#
# Copyright 2017, Google Inc. 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.
#
# This code example migrates your feed based sitelinks at campaign level to use
# extension settings.
# To learn more about extensionsettings, see
# https://developers.google.com/adwords/api/docs/guides/extension-settings.
# To learn more about migrating Feed based extensions to extension settings, see
# https://developers.google.com/adwords/api/docs/guides/migrate-to-extension-settings.

use strict;
use lib "../../../lib";

use Google::Ads::AdWords::Client;
use Google::Ads::AdWords::Logging;
use Google::Ads::AdWords::v201809::CampaignExtensionSetting;
use Google::Ads::AdWords::v201809::CampaignExtensionSettingOperation;
use Google::Ads::AdWords::v201809::CampaignFeedOperation;
use Google::Ads::AdWords::v201809::ExtensionSetting;
use Google::Ads::AdWords::v201809::ExtensionSetting::Platform;
use Google::Ads::AdWords::v201809::FeedItem;
use Google::Ads::AdWords::v201809::FeedItemOperation;
use Google::Ads::AdWords::v201809::SitelinkFeedItem;
use Google::Ads::AdWords::v201809::UrlList;
use Google::Ads::AdWords::Utilities::PageProcessor;

use constant PAGE_SIZE => 500;

use Cwd qw(abs_path);

# See the Placeholder reference page for a list of all the placeholder types and
# fields.
# https://developers.google.com/adwords/api/docs/appendix/placeholders
use constant PLACEHOLDER_SITELINKS                        => 1;
use constant PLACEHOLDER_FIELD_SITELINK_LINK_TEXT         => 1;
use constant PLACEHOLDER_FIELD_SITELINK_URL               => 2;
use constant PLACEHOLDER_FIELD_SITELINK_FINAL_URLS        => 5;
use constant PLACEHOLDER_FIELD_SITELINK_FINAL_MOBILE_URLS => 6;
use constant PLACEHOLDER_FIELD_SITELINK_TRACKING_TEMPLATE => 7;
use constant PLACEHOLDER_FIELD_SITELINK_LINE_1_TEXT       => 3;
use constant PLACEHOLDER_FIELD_SITELINK_LINE_2_TEXT       => 4;

# Example main subroutine.
sub migrate_to_extension_settings {
  my ($client) = @_;

  # Get all of the feeds for the client's account.
  my $feeds = get_feeds($client);

  foreach my $feed (@{$feeds}) {
    # Retrieve all the sitelinks from the current feed.
    my $feed_items = get_sitelinks_from_feed($client, $feed);

    # Get all the instances where a sitelink from this feed has been added to
    # a campaign.
    my $campaign_feeds =
      get_campaign_feeds($client, $feed, PLACEHOLDER_SITELINKS);

    my @all_feed_items_to_delete = ();
    foreach my $campaign_feed (@{$campaign_feeds}) {
      # Retrieve the sitelinks that have been associated with this campaign.
      my $feed_item_ids =
        get_feed_item_ids_for_campaign($client, $campaign_feed);
      my $platform_restriction =
        get_platform_restrictions_for_campaign($campaign_feed);

      if (@{$feed_item_ids}) {
        # Delete the campaign feed that associates the sitelinks from the feed
        # to the campaign.
        delete_campaign_feed($client, $campaign_feed);

        # Create extension settings instead of sitelinks.
        create_extension_setting($client, $feed_items, $campaign_feed,
          $feed_item_ids, $platform_restriction);

        # Mark the sitelinks from the feed for deletion.
        push @all_feed_items_to_delete, @{$feed_item_ids};
      } else {
        printf "Migration skipped for campaign feed with campaign ID %d " .
          "and feed ID %d because no mapped feed item IDs were found in " .
          "the campaign feed's matching function.\n",
          $campaign_feed->get_campaignId(),
          $campaign_feed->get_feedId();
      }
    }

    # Delete all the sitelinks from the feed.
    delete_old_feed_items($client, \@all_feed_items_to_delete, $feed);

    printf "Finished processing feed ID %d.\n", $feed->get_id();
  }
}

# Returns a list of all enabled feeds.
sub get_feeds() {
  my ($client) = @_;

  my $query =
    "SELECT Id, Name, Attributes WHERE Origin = 'USER' AND " .
    "FeedStatus = 'ENABLED'";

  # Paginate through results automatically, and retrieve the feeds.
  my @feeds = Google::Ads::AdWords::Utilities::PageProcessor->new({
      client    => $client,
      service   => $client->FeedService(),
      query     => $query,
      page_size => PAGE_SIZE
    })->get_entries();

  return \@feeds;
}

# Returns a dictionary of feed item ID to a list where each element is a
# dictionary of attribute name/value pairs representing a single sitelink
# feed item.
sub get_sitelinks_from_feed() {
  my ($client, $feed) = @_;

  my $feed_attribute_mappings =
    get_feed_mapping_attributes($client, $feed, PLACEHOLDER_SITELINKS);

  my $feed_items_by_id = {};
  foreach my $feed_item (@{get_feed_items($client, $feed)}) {
    my $attributes = {};

    foreach my $attribute_value (@{$feed_item->get_attributeValues()}) {
      my $field_ids =
        $feed_attribute_mappings->{$attribute_value->get_feedAttributeId()};

      if ($field_ids) {
        foreach my $field_id (@{$field_ids}) {
          if ($field_id == PLACEHOLDER_FIELD_SITELINK_LINK_TEXT) {
            $attributes->{'text'} = $attribute_value->get_stringValue();
          } elsif ($field_id == PLACEHOLDER_FIELD_SITELINK_URL) {
            $attributes->{'url'} = $attribute_value->get_stringValue();
          } elsif ($field_id == PLACEHOLDER_FIELD_SITELINK_FINAL_URLS) {
            $attributes->{'final_urls'} = $attribute_value->get_stringValues();
          } elsif ($field_id == PLACEHOLDER_FIELD_SITELINK_FINAL_MOBILE_URLS) {
            $attributes->{'final_mobile_urls'} =
              $attribute_value->get_stringValue();
          } elsif ($field_id == PLACEHOLDER_FIELD_SITELINK_TRACKING_TEMPLATE) {
            $attributes->{'tracking_url_template'} =
              $attribute_value->get_stringValue();
          } elsif ($field_id == PLACEHOLDER_FIELD_SITELINK_LINE_1_TEXT) {
            $attributes->{'line_1'} = $attribute_value->get_stringValue();
          } elsif ($field_id == PLACEHOLDER_FIELD_SITELINK_LINE_2_TEXT) {
            $attributes->{'line_2'} = $attribute_value->get_stringValue();
          }
        }
      }
    }

    $feed_items_by_id->{$feed_item->get_feedItemId()} = $attributes;
  }

  return $feed_items_by_id;
}

# Returns a map from feed attribute ID to the set of field IDs mapped to the
# attribute.
sub get_feed_mapping_attributes() {
  my ($client, $feed, $placeholder_type) = @_;

  my $query =
    sprintf "SELECT FeedMappingId, AttributeFieldMappings " .
    "WHERE FeedId = %d and PlaceholderType = %d " . "AND Status = 'ENABLED'",
    $feed->get_id(), $placeholder_type;

  # Paginate through results.
  # The contents of the subroutine will be executed for each feed mapping.
  # The attribute mappings hash is being passed as an argument to the
  # subroutine in order to populate it.
  my %attribute_mappings;
  Google::Ads::AdWords::Utilities::PageProcessor->new({
      client    => $client,
      service   => $client->FeedMappingService(),
      query     => $query,
      page_size => PAGE_SIZE
    }
    )->process_entries(
    # Normally, a feed attribute is mapped only to one field. However, you may
    # map it to more than one field if needed.
    sub {
      my ($feed_mapping) = @_;
      my @attributes = @{$feed_mapping->get_attributeFieldMappings()};
      foreach my $attribute_mapping (@attributes) {
        my $attribute_id = $attribute_mapping->get_feedAttributeId();
        if (!$attribute_mappings{$attribute_id}) {
          $attribute_mappings{$attribute_id} = [];
        }
        push $attribute_mappings{$attribute_id},
          $attribute_mapping->get_fieldId();
      }
    },
    );

  return \%attribute_mappings;
}

# Returns the feed items for a feed.
sub get_feed_items() {
  my ($client, $feed) = @_;

  my $query =
    sprintf "SELECT FeedItemId, AttributeValues " .
    "WHERE Status = 'ENABLED' AND FeedId = %d",
    $feed->get_id();

  # Paginate through results automatically, and retrieve the feed items.
  my @feed_items = Google::Ads::AdWords::Utilities::PageProcessor->new({
      client    => $client,
      service   => $client->FeedItemService(),
      query     => $query,
      page_size => PAGE_SIZE
    })->get_entries();

  return \@feed_items;
}

# Returns the campaign feeds that use a particular feed for a particular
# placeholder type.
sub get_campaign_feeds() {
  my ($client, $feed, $placeholder_type) = @_;

  my $query =
    sprintf "SELECT CampaignId, MatchingFunction, PlaceholderTypes " .
    "WHERE Status = 'ENABLED' AND FeedId = %d AND " .
    "PlaceholderTypes CONTAINS_ANY [%d]",
    $feed->get_id(), $placeholder_type;

  # Paginate through results automatically, and retrieve the campaign feeds.
  my @campaign_feeds = Google::Ads::AdWords::Utilities::PageProcessor->new({
      client    => $client,
      service   => $client->CampaignFeedService(),
      query     => $query,
      page_size => PAGE_SIZE
    })->get_entries();

  return \@campaign_feeds;
}

# Returns platform restrictions for site links in a campaign.
sub get_platform_restrictions_for_campaign() {
  my ($campaign_feed) = @_;

  # If the campaign feed has multiple arguments joined with AND in the matching
  # function, then iterate through each of the arguments looking for
  # something similar to EQUALS(CONTEXT.DEVICE, 'Mobile').
  my $matching_function = $campaign_feed->get_matchingFunction();
  if ($matching_function->get_operator() eq 'AND') {
    foreach my $argument (@{$matching_function->get_lhsOperand()}) {
      if ( $argument->isa("Google::Ads::AdWords::v201809::FunctionOperand")
        && $argument->get_value()->get_operator() eq 'EQUALS')
      {
        my $lhs_operand = $argument->get_value()->get_lhsOperand()->[0];
        if (
          $lhs_operand->isa(
            "Google::Ads::AdWords::v201809::RequestContextOperand")
          && $lhs_operand->get_contextType() eq 'DEVICE_PLATFORM'
          )
        {
          my $platformRestriction =
            $argument->get_value()->get_rhsOperand()->[0]->get_stringValue();
          return Google::Ads::AdWords::v201809::ExtensionSetting::Platform->new(
            {value => uc $platformRestriction});
        }
      }
    }
  }
  return Google::Ads::AdWords::v201809::ExtensionSetting::Platform->new(
    {value => 'NONE'});
}

# Returns the list of feed item IDs that are used by a campaign through a given
# campaign feed.
sub get_feed_item_ids_for_campaign() {
  my ($client, $campaign_feed) = @_;

  my @feed_item_ids = ();

  my $matching_function = $campaign_feed->get_matchingFunction();
  my $operator          = $matching_function->get_operator();

  if ($operator eq 'IN') {
    # Check if matchingFunction is of the form IN(FEED_ITEM_ID,{xxx,xxx}).
    push @feed_item_ids,
      @{get_feed_items_from_argument($campaign_feed->get_matchingFunction())};
  } elsif ($operator eq 'AND') {
    foreach my $argument (@{$matching_function->get_lhsOperand()}) {
      # Check if matchingFunction is of the form IN(FEED_ITEM_ID,{xxx,xxx}).
      if ( $argument->isa("Google::Ads::AdWords::v201809::FunctionOperand")
        && $argument->get_value()->get_operator() eq 'IN')
      {
        push @feed_item_ids,
          @{get_feed_items_from_argument($argument->get_value())};
      }
    }
  }
  return \@feed_item_ids;
}

# Returns an array of feed item ids for a specified Function.
sub get_feed_items_from_argument() {
  my ($function) = @_;

  my @feed_item_ids = ();

  my $lhs_operand      = $function->get_lhsOperand();
  my $lhs_operand_size = @{$lhs_operand};
  if ( $lhs_operand_size == 1
    && $lhs_operand->[0]
    ->isa("Google::Ads::AdWords::v201809::RequestContextOperand")
    && $lhs_operand->get_contextType eq 'FEED_ITEM_ID')
  {

    foreach my $argument (@{$function->get_rhsOperand()}) {
      push @feed_item_ids, $argument->get_longValue();
    }
  }

  return \@feed_item_ids;
}

# Deletes a campaign feed.
sub delete_campaign_feed() {
  my ($client, $campaign_feed) = @_;

  my $result = $client->CampaignFeedService()->mutate({
      operations => [
        Google::Ads::AdWords::v201809::CampaignFeedOperation->new({
            operand  => $campaign_feed,
            operator => 'REMOVE'
          })]});

  printf "Deleted campaign feed for campaign ID %d and feed ID %d.\n",
    $campaign_feed->get_campaignId(), $campaign_feed->get_feedId();
  return $result->get_value(0);
}

# Creates the extension setting for a list of feed items.
sub create_extension_setting() {
  my ($client, $feed_items, $campaign_feed, $feed_item_ids,
    $platform_restriction)
    = @_;

  my $campaign_extension_setting =
    Google::Ads::AdWords::v201809::CampaignExtensionSetting->new({
      campaignId    => $campaign_feed->get_campaignId(),
      extensionType => 'SITELINK'
    });

  my @extension_feed_items = ();

  foreach my $feed_item_id (@{$feed_item_ids}) {
    my $item_attributes = $feed_items->{$feed_item_id};

    my $sitelink_feed_item =
      Google::Ads::AdWords::v201809::SitelinkFeedItem->new({
        sitelinkText  => $item_attributes->{'text'},
        sitelinkLine2 => $item_attributes->{'line_1'},
        sitelinkLine3 => $item_attributes->{'line_2'}});

    if ($item_attributes->{'final_urls'}) {
      $sitelink_feed_item->set_sitelinkFinalUrls(
        Google::Ads::AdWords::v201809::UrlList->new(
          {urls => $item_attributes->{'final_urls'}}));
      if ($item_attributes->{'final_mobile_urls'}) {
        $sitelink_feed_item->set_sitelinkFinalMobileUrls(
          Google::Ads::AdWords::v201809::UrlList->new(
            {urls => $item_attributes->{'final_mobile_urls'}}));
      }
      if ($item_attributes->{'tracking_url_template'}) {
        $sitelink_feed_item->set_trackingUrlTemplate(
          $item_attributes->{'tracking_url_template'});
      }
    } else {
      $sitelink_feed_item->set_sitelinkUrl($item_attributes->{'url'});
    }

    push @extension_feed_items, $sitelink_feed_item;
  }

  $campaign_extension_setting->set_extensionSetting(
    Google::Ads::AdWords::v201809::ExtensionSetting->new({
        extensions           => \@extension_feed_items,
        platformRestrictions => $platform_restriction
      }));

  my $result = $client->CampaignExtensionSettingService()->mutate({
      operations => [
        Google::Ads::AdWords::v201809::CampaignExtensionSettingOperation->new({
            operand  => $campaign_extension_setting,
            operator => 'ADD'
          })]});

  printf "Created extension setting for campaign ID %d.\n",
    $campaign_feed->get_campaignId();
  return $result->get_value(0);
}

# Deletes the old feed items for which extension settings have been created.
sub delete_old_feed_items() {
  my ($client, $feed_item_ids, $feed) = @_;

  if (!@{$feed_item_ids}) {
    printf "No old feed items to delete for feed ID %d.\n", $feed->get_id();
    return;
  }

  my @operations = ();
  foreach my $feed_item_id (@{$feed_item_ids}) {
    push @operations,
      Google::Ads::AdWords::v201809::FeedItemOperation->new({
        operator => 'REMOVE',
        operand  => Google::Ads::AdWords::v201809::FeedItem->new({
            feedId     => $feed->get_id(),
            feedItemId => $feed_item_id
          })});
  }

  my $result = $client->FeedItemService()->mutate({operations => \@operations});

  my $number_of_operations = @{$result->get_value()};
  printf "Deleted %d old feed items from feed ID %d.\n",
    $number_of_operations, $feed->get_id();
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Log SOAP XML request, response and API errors.
Google::Ads::AdWords::Logging::enable_all_logging();

# Get AdWords Client, credentials will be read from ~/adwords.properties.
my $client = Google::Ads::AdWords::Client->new({version => "v201809"});

# By default examples are set to die on any server returned fault.
$client->set_die_on_faults(1);

# Call the example
migrate_to_extension_settings($client);