Affiliate location extensions

To use affiliate location extensions, you need to create a new affiliate location extensions feed in your account. Perform a FeedService.MutateFeeds create operation to create the feed:

C#

private static string CreateAffiliateLocationExtensionFeed(GoogleAdsClient client,
    long customerId, long chainId)
{
    // Optional: Delete all existing location extension feeds. This is an optional step,
    // and is required for this code example to run correctly more than once.
    // 1. Google Ads only allows one location extension feed per email address.
    // 2. A Google Ads account cannot have a location extension feed and an affiliate
    // location extension feed at the same time.
    DeleteLocationExtensionFeeds(client, customerId);

    // Get the FeedServiceClient.
    FeedServiceClient feedService = client.GetService(Services.V5.FeedService);

    // Creates a feed that will sync to retail addresses for a given retail chain ID.
    // Do not add FeedAttributes to this object as Google Ads will add
    // them automatically because this will be a system generated feed.
    Feed feed = new Feed()
    {
        Name = "Affiliate Location Extension feed #" + ExampleUtilities.GetRandomString(),

        AffiliateLocationFeedData = new AffiliateLocationFeedData()
        {
            ChainIds = { chainId },
            RelationshipType = AffiliateLocationFeedRelationshipType.GeneralRetailer
        },
        // Since this feed's contents will be managed by Google,
        // you must set its origin to GOOGLE.
        Origin = FeedOrigin.Google
    };

    FeedOperation operation = new FeedOperation()
    {
        Create = feed
    };

    // Adds the feed.
    MutateFeedsResponse response =
        feedService.MutateFeeds(customerId.ToString(), new[] { operation });

    // Displays the results.
    string feedResourceName = response.Results[0].ResourceName;
    Console.WriteLine($"Affliate location extension feed created with resource name: " +
        $"{feedResourceName}.");
    return feedResourceName;
}

PHP

private static function createAffiliateLocationExtensionFeed(
    GoogleAdsClient $googleAdsClient,
    int $customerId,
    int $chainId
): string {
    // Creates a feed that will sync to retail addresses for a given retail chain ID.
    // Do not add feed attributes, Google Ads will add them automatically because this will
    // be a system generated feed.
    $feed = new Feed([
        'name' => new StringValue([
            'value' => 'Affiliate Location Extension feed #' . uniqid()
        ]),
        'affiliate_location_feed_data' => new AffiliateLocationFeedData([
            'chain_ids' => [
                new Int64Value(['value' => $chainId])
            ],
            'relationship_type' => AffiliateLocationFeedRelationshipType::GENERAL_RETAILER
        ]),
        // Since this feed's contents will be managed by Google, you must set its origin to
        // GOOGLE.
        'origin' => FeedOrigin::GOOGLE
    ]);

    // Creates the feed operation.
    $operation = new FeedOperation();
    $operation->setCreate($feed);

    // Issues a mutate request to add the feed and prints some information.
    $feedServiceClient = $googleAdsClient->getFeedServiceClient();
    $response = $feedServiceClient->mutateFeeds($customerId, [$operation]);
    $feedResourceName = $response->getResults()[0]->getResourceName();
    printf(
        "Affiliate location extension feed created with resource name: '%s'.%s",
        $feedResourceName,
        PHP_EOL
    );

    return $feedResourceName;
}

Perl

sub create_affiliate_location_extension_feed {
  my ($api_client, $customer_id, $chain_id) = @_;

  # Create a feed that will sync to retail addresses for a given retail chain ID.
  # Do not add feed attributes, Google Ads will add them automatically because
  # this will be a system generated feed.
  my $feed = Google::Ads::GoogleAds::V5::Resources::Feed->new({
      name => "Affiliate Location Extension feed #" . uniqid(),
      affiliateLocationFeedData =>
        Google::Ads::GoogleAds::V5::Resources::AffiliateLocationFeedData->new({
          chainIds         => [$chain_id],
          relationshipType => GENERAL_RETAILER
        }
        ),
      # Since this feed's contents will be managed by Google, you must set its
      # origin to GOOGLE.
      origin => GOOGLE
    });

  # Create the feed operation.
  my $operation =
    Google::Ads::GoogleAds::V5::Services::FeedService::FeedOperation->new({
      create => $feed
    });

  # Issue a mutate request to add the feed and print some information.
  my $response = $api_client->FeedService()->mutate({
      customerId => $customer_id,
      operations => [$operation]});
  my $feed_resource_name = $response->{results}[0]{resourceName};
  printf
    "Affiliate location extension feed created with resource name: '%s'.\n",
    $feed_resource_name;

  return $feed_resource_name;
}

You need to populate the following fields that are specific to location extensions feeds:

Attribute Required Description
origin Yes Since the location extensions feed is system-generated, you need to set the origin field to GOOGLE.
attributes No Do not specify any attributes for the feed. Google Ads will create these for you automatically because this is a system-generated feed.
affiliate_location_feed_data No Setting the affiliate_location_feed_data attribute to a AffiliateLocationFeedData object on your feed tells Google Ads to:
  • Automatically create feed attributes for your feed.
  • Automatically create a FeedMapping for your feed.
  • Populate the feed with a list of locations that corresponds to the retail chains that you specified in the chain_ids attribute.

Attributes of AffiliateLocationFeedData

Attribute Required Description
chain_ids Yes The list of chains that you wish to advertise. See the list of valid chain IDs.
relationship_type Yes The relationship type between the advertiser and retail chains.

Wait for the feed to be ready

Once the Feed is created, Google Ads will create the feed attributes and FeedMapping. Afterwards, it will populate the feed contents by creating the FeedItem objects that correspond to the locations in the GMB account.

You need to wait until the FeedMapping is created to ensure that the feed is properly set up, and that it can be used for the next steps. This can be done by attempting to retrieve the FeedMapping for the relevant feed with placeholder_type equal to AFFILIATE_LOCATION.

C#

private static FeedMapping GetAffiliateLocationExtensionFeedMapping(GoogleAdsClient client,
    long customerId, string feedResourceName)
{
    // Get the GoogleAdsService.
    GoogleAdsServiceClient googleAdsService = client.GetService(
        Services.V5.GoogleAdsService);

    // Create the query.
    string query = $"SELECT feed_mapping.resource_name, " +
        $"feed_mapping.attribute_field_mappings, feed_mapping.status FROM " +
        $"feed_mapping WHERE feed_mapping.feed = '{feedResourceName}' and " +
        $"feed_mapping.status = ENABLED and feed_mapping.placeholder_type = " +
        "AFFILIATE_LOCATION LIMIT 1";

    // Issue a search request.
    PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
        googleAdsService.Search(customerId.ToString(), query);

    // Display the results.
    GoogleAdsRow googleAdsRow = result.FirstOrDefault();
    return (googleAdsRow == null) ? null : googleAdsRow.FeedMapping;
}

PHP

private static function getAffiliateLocationExtensionFeedMapping(
    GoogleAdsClient $googleAdsClient,
    int $customerId,
    string $feedResourceName
): ?FeedMapping {
    $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
    // Creates a query that retrieves the feed mapping.
    $query = sprintf(
        "SELECT feed_mapping.resource_name, " .
        "feed_mapping.attribute_field_mappings, " .
        "feed_mapping.status " .
        "FROM feed_mapping " .
        "WHERE feed_mapping.feed = '%s' " .
        "AND feed_mapping.status = ENABLED " .
        "AND feed_mapping.placeholder_type = AFFILIATE_LOCATION " .
        "LIMIT 1",
        $feedResourceName
    );

    // Issues a search request.
    $response = $googleAdsServiceClient->search(
        $customerId,
        $query,
        ['returnTotalResultsCount' => true]
    );

    return $response->getPage()->getPageElementCount() === 1
        ? $response->getIterator()->current()->getFeedMapping()
        : null;
}

Perl

sub get_affiliate_location_extension_feed_mapping {
  my ($api_client, $customer_id, $feed_resource_name) = @_;

  # Create a query that retrieves the feed mapping.
  my $search_query =
    "SELECT feed_mapping.resource_name, " .
    "feed_mapping.attribute_field_mappings, " .
    "feed_mapping.status FROM feed_mapping " .
    "WHERE feed_mapping.feed = '$feed_resource_name' " .
    "AND feed_mapping.status = ENABLED " .
    "AND feed_mapping.placeholder_type = AFFILIATE_LOCATION LIMIT 1";

  # Issue a search request.
  my $response = $api_client->GoogleAdsService()->search({
    customerId              => $customer_id,
    query                   => $search_query,
    returnTotalResultsCount => "true"
  });

  return $response->{totalResultsCount} && $response->{totalResultsCount} == 1
    ? $response->{results}[0]{feedMapping}
    : undef;
}

If the FeedMapping is not yet available, retry the calls with an exponential back-off policy until the Feed is ready.

C#

private static FeedMapping WaitForFeedToBeReady(GoogleAdsClient client, long customerId,
    string feedResourceName)
{
    int numAttempts = 0;
    int sleepSeconds = 0;

    while (numAttempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS)
    {
        // Once you create a feed, Google's servers will setup the feed by creating feed
        // attributes and feed mapping. Once the feed mapping is created, it is ready to be
        // used for creating customer feed.
        // This process is asynchronous, so we wait until the feed mapping is created,
        // peforming exponential backoff.
        FeedMapping feedMapping = GetAffiliateLocationExtensionFeedMapping(
            client, customerId, feedResourceName);

        if (feedMapping == null)
        {
            numAttempts++;
            sleepSeconds = (int)(5 * Math.Pow(2, numAttempts));
            Console.WriteLine($"Checked: #{numAttempts} time(s). Feed is not ready " +
                $"yet. Waiting {sleepSeconds} seconds before trying again.");
            Thread.Sleep(sleepSeconds * 1000);
        }
        else
        {
            Console.WriteLine($"Feed {feedResourceName} is now ready.");
            return feedMapping;
        }
    }
    throw new RpcException(new Status(StatusCode.DeadlineExceeded,
        $"Feed is not ready after {MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS}" +
        $" retries."));
}

PHP

private static function waitForFeedToBeReady(
    GoogleAdsClient $googleAdsClient,
    int $customerId,
    string $feedResourceName
): FeedMapping {
    $numAttempts = 0;

    while ($numAttempts < self::MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS) {
        // Once you create a feed, Google's servers will setup the feed by creating feed
        // attributes and feed mapping. Once the feed mapping is created, it is ready to be
        // used for creating customer feed.
        // This process is asynchronous, so we wait until the feed mapping is created,
        // performing exponential backoff.
        $feedMapping = self::getAffiliateLocationExtensionFeedMapping(
            $googleAdsClient,
            $customerId,
            $feedResourceName
        );

        if (is_null($feedMapping)) {
            $numAttempts++;
            $sleepSeconds = intval(5 * pow(2, $numAttempts));
            printf(
                'Checked: %d time(s). Feed is not ready yet. ' .
                'Waiting %d seconds before trying again.%s',
                $numAttempts,
                $sleepSeconds,
                PHP_EOL
            );
            sleep($sleepSeconds);
        } else {
            printf("Feed '%s' is now ready.%s", $feedResourceName, PHP_EOL);
            return $feedMapping;
        }
    }

    throw new RuntimeException(sprintf(
        "The affiliate location feed mapping is still not ready after %d attempt(s).%s",
        self::MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS,
        PHP_EOL
    ));
}

Perl

sub wait_for_feed_to_be_ready {
  my ($api_client, $customer_id, $feed_resource_name) = @_;

  my $num_attempts = 0;
  while ($num_attempts < MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS) {
    # Once you create a feed, Google's servers will setup the feed by creating
    # feed attributes and feed mapping. Once the feed mapping is created, it is
    # ready to be used for creating customer feed.
    # This process is asynchronous, so we wait until the feed mapping is created,
    # performing exponential backoff.
    my $feed_mapping =
      get_affiliate_location_extension_feed_mapping($api_client, $customer_id,
      $feed_resource_name);

    if (!$feed_mapping) {
      $num_attempts++;
      my $sleep_seconds = 5 * (2**$num_attempts);
      printf "Checked: %d time(s). Feed is not ready yet. " .
        "Waiting %d seconds before trying again.\n",
        $num_attempts,
        $sleep_seconds;
      sleep($sleep_seconds);
    } else {
      printf "Feed '%s' is now ready.\n", $feed_resource_name;
      return $feed_mapping;
    }
  }

  die(
    sprintf "The affiliate location feed mapping is still not ready " .
      "after %d attempt(s).\n",
    MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS
  );
}

Associate the feed with the customer, campaign, or ad group

Once the feed is ready to use, you can create a CampaignFeed object to associate it with a campaign. Associating the feed to an ad group or the customer is similar, except that you'd create an AdGroupFeed or CustomerFeed object, respectively, and use an appropriate matching function.

The following code snippet filters the affiliate location extensions for a campaign to serve only locations from a single retail chain ID.

C#

private static void CreateCampaignFeed(GoogleAdsClient client, long customerId,
    long campaignId, FeedMapping feedMapping, string feedResourceName, long chainId)
{
    // Get the CampaignFeedService.
    CampaignFeedServiceClient campaignFeedService = client.GetService(
        Services.V5.CampaignFeedService);

    long attributeIdForChainId = GetAttributeIdForChainId(feedMapping);
    string feedId = FeedName.Parse(feedResourceName).FeedId;

    string matchingFunction =
        $"IN(FeedAttribute[{feedId}, {attributeIdForChainId}], {chainId})";
    // Adds a CampaignFeed that associates the feed with this campaign for
    // the AFFILIATE_LOCATION placeholder type.
    CampaignFeed campaignFeed = new CampaignFeed()
    {
        Feed = feedResourceName,
        PlaceholderTypes = { PlaceholderType.AffiliateLocation },
        MatchingFunction = new MatchingFunction()
        {
            FunctionString = matchingFunction
        },
        Campaign = ResourceNames.Campaign(customerId, campaignId),
    };

    CampaignFeedOperation operation = new CampaignFeedOperation()
    {
        Create = campaignFeed
    };

    MutateCampaignFeedsResponse campaignFeedsResponse =
        campaignFeedService.MutateCampaignFeeds(
            customerId.ToString(), new[] { operation });

    // Displays the result.
    string addedCampaignFeed = campaignFeedsResponse.Results[0].ResourceName;
    Console.WriteLine($"Campaign feed created with resource name: {addedCampaignFeed}.");
    return;
}

PHP

private static function createCampaignFeed(
    GoogleAdsClient $googleAdsClient,
    int $customerId,
    int $campaignId,
    FeedMapping $feedMapping,
    string $feedResourceName,
    int $chainId
) {
    $matchingFunction = sprintf(
        'IN(FeedAttribute[%d, %d], %d)',
        FeedServiceClient::parseName($feedResourceName)['feed'],
        self::getAttributeIdForChainId($feedMapping),
        $chainId
    );

    // Adds a campaign feed that associates the feed with this campaign for the
    // AFFILIATE_LOCATION placeholder type.
    $campaignFeed = new CampaignFeed([
        'feed' => new StringValue(['value' => $feedResourceName]),
        'placeholder_types' => [PlaceholderType::AFFILIATE_LOCATION],
        'matching_function' => new MatchingFunction([
            'function_string' => new StringValue(['value' => $matchingFunction])
        ]),
        'campaign' => new StringValue([
            'value' => ResourceNames::forCampaign($customerId, $campaignId)
        ])
    ]);

    // Creates the campaign feed operation.
    $operation = new CampaignFeedOperation();
    $operation->setCreate($campaignFeed);

    // Issues a mutate request to add the campaign feed and prints some information.
    $campaignFeedServiceClient = $googleAdsClient->getCampaignFeedServiceClient();
    $response = $campaignFeedServiceClient->MutateCampaignFeeds($customerId, [$operation]);
    printf(
        "Campaign feed created with resource name: '%s'.%s",
        $response->getResults()[0]->getResourceName(),
        PHP_EOL
    );
}

Perl

sub create_campaign_feed {
  my ($api_client, $customer_id, $campaign_id, $feed_mapping,
    $feed_resource_name, $chain_id)
    = @_;

  my $feed_id                   = $1 if $feed_resource_name =~ /(\d+)$/;
  my $attribute_id_for_chain_id = get_attribute_id_for_chain_id($feed_mapping);
  my $matching_function =
    "IN(FeedAttribute[$feed_id, $attribute_id_for_chain_id], $chain_id)";

  # Add a campaign feed that associates the feed with this campaign for the
  # AFFILIATE_LOCATION placeholder type.
  my $campaign_feed = Google::Ads::GoogleAds::V5::Resources::CampaignFeed->new({
      feed             => $feed_resource_name,
      placeholderTypes => AFFILIATE_LOCATION,
      matchingFunction =>
        Google::Ads::GoogleAds::V5::Common::MatchingFunction->new({
          functionString => $matching_function
        }
        ),
      campaign => Google::Ads::GoogleAds::V5::Utils::ResourceNames::campaign(
        $customer_id, $campaign_id
      )});

  # Create the campaign feed operation.
  my $operation =
    Google::Ads::GoogleAds::V5::Services::CampaignFeedService::CampaignFeedOperation
    ->new({
      create => $campaign_feed
    });

  # Issue a mutate request to add the campaign feed and print some information.
  my $response = $api_client->CampaignFeedService()->mutate({
      customerId => $customer_id,
      operations => [$operation]});

  printf
    "Campaign feed created with resource name: '%s'.\n",
    $response->{results}[0]{resourceName};
}

The feed attribute ID can be retrieved from the feed's FeedMapping as follows:

C#

public static long GetAttributeIdForChainId(FeedMapping feedMapping)
{
    foreach (AttributeFieldMapping fieldMapping in feedMapping.AttributeFieldMappings)
    {
        if (fieldMapping.AffiliateLocationField ==
            AffiliateLocationPlaceholderField.ChainId)
        {
            return fieldMapping.FeedAttributeId.Value;
        }
    }
    throw new ArgumentException("Affiliate location feed mapping isn't setup correctly.");
}

PHP

private static function getAttributeIdForChainId(FeedMapping $feedMapping): int
{
    foreach ($feedMapping->getAttributeFieldMappings() as $fieldMapping) {
        /** @var AttributeFieldMapping $fieldMapping */
        if (
            $fieldMapping->getAffiliateLocationField()
            === AffiliateLocationPlaceholderField::CHAIN_ID
        ) {
            return $fieldMapping->getFeedAttributeIdUnwrapped();
        }
    }

    throw new RuntimeException(
        "Affiliate location feed mapping isn't setup correctly." . PHP_EOL
    );
}

Perl

sub get_attribute_id_for_chain_id {
  my ($feed_mapping) = @_;

  foreach my $field_mapping (@{$feed_mapping->{attributeFieldMappings}}) {
    if ($field_mapping->{affiliateLocationField} eq CHAIN_ID) {
      return $field_mapping->{feedAttributeId};
    }
  }

  die "Affiliate location feed mapping isn't setup correctly.";
}