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

Extensions Samples

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

Associate a Google My Business feed to that of a customer

#!/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 example adds a feed that syncs feed items from a Google
# My Business (GMB) account and associates the feed with a customer.

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

use Google::Ads::AdWords::Client;
use Google::Ads::AdWords::Constants;
use Google::Ads::AdWords::Logging;
use Google::Ads::AdWords::v201809::ConstantOperand;
use Google::Ads::AdWords::v201809::CustomerFeed;
use Google::Ads::AdWords::v201809::CustomerFeedOperation;
use Google::Ads::AdWords::v201809::Feed;
use Google::Ads::AdWords::v201809::FeedOperation;
use Google::Ads::AdWords::v201809::Function;
use Google::Ads::AdWords::v201809::PlacesLocationFeedData;
use Google::Ads::AdWords::v201809::OAuthInfo;

use Cwd qw(abs_path);
use Data::Uniqid qw(uniqid);

# 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_LOCATION => 7;

# The maximum number of CustomerFeed ADD operation attempts to make before
# throwing an exception.
use constant MAX_CUSTOMER_FEED_ADD_ATTEMPTS => 10;

# The email address of either an owner or a manager of the GMB account.
my $gmb_email_address = 'INSERT_GMB_EMAIL_ADDRESS_HERE';

# If the gmbEmailAddress above is the same user you used to generate your
# AdWords API refresh token, leave the assignment below unchanged.
# Otherwise, to obtain an access token for your GMB account, run the
# generate_offline_credentials example. Make sure you are logged in as the
# same user as gmbEmailAddress above when you follow the link provided by the
# example, then capture the credential's access token. Copy and paste the value
# into the assignment below.
my $gmb_access_token;

# If the gmb_email_address above is for a GMB manager instead of the GMB account
# owner, then set businessAccountIdentifier to the +Page ID of a location for
# which the manager has access. See the location extensions guide at
# https://developers.google.com/adwords/api/docs/guides/feed-services-locations
# for details.
my $business_account_identifier = undef;

# Example main subroutine.
sub add_gmb_location_extensions {
  my ($client, $gmb_email_address, $gmb_access_token,
    $business_account_identifier)
    = @_;

  # Create a feed that will sync to the Google My Business account specified
  # by gmb_email_address. Do not add FeedAttributes to this object,
  # as AdWords will add them automatically because this will be a
  # system generated feed.
  my $gmb_feed = Google::Ads::AdWords::v201809::Feed->new({
      name => "Google My Business feed " . uniqid(),
      # Since this feed's feed items will be managed by AdWords,
      # you must set its origin to ADWORDS.
      origin => "ADWORDS"
  });

  my $oauth_info = Google::Ads::AdWords::v201809::OAuthInfo->new({
      httpMethod     => "GET",
      httpRequestUrl => Google::Ads::AdWords::Constants::DEFAULT_OAUTH_SCOPE,
      httpAuthorizationHeader => "Bearer ${gmb_access_token}"
  });
  my $feed_data = Google::Ads::AdWords::v201809::PlacesLocationFeedData->new({
      emailAddress => $gmb_email_address,
      oAuthInfo    => $oauth_info,
      # Optional: specify labels to filter Google My Business listings. If
      # specified, only listings that have any of the labels set are
      # synchronized into FeedItems.
      labelFilters => ["Stores in New York City"]
  });

  if ($business_account_identifier) {
    $feed_data->set_businessAccountIdentifier($business_account_identifier);
  }

  $gmb_feed->set_systemFeedGenerationData($feed_data);

  # Create an operation to add the feed.
  my $feed_operation = Google::Ads::AdWords::v201809::FeedOperation->new({
      operand  => $gmb_feed,
      operator => "ADD"
  });

  # Add the feed. Since it is a system generated feed, AdWords will
  # automatically:
  # 1. Set up the FeedAttributes on the feed.
  # 2. Set up a FeedMapping that associates the FeedAttributes of the feed
  # with the placeholder fields of the LOCATION placeholder type.
  my $feed_result = $client->FeedService()->mutate({
      operations => [
        Google::Ads::AdWords::v201809::FeedOperation->new({
            operator => "ADD",
            operand  => $gmb_feed
          })]});

  my $added_feed = $feed_result->get_value(0);

  printf "Added GMB feed with ID %d\n", $added_feed->get_id();

  # Add a CustomerFeed that associates the feed with this customer for
  # the LOCATION placeholder type.
  my $customer_feed = Google::Ads::AdWords::v201809::CustomerFeed->new({
      feedId           => $added_feed->get_id(),
      placeholderTypes => [PLACEHOLDER_LOCATION]});

  # Create a matching function that will always evaluate to true.
  my $customer_matching_function = Google::Ads::AdWords::v201809::Function->new(
    {
      lhsOperand => [
        Google::Ads::AdWords::v201809::ConstantOperand->new({
            type         => "BOOLEAN",
            booleanValue => 1
          })
      ],
      operator => "IDENTITY"
    });

  $customer_feed->set_matchingFunction($customer_matching_function);

  # Create an operation to add the customer feed.
  my $customer_feed_operation =
    Google::Ads::AdWords::v201809::CustomerFeedOperation->new({
      operand  => $customer_feed,
      operator => "ADD"
    });

  # After the completion of the Feed ADD operation above the added feed will not
  # be available for usage in a CustomerFeed until the sync between the AdWords
  # and GMB accounts completes.  The loop below will retry adding the
  # CustomerFeed up to ten times with an exponential back-off policy.
  my $added_customer_feed = undef;
  my $number_of_attempts  = 0;
  # Disable die on faults for this section because we want to retry failed
  # attempts to add the customer feed.
  $client->set_die_on_faults(0);
  do {
    $number_of_attempts++;
    my $customer_feed_result =
      $client->CustomerFeedService()
      ->mutate({operations => [$customer_feed_operation]});

    if ($customer_feed_result->isa("SOAP::WSDL::SOAP::Typelib::Fault11")) {
      # Wait using exponential backoff policy
      my $sleep_seconds = 5 * (2**$number_of_attempts);
      printf "Attempt #%d to add the CustomerFeed was not successful. " .
        "Waiting %d seconds before trying again.\n", $number_of_attempts,
        $sleep_seconds;
      sleep $sleep_seconds;
    } else {
      $added_customer_feed = $customer_feed_result->get_value(0);
      printf "Attempt #%d to add the CustomerFeed was successful.\n",
        $number_of_attempts;
    }
    } while ($number_of_attempts < MAX_CUSTOMER_FEED_ADD_ATTEMPTS
    and !$added_customer_feed);

  # Restore the previous setting of die on faults now that we are done retrying
  # requests.
  $client->set_die_on_faults(1);

  if (!$added_customer_feed) {
    die "Could not create the CustomerFeed after " .
      MAX_CUSTOMER_FEED_ADD_ATTEMPTS . " attempts. Please retry " .
      "the CustomerFeed ADD operation later.";
  }

  printf "Added CustomerFeed for feed ID %d and placeholder type %d\n",
    $added_customer_feed->get_feedId(),
    PLACEHOLDER_LOCATION;

  return 1;
}

# 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);

# If the access token has not been set, then retrieve the access token.
my $auth_handler = $client->get_oauth_2_handler();
if (!$gmb_access_token) {
  $gmb_access_token = $auth_handler->get_access_token();
}

# Call the example
add_gmb_location_extensions($client, $gmb_email_address,
  $gmb_access_token, $business_account_identifier);

Associate a price extension to an account

#!/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 example adds a prices feed and associates it with a campaign.

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

use Google::Ads::AdWords::Client;
use Google::Ads::AdWords::Logging;
use Google::Ads::AdWords::v201809::CustomerExtensionSetting;
use Google::Ads::AdWords::v201809::CustomerExtensionSettingOperation;
use Google::Ads::AdWords::v201809::FeedItemCampaignTargeting;
use Google::Ads::AdWords::v201809::FeedItemSchedule;
use Google::Ads::AdWords::v201809::FeedItemScheduling;
use Google::Ads::AdWords::v201809::Keyword;
use Google::Ads::AdWords::v201809::Location;
use Google::Ads::AdWords::v201809::Money;
use Google::Ads::AdWords::v201809::MoneyWithCurrency;
use Google::Ads::AdWords::v201809::PriceFeedItem;
use Google::Ads::AdWords::v201809::PriceTableRow;
use Google::Ads::AdWords::v201809::UrlList;

use Cwd qw(abs_path);
use Data::Uniqid qw(uniqid);

# Replace with valid values of your account.
my $campaign_id = "INSERT_CAMPAIGN_ID_HERE";

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

  # Create the price extension feed item.
  my $price_feed_item = Google::Ads::AdWords::v201809::PriceFeedItem->new({
      priceExtensionType => "SERVICES",
      # Price qualifer is optional.
      priceQualifier      => "FROM",
      trackingUrlTemplate => "http://tracker.example.com/?u={lpurl}",
      language            => 'en',
      campaignTargeting =>
        Google::Ads::AdWords::v201809::FeedItemCampaignTargeting->new({
          TargetingCampaignId => $campaign_id
        }
        ),
      scheduling => Google::Ads::AdWords::v201809::FeedItemScheduling->new({
          feedItemSchedules => [
            Google::Ads::AdWords::v201809::FeedItemSchedule->new({
                dayOfWeek   => "SUNDAY",
                startHour   => "10",
                startMinute => "ZERO",
                endHour     => "18",
                endMinute   => "ZERO"
              }
            ),
            Google::Ads::AdWords::v201809::FeedItemSchedule->new({
                dayOfWeek   => "SATURDAY",
                startHour   => "10",
                startMinute => "ZERO",
                endHour     => "22",
                endMinute   => "ZERO"
              })]})});

  # To create a price extension, at least three table rows are needed.
  my @table_rows = ();
  push @table_rows, create_price_table_row(
    "Scrubs",
    "Body Scrub, Salt Scrub",
    "http://www.example.com/scrubs",
    "http://m.example.com/scrubs",
    60000000,    # 60 USD
    "USD",
    "PER_HOUR"
  );
  push @table_rows, create_price_table_row(
    "Hair Cuts",
    "Once a month",
    "http://www.example.com/haircuts",
    "http://m.example.com/haircuts",
    75000000,    # 75 USD
    "USD",
    "PER_MONTH"
  );
  push @table_rows, create_price_table_row(
    "Skin Care Package",
    "Four times a month",
    "http://www.example.com/skincarepackage",
    undef,
    250000000,    # 250 USD
    "USD",
    "PER_MONTH"
  );
  $price_feed_item->set_tableRows(\@table_rows);

  # Create your customer extension settings. This associates the price
  # extension to your account.
  my $customer_extension_setting =
    Google::Ads::AdWords::v201809::CustomerExtensionSetting->new({
      extensionType    => 'PRICE',
      extensionSetting => Google::Ads::AdWords::v201809::ExtensionSetting->new({
          extensions => [$price_feed_item]})});

  my $mutate_result = $client->CustomerExtensionSettingService()->mutate({
      operations => [
        Google::Ads::AdWords::v201809::CustomerExtensionSettingOperation->new({
            operand  => $customer_extension_setting,
            operator => 'ADD'
          })]});

  my $new_extension_setting = $mutate_result->get_value(0);

  printf "Extension setting with type '%s' was added to your account.\n",
    $new_extension_setting->get_extensionType();
  return 1;
}

# Creates a new price table row with the specified attributes.
sub create_price_table_row {
  my ($header, $description, $final_url, $final_mobile_url, $price_in_micros,
    $currency_code, $price_unit)
    = @_;
  my $price_table_row = Google::Ads::AdWords::v201809::PriceTableRow->new({
      header      => $header,
      description => $description,
      finalUrls =>
        [Google::Ads::AdWords::v201809::UrlList->new({urls => [$final_url]})],
      price => Google::Ads::AdWords::v201809::MoneyWithCurrency->new({
          money => Google::Ads::AdWords::v201809::Money->new({
              microAmount => $price_in_micros
            }
          ),
          currencyCode => $currency_code
        }
      ),
      priceUnit => $price_unit
    });

  # Optional: set the mobile final URLs.
  if ($final_mobile_url) {
    $price_table_row->set_finalMobileUrls([
        Google::Ads::AdWords::v201809::UrlList->new(
          {urls => [$final_mobile_url]})]);
  }

  return $price_table_row;
}

# 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
add_prices($client, $campaign_id);


#!/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 example adds a sitelinks feed and associates it with a campaign.

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::FeedItemGeoRestriction;
use Google::Ads::AdWords::v201809::Keyword;
use Google::Ads::AdWords::v201809::Location;
use Google::Ads::AdWords::v201809::SitelinkFeedItem;

use Cwd qw(abs_path);
use Data::Uniqid qw(uniqid);

# Replace with valid values of your account.
my $campaign_id = "INSERT_CAMPAIGN_ID_HERE";

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

  my $customer           = $client->CustomerService()->getCustomers()->[0];
  my $customer_time_zone = $customer->get_dateTimeZone();
  printf(
    "Found customer ID %d with time zone %s.\n",
    $customer->get_customerId(),
    $customer_time_zone
  );

  # Create the site links.
  my $site_link_1 = Google::Ads::AdWords::v201809::SitelinkFeedItem->new({
      sitelinkText      => 'Store Hours',
      sitelinkFinalUrls => Google::Ads::AdWords::v201809::UrlList->new(
        {urls => ['http://www.example.com/storehours']})});

  # Set the startTime and endTime to show the Thanksgiving specials link only
  # from 20 - 27 Nov.
  my $site_link_2 = Google::Ads::AdWords::v201809::SitelinkFeedItem->new({
      sitelinkText      => 'Thanksgiving Specials',
      sitelinkFinalUrls => Google::Ads::AdWords::v201809::UrlList->new(
        {urls => ['http://www.example.com/thanksgiving']})});

  # The time zone of the start and end date/times must match the time zone of
  # the customer.
  my ($sec, $min, $hour, $mday, $mon, $year) = localtime(time - (60 * 60 * 24));
  my $start_time =
    sprintf("%d1120 000000 %s", ($year + 1900), $customer_time_zone);
  my $end_time =
    sprintf("%d1127 235959 %s", ($year + 1900), $customer_time_zone);
  $site_link_2->set_startTime($start_time);
  $site_link_2->set_endTime($end_time);

  # Target this sitelink for United States only. See
  # https://developers.google.com/adwords/api/docs/appendix/geotargeting
  # for valid geolocation codes.
  my $location = Google::Ads::AdWords::v201809::Location->new({
    id => 2840    # United States
  });
  $site_link_2->set_geoTargeting($location);

  # Restrict targeting only to people physically within the United States.
  # Otherwise, this could also show to people interested in the United States
  # but not physically located there.
  my $geo_targeting_restriction =
    Google::Ads::AdWords::v201809::FeedItemGeoRestriction->new({
      geoRestriction => "LOCATION_OF_PRESENCE"
    });
  $site_link_2->set_geoTargetingRestriction($geo_targeting_restriction);

  # Set the devicePreference to show the wifi details primarily for high end
  # mobile users.
  # Target this sitelink for the keyword "free wifi".
  my $keyword = Google::Ads::AdWords::v201809::Keyword->new({
    text      => "free wifi",
    matchType => "BROAD"
  });
  my $site_link_3 = Google::Ads::AdWords::v201809::SitelinkFeedItem->new({
      sitelinkText      => 'Wifi available',
      keywordTargeting  => $keyword,
      sitelinkFinalUrls => Google::Ads::AdWords::v201809::UrlList->new(
        {urls => ['http://www.example.com/mobile/wifi']}
      ),
      devicePreference =>
        Google::Ads::AdWords::v201809::FeedItemDevicePreference->new({
          # See https://developers.google.com/adwords/api/docs/appendix/platforms
          # for device criteria IDs.
          devicePreference => '30001'
        })});

  # Set the feedItemSchedules to show the happy hours link only during Mon - Fri
  # 6PM to 9PM.
  my $site_link_4 = Google::Ads::AdWords::v201809::SitelinkFeedItem->new({
      sitelinkText      => 'Happy hours',
      sitelinkFinalUrls => Google::Ads::AdWords::v201809::UrlList->new(
        {urls => ['http://www.example.com/mobile/happyhours']}
      ),
    });

  my @schedules = ();
  foreach my $day_name ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY')
  {
    push @schedules,
      Google::Ads::AdWords::v201809::FeedItemSchedule->new({
        'dayOfWeek'   => $day_name,
        'startHour'   => 18,
        'startMinute' => 'ZERO',
        'endHour'     => 21,
        'endMinute'   => 'ZERO',
      });
  }
  $site_link_4->set_scheduling(
    Google::Ads::AdWords::v201809::FeedItemScheduling->new(
      {feedItemSchedules => \@schedules}));

  my $campaign_extension_setting =
    Google::Ads::AdWords::v201809::CampaignExtensionSetting->new({
      campaignId       => $campaign_id,
      extensionType    => 'SITELINK',
      extensionSetting => Google::Ads::AdWords::v201809::ExtensionSetting->new({
          extensions => [$site_link_1, $site_link_2, $site_link_3, $site_link_4]
        })});

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

  my $added_extension_setting = $mutate_result->get_value(0);

  printf "Extension setting with type = %s was added to campaign ID %d.\n",
    $added_extension_setting->get_extensionType(),
    $added_extension_setting->get_campaignId();
  return 1;
}

# 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
add_site_links($client, $campaign_id);


#!/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 example adds a sitelinks feed and associates it with a campaign. To
# create a campaign, run add_campaigns.pl. To add sitelinks using the simpler
# ExtensionSetting services, see add_site_links.pl.

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

use Google::Ads::AdWords::Client;
use Google::Ads::AdWords::Logging;
use Google::Ads::AdWords::v201809::AttributeFieldMapping;
use Google::Ads::AdWords::v201809::CampaignFeed;
use Google::Ads::AdWords::v201809::CampaignFeedOperation;
use Google::Ads::AdWords::v201809::Feed;
use Google::Ads::AdWords::v201809::FeedAttribute;
use Google::Ads::AdWords::v201809::FeedItem;
use Google::Ads::AdWords::v201809::FeedItemAdGroupTarget;
use Google::Ads::AdWords::v201809::FeedItemAttributeValue;
use Google::Ads::AdWords::v201809::FeedItemCriterionTarget;
use Google::Ads::AdWords::v201809::FeedItemTargetOperation;
use Google::Ads::AdWords::v201809::FeedItemGeoRestriction;
use Google::Ads::AdWords::v201809::FeedItemOperation;
use Google::Ads::AdWords::v201809::FeedItemTargetOperation;
use Google::Ads::AdWords::v201809::FeedMapping;
use Google::Ads::AdWords::v201809::FeedMappingOperation;
use Google::Ads::AdWords::v201809::FeedOperation;
use Google::Ads::AdWords::v201809::Function;
use Google::Ads::AdWords::v201809::Location;

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_FINAL_URLS  => 5;
use constant PLACEHOLDER_FIELD_SITELINK_LINE_2_TEXT => 3;
use constant PLACEHOLDER_FIELD_SITELINK_LINE_3_TEXT => 4;

# Replace with valid values of your account.
my $campaign_id = "INSERT_CAMPAIGN_ID_HERE";
# Optional: Ad group to restrict targeting to. Set to undef to not use it.
my $ad_group_id = "INSERT_AD_GROUP_ID_HERE";
my $feed_name   = "INSERT_FEED_NAME_HERE";

# Example main subroutine.
sub add_site_links_using_feeds {
  my ($client, $campaign_id, $ad_group_id, $feed_name) = @_;

  my $site_links_data = {
    "siteLinksFeedId"             => 0,
    "linkTextFeedAttributeId"     => 0,
    "linkFinalUrlFeedAttributeId" => 0,
    "line2FeedAttributeId"        => 0,
    "line3FeedAttributeId"        => 0,
    "feedItemIds"                 => []};

  create_site_links_feed($client, $site_links_data, $feed_name);
  create_site_links_feed_items($client, $site_links_data);
  create_site_links_feed_mapping($client, $site_links_data);
  create_site_links_campaign_feed($client, $site_links_data, $campaign_id);

  return 1;
}

sub restrict_feed_item_to_geo_target {
  my($client, $feed_item, $location_id) = @_;

  # Optional: Restrict the first feed item to only serve with ads for the
  # specified geo target.
  my $criterion_target =
      Google::Ads::AdWords::v201809::FeedItemCriterionTarget->new({
          feedId     => $feed_item->get_feedId(),
          feedItemId => $feed_item->get_feedItemId(),
          # The IDs can be found in the documentation or retrieved with the
          # LocationCriterionService.
          criterion  => Google::Ads::AdWords::v201809::Location->new({
              id => $location_id
          })
      });

  my $operation =
      Google::Ads::AdWords::v201809::FeedItemTargetOperation->new({
          operator => "ADD",
          operand  => $criterion_target
      });

  my $result = $client->FeedItemTargetService()
      ->mutate({operations => [$operation]});

  my $new_location_target = $result->get_value(0);

  printf(
      "Feed item target for feed ID %d and feed item ID %d" .
          " was created to restrict serving to location ID %d",
      $new_location_target->get_feedId(),
      $new_location_target->get_feedItemId(),
      $new_location_target->get_criterion()->get_id());
}

sub create_site_links_feed {
  my ($client, $site_links_data, $feed_name) = @_;

  my $text_attribute = Google::Ads::AdWords::v201809::FeedAttribute->new({
    type => "STRING",
    name => "Link Text"
  });
  my $final_url_attribute = Google::Ads::AdWords::v201809::FeedAttribute->new({
    type => "URL_LIST",
    name => "Link Final URLs"
  });
  my $line_2_attribute = Google::Ads::AdWords::v201809::FeedAttribute->new({
    type => "STRING",
    name => "Line 2"
  });
  my $line_3_attribute = Google::Ads::AdWords::v201809::FeedAttribute->new({
    type => "STRING",
    name => "Line 3"
  });
  my $feed = Google::Ads::AdWords::v201809::Feed->new({
      name       => $feed_name,
      attributes => [
        $text_attribute,   $final_url_attribute,
        $line_2_attribute, $line_3_attribute
      ],
      origin => "USER"
    });

  my $operation = Google::Ads::AdWords::v201809::FeedOperation->new({
    operator => "ADD",
    operand  => $feed
  });

  my $feed_result =
    $client->FeedService()->mutate({operations => [$operation]});

  my $saved_feed = $feed_result->get_value(0);

  $site_links_data->{"siteLinksFeedId"} = $saved_feed->get_id();

  my $saved_attributes = $saved_feed->get_attributes();

  $site_links_data->{"linkTextFeedAttributeId"} =
    $saved_attributes->[0]->get_id();
  $site_links_data->{"linkFinalUrlFeedAttributeId"} =
    $saved_attributes->[1]->get_id();
  $site_links_data->{"line2FeedAttributeId"} = $saved_attributes->[2]->get_id();
  $site_links_data->{"line3FeedAttributeId"} = $saved_attributes->[3]->get_id();
  printf(
    "Feed with name '%s' and ID %d with linkTextAttributeId %d" .
      " and linkFinalUrlAttributeId %d and line2AttributeId %d" .
      " and line3AttributeId %d was created.\n",
    $saved_feed->get_name(),          $saved_feed->get_id(),
    $saved_attributes->[0]->get_id(), $saved_attributes->[1]->get_id(),
    $saved_attributes->[2]->get_id(), $saved_attributes->[3]->get_id());
}

sub create_site_links_feed_items {
  my ($client, $site_links_data) = @_;

  my @operations = ();

  push @operations,
    create_feed_item_add_operation($site_links_data,
    "Home", "http://www.example.com", "Home line 2", "Home line 3");
  push @operations,
    create_feed_item_add_operation(
    $site_links_data, "Stores",
    "http://www.example.com/stores",
    "Stores line 2",
    "Stores line 3"
    );
  push @operations,
    create_feed_item_add_operation(
    $site_links_data, "On Sale", "http://www.example.com/sale",
    "On Sale line 2",
    "On Sale line 3"
    );
  push @operations,
    create_feed_item_add_operation(
    $site_links_data, "Support",
    "http://www.example.com/support",
    "Support line 2",
    "Support line 3"
    );
  push @operations,
    create_feed_item_add_operation(
    $site_links_data, "Products",
    "http://www.example.com/prods",
    "Products line 2",
    "Products line 3"
    );
  # This site link is using geographical targeting to use LOCATION_OF_PRESENCE.
  push @operations,
    create_feed_item_add_operation(
    $site_links_data, "About Us",
    "http://www.example.com/about",
    "About Us line 2",
    "About Us line 3", "1"
    );

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

  foreach my $feed_item (@{$result->get_value()}) {
    printf "FeedItem with feedItemId %d was added.\n",
      $feed_item->get_feedItemId();
    push @{$site_links_data->{"feedItemIds"}}, $feed_item->get_feedItemId();
  }

  # Target the "aboutUs" sitelink to geographically target California.
  # See https://developers.google.com/adwords/api/docs/appendix/geotargeting
  # for location criteria for supported locations.
  restrict_feed_item_to_geo_target($client, $result->get_value()->[5], "21137");
}

sub create_feed_item_add_operation {
  my ($site_links_data, $text, $final_url, $line_2, $line_3,
      $restrict_to_lop) = @_;

  my $text_attribute_value =
    Google::Ads::AdWords::v201809::FeedItemAttributeValue->new({
      feedAttributeId => $site_links_data->{"linkTextFeedAttributeId"},
      stringValue     => $text
    });
  my $final_url_attribute_value =
    Google::Ads::AdWords::v201809::FeedItemAttributeValue->new({
      feedAttributeId => $site_links_data->{"linkFinalUrlFeedAttributeId"},
      stringValues    => [$final_url]});
  my $line_2_attribute_value =
    Google::Ads::AdWords::v201809::FeedItemAttributeValue->new({
      feedAttributeId => $site_links_data->{"line2FeedAttributeId"},
      stringValue     => $line_2
    });
  my $line_3_attribute_value =
    Google::Ads::AdWords::v201809::FeedItemAttributeValue->new({
      feedAttributeId => $site_links_data->{"line3FeedAttributeId"},
      stringValue     => $line_3
    });

  my $feed_item = Google::Ads::AdWords::v201809::FeedItem->new({
      feedId          => $site_links_data->{"siteLinksFeedId"},
      attributeValues => [
        $text_attribute_value,   $final_url_attribute_value,
        $line_2_attribute_value, $line_3_attribute_value
      ]});

  # OPTIONAL: Restrict targeting only to people physically within the location.
  if ($restrict_to_lop) {
    my $geo_targeting_restriction =
      Google::Ads::AdWords::v201809::FeedItemGeoRestriction->new({
        geoRestriction => "LOCATION_OF_PRESENCE"
      });
    $feed_item->set_geoTargetingRestriction($geo_targeting_restriction);
  }

  my $operation = Google::Ads::AdWords::v201809::FeedItemOperation->new({
    operand  => $feed_item,
    operator => "ADD"
  });

  return $operation;
}

sub create_site_links_feed_mapping {
  my ($client, $site_links_data) = @_;

  # Map the FeedAttributeIds to the fieldId constants.
  my $text_field_mapping =
    Google::Ads::AdWords::v201809::AttributeFieldMapping->new({
      feedAttributeId => $site_links_data->{"linkTextFeedAttributeId"},
      fieldId         => PLACEHOLDER_FIELD_SITELINK_LINK_TEXT
    });
  my $final_url_field_mapping =
    Google::Ads::AdWords::v201809::AttributeFieldMapping->new({
      feedAttributeId => $site_links_data->{"linkFinalUrlFeedAttributeId"},
      fieldId         => PLACEHOLDER_FIELD_SITELINK_FINAL_URLS
    });
  my $line_2_field_mapping =
    Google::Ads::AdWords::v201809::AttributeFieldMapping->new({
      feedAttributeId => $site_links_data->{"line2FeedAttributeId"},
      fieldId         => PLACEHOLDER_FIELD_SITELINK_LINE_2_TEXT
    });
  my $line_3_field_mapping =
    Google::Ads::AdWords::v201809::AttributeFieldMapping->new({
      feedAttributeId => $site_links_data->{"line3FeedAttributeId"},
      fieldId         => PLACEHOLDER_FIELD_SITELINK_LINE_3_TEXT
    });

  # Create the FeedMapping and operation.
  my $feed_mapping = Google::Ads::AdWords::v201809::FeedMapping->new({
      placeholderType        => PLACEHOLDER_SITELINKS,
      feedId                 => $site_links_data->{"siteLinksFeedId"},
      attributeFieldMappings => [
        $text_field_mapping,   $final_url_field_mapping,
        $line_2_field_mapping, $line_3_field_mapping
      ]});

  my $operation = Google::Ads::AdWords::v201809::FeedMappingOperation->new({
    operand  => $feed_mapping,
    operator => "ADD"
  });

  # Save the feed mapping.
  my $result =
    $client->FeedMappingService()->mutate({operations => [$operation]});

  foreach my $saved_feed_mapping (@{$result->get_value()}) {
    printf "Feed mapping with ID %d and placeholderType %d was saved for " .
      "feed with ID %d.\n", $saved_feed_mapping->get_feedMappingId(),
      $saved_feed_mapping->get_placeholderType(),
      $saved_feed_mapping->get_feedId();
  }
}

sub create_site_links_campaign_feed {
  my ($client, $site_links_data, $campaign_id) = @_;

  # Construct a matching function that assoicates the sitelink feed items
  # to the campaign, and sets the device preference to mobile.
  # See the matching function guide at
  # https://developers.google.com/adwords/api/docs/guides/feed-matching-functions
  # for more details.
  my $matching_function_string =
    sprintf("AND( IN(FEED_ITEM_ID, {%s}), EQUALS(CONTEXT.DEVICE, 'Mobile') )",
    (join ' ,', @{$site_links_data->{"feedItemIds"}}));

  my $matching_function = Google::Ads::AdWords::v201809::Function->new(
    {functionString => $matching_function_string});

  my $campaign_feed = Google::Ads::AdWords::v201809::CampaignFeed->new({
      feedId           => $site_links_data->{"siteLinksFeedId"},
      campaignId       => $campaign_id,
      matchingFunction => $matching_function,
      # Specifying placeholder types on the CampaignFeed allows the same feed
      # to be used for different placeholders in different Campaigns.
      placeholderTypes => [PLACEHOLDER_SITELINKS]});

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

  $campaign_feed = $result->get_value(0);

  printf "Campaign with ID %d was associated with feed with ID %d.\n",
    $campaign_feed->get_campaignId(), $campaign_feed->get_feedId();

  # Optional: Restrict the first feed item to only service with ads for the
  # specified ad group ID.
  if ($ad_group_id) {
    my $feed_item_target =
      Google::Ads::AdWords::v201809::FeedItemAdGroupTarget->new({
        feedId => $site_links_data->{"siteLinksFeedId"},
        feedItemId => $site_links_data->{"feedItemIds"}->[0],
        adGroupId => $ad_group_id
      });

    my $result = $client->FeedItemTargetService()->mutate({
        operations => [
          Google::Ads::AdWords::v201809::FeedItemTargetOperation->new({
              operand  => $feed_item_target,
              operator => "ADD"
            })]});

    $feed_item_target = $result->get_value(0);

    printf("Feed item target for feed ID %d and feed item ID %d" .
           " was created to restrict serving to ad group ID %d\n",
           $feed_item_target->get_feedId(),
           $feed_item_target->get_feedItemId(),
           $feed_item_target->get_adGroupId());
  }
}

# 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
add_site_links_using_feeds($client, $campaign_id, $ad_group_id, $feed_name);