Add Ad Customizer

Java

// Copyright 2019 Google LLC
//
// 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
//
//     https://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.

package com.google.ads.googleads.examples.advancedoperations;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v6.common.ExpandedTextAdInfo;
import com.google.ads.googleads.v6.enums.AdCustomizerPlaceholderFieldEnum.AdCustomizerPlaceholderField;
import com.google.ads.googleads.v6.enums.FeedAttributeTypeEnum.FeedAttributeType;
import com.google.ads.googleads.v6.enums.PlaceholderTypeEnum.PlaceholderType;
import com.google.ads.googleads.v6.errors.GoogleAdsError;
import com.google.ads.googleads.v6.errors.GoogleAdsException;
import com.google.ads.googleads.v6.resources.Ad;
import com.google.ads.googleads.v6.resources.AdGroupAd;
import com.google.ads.googleads.v6.resources.AttributeFieldMapping;
import com.google.ads.googleads.v6.resources.Feed;
import com.google.ads.googleads.v6.resources.FeedAttribute;
import com.google.ads.googleads.v6.resources.FeedItem;
import com.google.ads.googleads.v6.resources.FeedItemAttributeValue;
import com.google.ads.googleads.v6.resources.FeedItemTarget;
import com.google.ads.googleads.v6.resources.FeedMapping;
import com.google.ads.googleads.v6.services.AdGroupAdOperation;
import com.google.ads.googleads.v6.services.AdGroupAdServiceClient;
import com.google.ads.googleads.v6.services.FeedItemOperation;
import com.google.ads.googleads.v6.services.FeedItemServiceClient;
import com.google.ads.googleads.v6.services.FeedItemTargetOperation;
import com.google.ads.googleads.v6.services.FeedItemTargetServiceClient;
import com.google.ads.googleads.v6.services.FeedMappingOperation;
import com.google.ads.googleads.v6.services.FeedMappingServiceClient;
import com.google.ads.googleads.v6.services.FeedOperation;
import com.google.ads.googleads.v6.services.FeedServiceClient;
import com.google.ads.googleads.v6.services.GoogleAdsServiceClient;
import com.google.ads.googleads.v6.services.GoogleAdsServiceClient.SearchPagedResponse;
import com.google.ads.googleads.v6.services.MutateAdGroupAdResult;
import com.google.ads.googleads.v6.services.MutateAdGroupAdsResponse;
import com.google.ads.googleads.v6.services.MutateFeedItemResult;
import com.google.ads.googleads.v6.services.MutateFeedItemTargetsResponse;
import com.google.ads.googleads.v6.services.MutateFeedItemsResponse;
import com.google.ads.googleads.v6.services.MutateFeedMappingsResponse;
import com.google.ads.googleads.v6.services.MutateFeedsResponse;
import com.google.ads.googleads.v6.services.SearchGoogleAdsRequest;
import com.google.ads.googleads.v6.utils.ResourceNames;
import com.google.common.collect.ImmutableList;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;

/**
 * Adds an ad customizer feed and associates it with the customer. Then it adds an ad that uses the
 * feed to populate dynamic data.
 */
public class AddAdCustomizer {

  // We're doing only searches by resource_name in this example, we can set page size = 1.
  private static final int PAGE_SIZE = 1;

  // We're creating two different ad groups to be dynamically populated by the same feed.
  private static final int NUMBER_OF_AD_GROUPS = 2;

  private static class AddAdCustomizerParams extends CodeSampleParams {

    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private Long customerId;

    @Parameter(names = ArgumentNames.AD_GROUP_IDS, required = true)
    private List<Long> adGroupIds;
  }

  public static void main(String[] args) throws IOException {
    AddAdCustomizerParams params = new AddAdCustomizerParams();
    if (!params.parseArguments(args)) {

      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
      params.adGroupIds =
          Arrays.asList(
              Long.parseLong("INSERT_AD_GROUP_ID_HERE"), Long.parseLong("INSERT_AD_GROUP_ID_HERE"));
    }

    GoogleAdsClient googleAdsClient = null;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      System.exit(1);
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      System.exit(1);
    }

    try {
      new AddAdCustomizer().runExample(googleAdsClient, params);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
      System.exit(1);
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param params the example parameters.
   * @throws GoogleAdsException if an API request failed with one or more service errors.
   */
  private void runExample(GoogleAdsClient googleAdsClient, AddAdCustomizerParams params) {

    if (params.adGroupIds.size() != NUMBER_OF_AD_GROUPS) {
      throw new IllegalArgumentException(
          "Please pass exactly two ad group IDs in the adGroupId parameter.");
    }

    String feedName = "Ad Customizer example feed " + System.currentTimeMillis();

    // Create a feed to be used as the ad customizer.
    String adCustomizerFeedResourceName =
        createAdCustomizerFeed(googleAdsClient, params.customerId, feedName);

    // Retrieve the attributes for the newly created feed.
    Map<String, FeedAttribute> adCustomizerFeedAttributes =
        getFeedAttributes(googleAdsClient, params.customerId, adCustomizerFeedResourceName);

    // Map the feed to the ad customizer placeholder type to mark it as an ad customizer.
    createAdCustomizerMapping(
        googleAdsClient,
        params.customerId,
        adCustomizerFeedResourceName,
        adCustomizerFeedAttributes);

    // Create the feed items that will fill the placeholders in the ads customized by the feed.
    List<String> feedItemResourceNames =
        createFeedItems(
            googleAdsClient,
            params.customerId,
            adCustomizerFeedResourceName,
            adCustomizerFeedAttributes);

    // Create a feed item targeting to associate the feed items with specific ad groups to
    // prevent them from being used in other ways.
    createFeedItemTargets(
        googleAdsClient, params.customerId, params.adGroupIds, feedItemResourceNames);

    // Create ads with the customizations provided by the feed items.
    createAdsWithCustomizations(googleAdsClient, params.customerId, params.adGroupIds, feedName);
  }

  /**
   * Creates a feed to be used for ad customization.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param feedName the name of the feed to create.
   * @return the resource name of the newly created feed.
   */
  private String createAdCustomizerFeed(
      GoogleAdsClient googleAdsClient, long customerId, String feedName) {

    // Creates three feed attributes: a name, a price and a date. The attribute names are arbitrary
    // choices and will be used as placeholders in the ad text fields.
    FeedAttribute nameAttribute =
        FeedAttribute.newBuilder().setName("Name").setType(FeedAttributeType.STRING).build();

    FeedAttribute priceAttribute =
        FeedAttribute.newBuilder().setName("Price").setType(FeedAttributeType.STRING).build();

    FeedAttribute dateAttribute =
        FeedAttribute.newBuilder().setName("Date").setType(FeedAttributeType.DATE_TIME).build();

    Feed adCustomizerFeed =
        Feed.newBuilder()
            .setName(feedName)
            .addAttributes(nameAttribute)
            .addAttributes(priceAttribute)
            .addAttributes(dateAttribute)
            .build();

    FeedOperation feedOperation = FeedOperation.newBuilder().setCreate(adCustomizerFeed).build();

    try (FeedServiceClient feedServiceClient =
        googleAdsClient.getLatestVersion().createFeedServiceClient()) {

      MutateFeedsResponse response =
          feedServiceClient.mutateFeeds(Long.toString(customerId), ImmutableList.of(feedOperation));

      String feedResourceName = response.getResults(0).getResourceName();
      System.out.printf("Added feed with resource name %s.%n", feedResourceName);
      return feedResourceName;
    }
  }

  /**
   * Retrieves all the attributes for a feed and returns them in a map using the attribute names as
   * keys.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param feedResourceName the resource name of the feed.
   * @return the attributes of the feed.
   */
  private Map<String, FeedAttribute> getFeedAttributes(
      GoogleAdsClient googleAdsClient, long customerId, String feedResourceName) {
    String query =
        String.format(
            "SELECT feed.attributes, feed.name FROM feed WHERE feed.resource_name = '%s'",
            feedResourceName);

    SearchGoogleAdsRequest request =
        SearchGoogleAdsRequest.newBuilder()
            .setCustomerId(Long.toString(customerId))
            .setPageSize(PAGE_SIZE)
            .setQuery(query)
            .build();

    Map<String, FeedAttribute> feedAttributes = new HashMap<>();
    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      SearchPagedResponse searchPagedResponse = googleAdsServiceClient.search(request);

      Feed feed = searchPagedResponse.iterateAll().iterator().next().getFeed();

      System.out.printf(
          "Found the following attributes for feed with name '%s':%n", feed.getName());
      for (FeedAttribute feedAttribute : feed.getAttributesList()) {
        System.out.printf(
            "\t'%s' with id %d and type '%s'%n",
            feedAttribute.getName(), feedAttribute.getId(), feedAttribute.getType());
        feedAttributes.put(feedAttribute.getName(), feedAttribute);
      }
    }
    return feedAttributes;
  }

  /**
   * Creates a feed mapping and sets the feed as an ad customizer feed.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param feedResourceName the resource name of the feed.
   * @param feedAttributes the attributes of the feed.
   */
  private void createAdCustomizerMapping(
      GoogleAdsClient googleAdsClient,
      long customerId,
      String feedResourceName,
      Map<String, FeedAttribute> feedAttributes) {

    // Map the feed attributes to ad customizer placeholder fields.
    // For a full list of ad customizer placeholder fields, see
    // https://developers.google.com/google-ads/api/reference/rpc/latest/AdCustomizerPlaceholderFieldEnum.AdCustomizerPlaceholderField
    AttributeFieldMapping nameFieldMapping =
        AttributeFieldMapping.newBuilder()
            .setFeedAttributeId(feedAttributes.get("Name").getId())
            .setAdCustomizerField(AdCustomizerPlaceholderField.STRING)
            .build();

    AttributeFieldMapping priceFieldMapping =
        AttributeFieldMapping.newBuilder()
            .setFeedAttributeId(feedAttributes.get("Price").getId())
            .setAdCustomizerField(AdCustomizerPlaceholderField.PRICE)
            .build();

    AttributeFieldMapping dateFieldMapping =
        AttributeFieldMapping.newBuilder()
            .setFeedAttributeId(feedAttributes.get("Date").getId())
            .setAdCustomizerField(AdCustomizerPlaceholderField.DATE)
            .build();

    FeedMapping feedMapping =
        FeedMapping.newBuilder()
            .setFeed(feedResourceName)
            // Sets the feed to the AD_CUSTOMIZER placeholder type.
            .setPlaceholderType(PlaceholderType.AD_CUSTOMIZER)
            .addAttributeFieldMappings(nameFieldMapping)
            .addAttributeFieldMappings(priceFieldMapping)
            .addAttributeFieldMappings(dateFieldMapping)
            .build();

    FeedMappingOperation feedMappingOperation =
        FeedMappingOperation.newBuilder().setCreate(feedMapping).build();

    try (FeedMappingServiceClient feedMappingServiceClient =
        googleAdsClient.getLatestVersion().createFeedMappingServiceClient()) {

      MutateFeedMappingsResponse response =
          feedMappingServiceClient.mutateFeedMappings(
              Long.toString(customerId), ImmutableList.of(feedMappingOperation));

      System.out.printf(
          "Added feed mapping with resource name %s.%n", response.getResults(0).getResourceName());
    }
  }

  /**
   * Creates two different feed items to enable two different ad customizations.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param feedResourceName the resource name of the feed.
   * @param feedAttributes the attributes of the feed.
   * @return the resource names of the feed items.
   */
  private List<String> createFeedItems(
      GoogleAdsClient googleAdsClient,
      long customerId,
      String feedResourceName,
      Map<String, FeedAttribute> feedAttributes) {

    List<FeedItemOperation> feedItemOperations = new ArrayList<>();

    DateTime marsDate = DateTime.now().withDayOfMonth(1).withHourOfDay(0).withMinuteOfHour(0);
    feedItemOperations.add(
        createFeedItemOperation(
            "Mars",
            "$1234.56",
            marsDate.toString("yyyyMMdd HHmmss"),
            feedResourceName,
            feedAttributes));

    DateTime venusDate = DateTime.now().withDayOfMonth(15).withHourOfDay(0).withMinuteOfHour(0);
    feedItemOperations.add(
        createFeedItemOperation(
            "Venus",
            "$1450.00",
            venusDate.toString("yyyyMMdd HHmmss"),
            feedResourceName,
            feedAttributes));

    try (FeedItemServiceClient feedItemServiceClient =
        googleAdsClient.getLatestVersion().createFeedItemServiceClient()) {
      List<String> feedItemResourceNames = new ArrayList<>();
      MutateFeedItemsResponse response =
          feedItemServiceClient.mutateFeedItems(Long.toString(customerId), feedItemOperations);

      System.out.printf("Added %d feed items:%n", response.getResultsCount());

      for (MutateFeedItemResult result : response.getResultsList()) {
        String feedItemResourceName = result.getResourceName();
        feedItemResourceNames.add(feedItemResourceName);
        System.out.printf("Added feed item with resource name %s.%n", feedItemResourceName);
      }
      return feedItemResourceNames;
    }
  }

  /**
   * Helper function to create a FeedItemOperation.
   *
   * @param name the value of the Name attribute.
   * @param price the value of the Price attribute.
   * @param date the value of the Date attribute.
   * @param feedResourceName the resource name of the feed.
   * @param feedAttributes the attributes to be set on the feed.
   * @return a FeedItemOperation to create a feed item.
   */
  private FeedItemOperation createFeedItemOperation(
      String name,
      String price,
      String date,
      String feedResourceName,
      Map<String, FeedAttribute> feedAttributes) {
    FeedItemAttributeValue nameAttributeValue =
        FeedItemAttributeValue.newBuilder()
            .setFeedAttributeId(feedAttributes.get("Name").getId())
            .setStringValue(name)
            .build();

    FeedItemAttributeValue priceAttributeValue =
        FeedItemAttributeValue.newBuilder()
            .setFeedAttributeId(feedAttributes.get("Price").getId())
            .setStringValue(price)
            .build();

    FeedItemAttributeValue dateAttributeValue =
        FeedItemAttributeValue.newBuilder()
            .setFeedAttributeId(feedAttributes.get("Date").getId())
            .setStringValue(date)
            .build();

    FeedItem feedItem =
        FeedItem.newBuilder()
            .setFeed(feedResourceName)
            .addAttributeValues(nameAttributeValue)
            .addAttributeValues(priceAttributeValue)
            .addAttributeValues(dateAttributeValue)
            .build();

    return FeedItemOperation.newBuilder().setCreate(feedItem).build();
  }

  /**
   * Restricts the feed items to work only with a specific ad group; this prevents the feed items
   * from being used elsewhere and makes sure they are used only for customizing a specific ad
   * group.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param adGroupIds the ad group IDs to bind the feed items to.
   * @param feedItemResourceNames the resource names of the feed items.
   */
  private void createFeedItemTargets(
      GoogleAdsClient googleAdsClient,
      long customerId,
      List<Long> adGroupIds,
      List<String> feedItemResourceNames) {

    // Bind each feed item to a specific ad group to make sure it will only be used to customize
    // ads inside that ad group; using the feed item elsewhere will result in an error.
    for (int i = 0; i < feedItemResourceNames.size(); i++) {
      String feedItemResourceName = feedItemResourceNames.get(i);
      Long adGroupId = adGroupIds.get(i);

      FeedItemTarget feedItemTarget =
          FeedItemTarget.newBuilder()
              .setAdGroup(ResourceNames.adGroup(customerId, adGroupId))
              .setFeedItem(feedItemResourceName)
              .build();

      FeedItemTargetOperation feedItemTargetOperation =
          FeedItemTargetOperation.newBuilder().setCreate(feedItemTarget).build();

      try (FeedItemTargetServiceClient feedItemTargetServiceClient =
          googleAdsClient.getLatestVersion().createFeedItemTargetServiceClient()) {

        MutateFeedItemTargetsResponse response =
            feedItemTargetServiceClient.mutateFeedItemTargets(
                Long.toString(customerId), ImmutableList.of(feedItemTargetOperation));

        String feedItemTargetResourceName = response.getResults(0).getResourceName();
        System.out.printf(
            "Added feed item target with resource name '%s'.%n", feedItemTargetResourceName);
      }
    }
  }

  /**
   * Creates expanded text ads that use the ad customizer feed to populate the placeholders.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param adGroupIds the ad group IDs in which to create the ads.
   * @param feedName the name of the feed.
   */
  private void createAdsWithCustomizations(
      GoogleAdsClient googleAdsClient, long customerId, List<Long> adGroupIds, String feedName) {

    // Creates an expanded text ad using the feed attribute names as placeholders.
    ExpandedTextAdInfo expandedTextAdInfo =
        ExpandedTextAdInfo.newBuilder()
            .setHeadlinePart1(String.format("Luxury cruise to {=%s.Name}", feedName))
            .setHeadlinePart2(String.format("Only {=%s.Price}", feedName))
            .setDescription(String.format("Offer ends in {=countdown(%s.Date)}!", feedName))
            .build();

    Ad ad =
        Ad.newBuilder()
            .setExpandedTextAd(expandedTextAdInfo)
            .addFinalUrls("http://www.example.com")
            .build();

    List<AdGroupAdOperation> adGroupAdOperations = new ArrayList<>();

    // Creates the same ad in all ad groups. When they serve, they will show different values,
    // since they match different feed items.
    for (Long adGroupId : adGroupIds) {
      AdGroupAd adGroupAd =
          AdGroupAd.newBuilder()
              .setAd(ad)
              .setAdGroup(ResourceNames.adGroup(customerId, adGroupId))
              .build();

      AdGroupAdOperation adGroupAdOperation =
          AdGroupAdOperation.newBuilder().setCreate(adGroupAd).build();

      adGroupAdOperations.add(adGroupAdOperation);
    }

    try (AdGroupAdServiceClient adGroupAdServiceClient =
        googleAdsClient.getLatestVersion().createAdGroupAdServiceClient()) {

      MutateAdGroupAdsResponse response =
          adGroupAdServiceClient.mutateAdGroupAds(Long.toString(customerId), adGroupAdOperations);

      System.out.printf("Added %d ads:%n", response.getResultsCount());
      for (MutateAdGroupAdResult result : response.getResultsList()) {
        System.out.printf("Added an ad with resource name '%s'.%n", result.getResourceName());
      }
    }
  }
}

C#

// Copyright 2020 Google LLC
//
// 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.

using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V6.Common;
using Google.Ads.GoogleAds.V6.Errors;
using Google.Ads.GoogleAds.V6.Resources;
using Google.Ads.GoogleAds.V6.Services;

using System;
using System.Collections.Generic;
using System.Linq;
using static Google.Ads.GoogleAds.V6.Enums.AdCustomizerPlaceholderFieldEnum.Types;
using static Google.Ads.GoogleAds.V6.Enums.FeedAttributeTypeEnum.Types;
using static Google.Ads.GoogleAds.V6.Enums.PlaceholderTypeEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V6
{
    /// <summary>
    /// This code example adds an ad customizer feed and associates it with the customer.
    /// Then it adds an ad that uses the feed to populate dynamic data.
    /// </summary>
    public class AddAdCustomizer : ExampleBase
    {
        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            AddAdCustomizer codeExample = new AddAdCustomizer();
            Console.WriteLine(codeExample.Description);

            // The Google Ads customer ID for which the call is made.
            long customerId = long.Parse("INSERT_CUSTOMER_ID_HERE");

            // ID of the ad groups to which ads are added.
            long[] adGroupIds = new[]
            {
                long.Parse("INSERT_AD_GROUP_ID_HERE"),
                long.Parse("INSERT_AD_GROUP_ID_HERE"),
            };

            codeExample.Run(new GoogleAdsClient(), customerId, adGroupIds);
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This code example adds an ad customizer feed and associates it with the customer. " +
            "Then it adds an ad that uses the feed to populate dynamic data.";

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="adGroupIds">ID of the ad groups to which ad customizers are added.</param>
        public void Run(GoogleAdsClient client, long customerId, long[] adGroupIds)
        {
            // Get the AdGroupBidModifierService.
            AdGroupBidModifierServiceClient adGroupBidModifierService =
                client.GetService(Services.V6.AdGroupBidModifierService);

            string feedName = "Ad_Customizer_example_feed_" + ExampleUtilities.GetShortRandomString();

            try
            {
                // Create a feed to be used as the ad customizer.
                string adCustomizerFeedResourceName =
                    CreateAdCustomizerFeed(client, customerId, feedName);

                // Retrieve the attributes for the newly created feed.
                Dictionary<string, FeedAttribute> adCustomizerFeedAttributes =
                    GetFeedAttributes(client, customerId, adCustomizerFeedResourceName);

                // Map the feed to the ad customizer placeholder type to mark it as an
                // ad customizer.
                CreateAdCustomizerMapping(client, customerId, adCustomizerFeedResourceName,
                    adCustomizerFeedAttributes);

                // Create the feed items that will fill the placeholders in the ads customized by
                // the feed.
                List<string> feedItemResourceNames = CreateFeedItems(client, customerId,
                    adCustomizerFeedResourceName, adCustomizerFeedAttributes);

                // Create a feed item targeting to associate the feed items with specific
                // ad groups to prevent them from being used in other ways.
                CreateFeedItemTargets(client, customerId, adGroupIds, feedItemResourceNames);

                // Create ads with the customizations provided by the feed items.
                CreateAdsWithCustomizations(client, customerId, adGroupIds, feedName);
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }

        }

        /// <summary>
        /// Creates a feed to be used for ad customization.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="feedName">Name of the feed.</param>
        /// <returns>The resource name of the newly created feed.</returns>
        private string CreateAdCustomizerFeed(GoogleAdsClient client, long customerId,
            string feedName)
        {
            // Get the FeedServiceClient.
            FeedServiceClient feedService = client.GetService(Services.V6.FeedService);

            // Creates three feed attributes: a name, a price and a date. The attribute names
            // are arbitrary choices and will be used as placeholders in the ad text fields.
            FeedAttribute nameAttribute = new FeedAttribute()
            {
                Name = "Name",
                Type = FeedAttributeType.String
            };

            FeedAttribute priceAttribute = new FeedAttribute()
            {
                Name = "Price",
                Type = FeedAttributeType.String
            };

            FeedAttribute dateAttribute = new FeedAttribute()
            {
                Name = "Date",
                Type = FeedAttributeType.DateTime
            };

            Feed adCustomizerFeed = new Feed()
            {
                Name = feedName,
                Attributes = { nameAttribute, priceAttribute, dateAttribute }
            };

            FeedOperation feedOperation = new FeedOperation()
            {
                Create = adCustomizerFeed
            };

            MutateFeedsResponse response =
                feedService.MutateFeeds(customerId.ToString(), new[] { feedOperation });

            string feedResourceName = response.Results[0].ResourceName;
            Console.WriteLine($"Added feed with resource name '{feedResourceName}'.");
            return feedResourceName;
        }

        /// <summary>
        ///  Retrieves all the attributes for a feed and returns them in a map using the
        ///  attribute names as keys.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="feedResourceName">The resource name of the feed.</param>
        /// <returns>The attributes of the feed.</returns>
        private Dictionary<string, FeedAttribute> GetFeedAttributes(GoogleAdsClient client,
                    long customerId, string feedResourceName)
        {
            // Get the GoogleAdsServiceClient.
            GoogleAdsServiceClient googleAdsService =
                client.GetService(Services.V6.GoogleAdsService);

            string query = $"SELECT feed.attributes, feed.name FROM feed WHERE " +
                $"feed.resource_name = '{feedResourceName}'";

            SearchGoogleAdsRequest request = new SearchGoogleAdsRequest()
            {
                CustomerId = customerId.ToString(),
                Query = query
            };

            Dictionary<string, FeedAttribute> feedAttributes =
                new Dictionary<string, FeedAttribute>();

            Feed feed = googleAdsService.Search(request).First().Feed;

            Console.WriteLine($"Found the following attributes for feed with name '{feed.Name}'");
            foreach (FeedAttribute feedAttribute in feed.Attributes)
            {
                Console.WriteLine($"\t'{feedAttribute.Name}' with id {feedAttribute.Id} and " +
                    $"type '{feedAttribute.Type}'");
                feedAttributes[feedAttribute.Name] = feedAttribute;
            }
            return feedAttributes;
        }

        /// <summary>
        /// Creates a feed mapping and sets the feed as an ad customizer feed.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="feedResourceName">The resource name of the feed.</param>
        /// <param name="feedAttributes">The attributes of the feed.</param>
        private void CreateAdCustomizerMapping(GoogleAdsClient client, long customerId,
                    string feedResourceName, Dictionary<string, FeedAttribute> feedAttributes)
        {
            // Get the FeedMappingService.
            FeedMappingServiceClient feedMappingService =
                client.GetService(Services.V6.FeedMappingService);

            // Map the feed attributes to ad customizer placeholder fields.
            // For a full list of ad customizer placeholder fields, see
            // https://developers.google.com/google-ads/api/reference/rpc/latest/AdCustomizerPlaceholderFieldEnum.AdCustomizerPlaceholderField
            AttributeFieldMapping nameFieldMapping = new AttributeFieldMapping()
            {
                FeedAttributeId = feedAttributes["Name"].Id,
                AdCustomizerField = AdCustomizerPlaceholderField.String
            };

            AttributeFieldMapping priceFieldMapping = new AttributeFieldMapping()
            {
                FeedAttributeId = feedAttributes["Price"].Id,
                AdCustomizerField = AdCustomizerPlaceholderField.Price
            };

            AttributeFieldMapping dateFieldMapping = new AttributeFieldMapping()
            {
                FeedAttributeId = feedAttributes["Date"].Id,
                AdCustomizerField = AdCustomizerPlaceholderField.Date
            };

            FeedMapping feedMapping = new FeedMapping()
            {
                Feed = feedResourceName,
                PlaceholderType = PlaceholderType.AdCustomizer,
                AttributeFieldMappings = { nameFieldMapping, priceFieldMapping, dateFieldMapping }
            };

            FeedMappingOperation operation = new FeedMappingOperation()
            {
                Create = feedMapping
            };

            MutateFeedMappingsResponse response =
                feedMappingService.MutateFeedMappings(customerId.ToString(), new[] { operation });

            Console.WriteLine($"Added feed mapping with resource name" +
                $" '{response.Results[0].ResourceName}'.");
        }

        /// <summary>
        /// Creates two different feed items to enable two different ad customizations.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="feedResourceName">The resource name of the feed.</param>
        /// <param name="feedAttributes">The attributes of the feed.</param>
        /// <returns>The resource names of the feed items.</returns>
        private List<string> CreateFeedItems(GoogleAdsClient client, long customerId,
                    string feedResourceName, Dictionary<string, FeedAttribute> feedAttributes)
        {
            // Get the FeedItemServiceClient.
            FeedItemServiceClient feedItemService =
                client.GetService(Services.V6.FeedItemService);

            List<FeedItemOperation> feedItemOperations = new List<FeedItemOperation>();

            DateTime marsDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
            feedItemOperations.Add(
                CreateFeedItemOperation("Mars", "$1234.56", marsDate.ToString("yyyyMMdd HHmmss"),
                    feedResourceName, feedAttributes));

            DateTime venusDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 15);
            feedItemOperations.Add(
                CreateFeedItemOperation("Venus", "$1450.00", venusDate.ToString("yyyyMMdd HHmmss"),
                    feedResourceName, feedAttributes));

            List<string> feedItemResourceNames = new List<string>();
            MutateFeedItemsResponse response =
                feedItemService.MutateFeedItems(customerId.ToString(), feedItemOperations);

            Console.WriteLine($"Added {response.Results.Count} feed items:");

            foreach (MutateFeedItemResult result in response.Results)
            {
                string feedItemResourceName = result.ResourceName;
                feedItemResourceNames.Add(feedItemResourceName);
                Console.WriteLine($"Added feed item with resource name '{feedItemResourceName}'.");
            }
            return feedItemResourceNames;
        }

        /// <summary>
        /// Helper function to create a FeedItemOperation.
        /// </summary>
        /// <param name="name">The value of the Name attribute.</param>
        /// <param name="price">The value of the Price attribute.</param>
        /// <param name="date">The value of the Date attribute.</param>
        /// <param name="feedResourceName">The resource name of the feed.</param>
        /// <param name="feedAttributes">The attributes to be set on the feed.</param>
        /// <returns>A FeedItemOperation to create a feed item.</returns>
        private FeedItemOperation CreateFeedItemOperation(string name, string price, string date,
                    string feedResourceName, Dictionary<string, FeedAttribute> feedAttributes)
        {
            FeedItemAttributeValue nameAttributeValue = new FeedItemAttributeValue()
            {
                FeedAttributeId = feedAttributes["Name"].Id,
                StringValue = name
            };

            FeedItemAttributeValue priceAttributeValue = new FeedItemAttributeValue()
            {
                FeedAttributeId = feedAttributes["Price"].Id,
                StringValue = price
            };

            FeedItemAttributeValue dateAttributeValue = new FeedItemAttributeValue()
            {
                FeedAttributeId = feedAttributes["Date"].Id,
                StringValue = date
            };

            FeedItem feedItem = new FeedItem()
            {
                Feed = feedResourceName,
                AttributeValues = { nameAttributeValue, priceAttributeValue, dateAttributeValue }
            };

            return new FeedItemOperation()
            {
                Create = feedItem
            };
        }

        /// <summary>
        /// Restricts the feed items to work only with a specific ad group; this prevents the
        /// feed items from being used elsewhere and makes sure they are used only for
        /// customizing a specific ad group.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="adGroupIds">The ad group IDs to bind the feed items to..</param>
        /// <param name="feedItemResourceNames">The resource names of the feed items.</param>
        private void CreateFeedItemTargets(GoogleAdsClient client,
                    long customerId, long[] adGroupIds, List<string> feedItemResourceNames)
        {
            // Get the FeedItemTargetServiceClient.
            FeedItemTargetServiceClient feedItemTargetService =
                client.GetService(Services.V6.FeedItemTargetService);

            // Bind each feed item to a specific ad group to make sure it will only be used to
            // customize ads inside that ad group; using the feed item elsewhere will result
            // in an error.
            for (int i = 0; i < feedItemResourceNames.Count; i++)
            {
                string feedItemResourceName = feedItemResourceNames[i];
                long adGroupId = adGroupIds[i];

                FeedItemTarget feedItemTarget = new FeedItemTarget()
                {
                    AdGroup = ResourceNames.AdGroup(customerId, adGroupId),
                    FeedItem = feedItemResourceName
                };

                FeedItemTargetOperation feedItemTargetOperation = new FeedItemTargetOperation()
                {
                    Create = feedItemTarget
                };

                MutateFeedItemTargetsResponse response =
                    feedItemTargetService.MutateFeedItemTargets(customerId.ToString(),
                        new[] { feedItemTargetOperation });

                string feedItemTargetResourceName = response.Results[0].ResourceName;
                Console.WriteLine($"Added feed item target with resource name " +
                    $"'{response.Results[0].ResourceName}'.");
            }
        }

        /// <summary>
        /// Creates expanded text ads that use the ad customizer feed to populate the placeholders.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="adGroupIds">The ad group IDs in which to create the ads.</param>
        /// <param name="feedName">Name of the feed.</param>
        private void CreateAdsWithCustomizations(GoogleAdsClient client, long customerId,
                    long[] adGroupIds, string feedName)
        {
            // Get the AdGroupAdServiceClient.
            AdGroupAdServiceClient adGroupAdService =
                client.GetService(Services.V6.AdGroupAdService);


            // Creates an expanded text ad using the feed attribute names as placeholders.
            Ad ad = new Ad()
            {
                ExpandedTextAd = new ExpandedTextAdInfo()
                {
                    HeadlinePart1 = $"Luxury cruise to {{={feedName}.Name}}",
                    HeadlinePart2 = $"Only {{={feedName}.Price}}",
                    Description = $"Offer ends in {{=countdown({feedName}.Date)}}!"
                },
                FinalUrls = { "http://www.example.com" }
            };

            List<AdGroupAdOperation> adGroupAdOperations = new List<AdGroupAdOperation>();

            // Creates the same ad in all ad groups. When they serve, they will show
            // different values, since they match different feed items.
            foreach (long adGroupId in adGroupIds)
            {
                AdGroupAd adGroupAd = new AdGroupAd()
                {
                    Ad = ad,
                    AdGroup = ResourceNames.AdGroup(customerId, adGroupId)
                };

                adGroupAdOperations.Add(new AdGroupAdOperation()
                {
                    Create = adGroupAd
                });
            }

            MutateAdGroupAdsResponse response =
                adGroupAdService.MutateAdGroupAds(customerId.ToString(), adGroupAdOperations);

            Console.WriteLine($"Added {response.Results.Count} ads:");
            foreach (MutateAdGroupAdResult result in response.Results)
            {
                Console.WriteLine($"Added an ad with resource name '{result.ResourceName}'.");
            }
        }
    }
}

PHP

<?php

/**
 * Copyright 2019 Google LLC
 *
 * 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
 *
 *     https://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.
 */

namespace Google\Ads\GoogleAds\Examples\AdvancedOperations;

require __DIR__ . '/../../vendor/autoload.php';

use DateTime;
use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V6\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V6\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V6\GoogleAdsException;
use Google\Ads\GoogleAds\Util\V6\ResourceNames;
use Google\Ads\GoogleAds\V6\Common\ExpandedTextAdInfo;
use Google\Ads\GoogleAds\V6\Enums\AdCustomizerPlaceholderFieldEnum\AdCustomizerPlaceholderField;
use Google\Ads\GoogleAds\V6\Enums\FeedAttributeTypeEnum\FeedAttributeType;
use Google\Ads\GoogleAds\V6\Enums\FeedOriginEnum\FeedOrigin;
use Google\Ads\GoogleAds\V6\Enums\PlaceholderTypeEnum\PlaceholderType;
use Google\Ads\GoogleAds\V6\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V6\Resources\Ad;
use Google\Ads\GoogleAds\V6\Resources\AdGroupAd;
use Google\Ads\GoogleAds\V6\Resources\AttributeFieldMapping;
use Google\Ads\GoogleAds\V6\Resources\Feed;
use Google\Ads\GoogleAds\V6\Resources\FeedAttribute;
use Google\Ads\GoogleAds\V6\Resources\FeedItem;
use Google\Ads\GoogleAds\V6\Resources\FeedItemAttributeValue;
use Google\Ads\GoogleAds\V6\Resources\FeedItemTarget;
use Google\Ads\GoogleAds\V6\Resources\FeedMapping;
use Google\Ads\GoogleAds\V6\Services\AdGroupAdOperation;
use Google\Ads\GoogleAds\V6\Services\FeedItemOperation;
use Google\Ads\GoogleAds\V6\Services\FeedItemTargetOperation;
use Google\Ads\GoogleAds\V6\Services\FeedMappingOperation;
use Google\Ads\GoogleAds\V6\Services\FeedOperation;
use Google\ApiCore\ApiException;

/**
 * Adds an ad customizer feed and associates it with the customer. Then it adds an ad that uses the
 * feed to populate dynamic data.
 */
class AddAdCustomizer
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const AD_GROUP_ID_1 = 'INSERT_AD_GROUP_ID_1_HERE';
    private const AD_GROUP_ID_2 = 'INSERT_AD_GROUP_ID_2_HERE';

    // We're creating two different ad groups to be dynamically populated by the same feed.
    private const NUMBER_OF_AD_GROUPS = 2;

    // We're doing only searches by resource_name in this example, we can set page size = 1.
    private const PAGE_SIZE = 1;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::AD_GROUP_IDS => GetOpt::MULTIPLE_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::AD_GROUP_IDS] ?: [self::AD_GROUP_ID_1, self::AD_GROUP_ID_2]
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param array $adGroupIds the ad group IDs
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        array $adGroupIds
    ) {
        if (count($adGroupIds) != self::NUMBER_OF_AD_GROUPS) {
            throw new \InvalidArgumentException(
                'Please pass exactly ' . self::NUMBER_OF_AD_GROUPS .
                ' ad group IDs in the adGroupIds parameter.'
            );
        }

        $feedName = 'Ad Customizer example feed ' . uniqid();

        // Create a feed to be used for ad customization.
        $adCustomizerFeedResourceName = self::createAdCustomizerFeed(
            $googleAdsClient,
            $customerId,
            $feedName
        );

        // Retrieve the attributes of the feed.
        $adCustomizerFeedAttributes = self::getFeedAttributes(
            $googleAdsClient,
            $customerId,
            $adCustomizerFeedResourceName
        );

        // Map the feed to the ad customizer placeholder fields.
        self::createAdCustomizerMapping(
            $googleAdsClient,
            $customerId,
            $adCustomizerFeedResourceName,
            $adCustomizerFeedAttributes
        );

        // Create feed items to be used to customize ads.
        $feedItemResourceNames = self::createFeedItems(
            $googleAdsClient,
            $customerId,
            $adCustomizerFeedResourceName,
            $adCustomizerFeedAttributes
        );

        // Set the feed to be used only with the specified ad groups.
        self::createFeedItemTargets(
            $googleAdsClient,
            $customerId,
            $adGroupIds,
            $feedItemResourceNames
        );

        // Create ads that use the feed for customization.
        self::createAdsWithCustomizations(
            $googleAdsClient,
            $customerId,
            $adGroupIds,
            $feedName
        );
    }

   /**
    * Creates a feed to be used for ad customization.
    *
    * @param GoogleAdsClient $googleAdsClient the Google Ads API client
    * @param int $customerId the customer ID in which to create the feed
    * @param string $feedName the name of the feed to create
    * @return string the resource name of the newly created feed
    */
    private static function createAdCustomizerFeed(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $feedName
    ) {
        // Creates three feed attributes: a name, a price and a date. The attribute names are
        // arbitrary choices and will be used as placeholders in the ad text fields.
        $nameAttribute = new FeedAttribute(['type' => FeedAttributeType::STRING, 'name' => 'Name']);
        $priceAttribute =
            new FeedAttribute(['type' => FeedAttributeType::STRING, 'name' => 'Price']);
        $dateAttribute =
            new FeedAttribute(['type' => FeedAttributeType::DATE_TIME, 'name' => 'Date']);

        // Creates the feed.
        $feed = new Feed([
            'name' => $feedName,
            'attributes' => [$nameAttribute, $priceAttribute, $dateAttribute],
            'origin' => FeedOrigin::USER
        ]);

        // Creates a feed operation for creating a feed.
        $feedOperation = new FeedOperation();
        $feedOperation->setCreate($feed);

        // Issues a mutate request to add the feed.
        $feedServiceClient = $googleAdsClient->getFeedServiceClient();
        $feedResponse = $feedServiceClient->mutateFeeds($customerId, [$feedOperation]);

        $feedResourceName = $feedResponse->getResults()[0]->getResourceName();
        printf("Added feed with resource name '%s'.%s", $feedResourceName, PHP_EOL);

        return $feedResourceName;
    }

    /**
     * Retrieves attributes for a feed.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $feedResourceName the resource name of the feed
     * @return array the feed attributes, keyed by attribute name
     */
    private static function getFeedAttributes(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $feedResourceName
    ) {
        $query = "SELECT feed.attributes, feed.name FROM feed "
            . "WHERE feed.resource_name = '$feedResourceName'";

        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
        $response =
            $googleAdsServiceClient->search($customerId, $query, ['pageSize' => self::PAGE_SIZE]);

        /** @var Feed $feed */
        $feed = $response->getIterator()->current()->getFeed();
        $feedDetails = [];
        printf(
            "Found the following attributes for feed with name %s:%s",
            $feed->getName(),
            PHP_EOL
        );
        foreach ($feed->getAttributes() as $feedAttribute) {
            /** @var FeedAttribute $feedAttribute */
            $feedDetails[$feedAttribute->getName()] = $feedAttribute->getId();
            printf(
                "\t'%s' with id %d and type '%s'%s",
                $feedAttribute->getName(),
                $feedAttribute->getId(),
                FeedAttributeType::name($feedAttribute->getType()),
                PHP_EOL
            );
        }
        return $feedDetails;
    }

    /**
     * Creates a feed mapping for a given feed.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $adCustomizerFeedResourceName the resource name of the ad customizer feed
     * @param array $feedDetails an associative array from feed attribute names to their IDs
     */
    private static function createAdCustomizerMapping(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $adCustomizerFeedResourceName,
        array $feedDetails
    ) {
        // Maps the feed attribute IDs to the field ID constants.
        $nameFieldMapping = new AttributeFieldMapping([
            'feed_attribute_id' => $feedDetails['Name'],
            'ad_customizer_field' => AdCustomizerPlaceholderField::STRING
        ]);

        $priceFieldMapping = new AttributeFieldMapping([
            'feed_attribute_id' => $feedDetails['Price'],
            'ad_customizer_field' => AdCustomizerPlaceholderField::PRICE
        ]);

        $dateFieldMapping = new AttributeFieldMapping([
            'feed_attribute_id' => $feedDetails['Date'],
            'ad_customizer_field' => AdCustomizerPlaceholderField::DATE
        ]);

        // Creates the feed mapping.
        $feedMapping = new FeedMapping([
            'placeholder_type' => PlaceholderType::AD_CUSTOMIZER,
            'feed' => $adCustomizerFeedResourceName,
            'attribute_field_mappings' => [$nameFieldMapping, $priceFieldMapping, $dateFieldMapping]
        ]);

        // Creates the operation.
        $feedMappingOperation = new FeedMappingOperation();
        $feedMappingOperation->setCreate($feedMapping);

        // Issues a mutate request to add the feed mapping.
        $feedMappingServiceClient = $googleAdsClient->getFeedMappingServiceClient();
        $response = $feedMappingServiceClient->mutateFeedMappings(
            $customerId,
            [$feedMappingOperation]
        );

        // Displays the results.
        foreach ($response->getResults() as $result) {
            printf(
                "Created feed mapping with resource name '%s'.%s",
                $result->getResourceName(),
                PHP_EOL
            );
        }
    }

    /**
     * Creates two different feed items to enable two different ad customizations.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $adCustomizerFeedResourceName the resource name of the feed
     * @param array $adCustomizerFeedAttributes the attributes of the feed
     * @return string[] the created feed item resource names
     */
    private static function createFeedItems(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $adCustomizerFeedResourceName,
        array $adCustomizerFeedAttributes
    ) {
        $feedItemOperations = [];

        $feedItemOperations[] = self::createFeedItemOperation(
            'Mars',
            '$1234.56',
            date_format(new DateTime('first day of this month'), 'Ymd His'),
            $adCustomizerFeedResourceName,
            $adCustomizerFeedAttributes
        );

        $feedItemOperations[] = self::createFeedItemOperation(
            'Venus',
            '$6543.21',
            // Set the date to the 15th of the current month.
            date_format(DateTime::createFromFormat('d', '15'), 'Ymd His'),
            $adCustomizerFeedResourceName,
            $adCustomizerFeedAttributes
        );

        // Adds the feed items.
        $feedItemServiceClient = $googleAdsClient->getFeedItemServiceClient();
        $response = $feedItemServiceClient->mutateFeedItems($customerId, $feedItemOperations);

        $feedItemResourceNames = [];
        // Displays the results.
        foreach ($response->getResults() as $result) {
            printf(
                "Created feed item with resource name '%s'.%s",
                $result->getResourceName(),
                PHP_EOL
            );
            $feedItemResourceNames[] = $result->getResourceName();
        }

        return $feedItemResourceNames;
    }

    /**
     * Creates a FeedItemOperation.
     *
     * @param string $name the value of the Name attribute
     * @param string $price the value of the Price attribute
     * @param string $date the value of the Date attribute
     * @param string $adCustomizerFeedResourceName the resource name of the feed
     * @param array $adCustomizerFeedAttributes the attributes to be set on the feed
     * @return FeedItemOperation the feed item operation to create a feed item
     */
    private static function createFeedItemOperation(
        string $name,
        string $price,
        string $date,
        string $adCustomizerFeedResourceName,
        array $adCustomizerFeedAttributes
    ) {
        $nameAttributeValue = new FeedItemAttributeValue([
            'feed_attribute_id' => $adCustomizerFeedAttributes['Name'],
            'string_value' => $name
        ]);

        $priceAttributeValue = new FeedItemAttributeValue([
            'feed_attribute_id' => $adCustomizerFeedAttributes['Price'],
            'string_value' => $price
        ]);

        $dateAttributeValue = new FeedItemAttributeValue([
            'feed_attribute_id' => $adCustomizerFeedAttributes['Date'],
            'string_value' => $date
        ]);

        $feedItem = new FeedItem([
            'feed' => $adCustomizerFeedResourceName,
            'attribute_values' => [$nameAttributeValue, $priceAttributeValue, $dateAttributeValue]
        ]);

        $feedItemOperation = new FeedItemOperation();
        $feedItemOperation->setCreate($feedItem);

        return $feedItemOperation;
    }

  /**
   * Restricts the feed items to work only with a specific ad group; this prevents the feed items
   * from being used elsewhere and makes sure they are used only for customizing a specific ad
   * group.
   *
   * @param GoogleAdsClient $googleAdsClient the Google Ads API client
   * @param int $customerId the customer ID
   * @param array $adGroupIds the ad group IDs to bind the feed items to
   * @param array $feedItemResourceNames the resource names of the feed items
   */
    private static function createFeedItemTargets(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        array $adGroupIds,
        array $feedItemResourceNames
    ) {
        // Bind each feed item to a specific ad group to make sure it will only be used to customize
        // ads inside that ad group; using the feed item elsewhere will result in an error.
        for ($i = 0; $i < count($feedItemResourceNames); $i++) {
            $feedItemResourceName = $feedItemResourceNames[$i];
            $adGroupId = $adGroupIds[$i];

            $feedItemTarget = new FeedItemTarget([
                'feed_item' => $feedItemResourceName,
                'ad_group' => ResourceNames::forAdGroup($customerId, $adGroupId)
            ]);

            // Creates the operation.
            $feedItemTargetOperation = new FeedItemTargetOperation();
            $feedItemTargetOperation->setCreate($feedItemTarget);

            // Issues a mutate request to add the feed item target.
            $feedItemTargetServiceClient = $googleAdsClient->getFeedItemTargetServiceClient();
            $feedItemTargetResponse = $feedItemTargetServiceClient->mutateFeedItemTargets(
                $customerId,
                [$feedItemTargetOperation]
            );

            $feedItemTargetResourceName =
                $feedItemTargetResponse->getResults()[0]->getResourceName();
            printf(
                "Added feed item target with resource name '%s'.%s",
                $feedItemTargetResourceName,
                PHP_EOL
            );
        }
    }

    /**
     * Creates expanded text ads that use the ad customizer feed to populate the placeholders.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the client customer ID
     * @param array $adGroupIds the ad group IDs in which to create the ads
     * @param string $feedName the name of the feed
     */
    private static function createAdsWithCustomizations(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        array $adGroupIds,
        string $feedName
    ) {
        $expandedTextAdInfo = new ExpandedTextAdInfo([
            'headline_part1' => "Luxury cruise to {=$feedName.Name}",
            'headline_part2' => "Only {=$feedName.Price}",
            'description' => "Offer ends in {=countdown($feedName.Date)}!"
        ]);

        $ad = new Ad([
            'expanded_text_ad' => $expandedTextAdInfo,
            'final_urls' => ['http://www.example.com']
        ]);

        $adGroupAdOperations = [];

        foreach ($adGroupIds as $adGroupId) {
            $adGroupAd = new AdGroupAd([
                'ad' => $ad,
                'ad_group' => ResourceNames::forAdGroup($customerId, $adGroupId)
            ]);

            $adGroupAdOperation = new AdGroupAdOperation();
            $adGroupAdOperation->setCreate($adGroupAd);

            $adGroupAdOperations[] = $adGroupAdOperation;
        }

        // Issues a mutate request to add the ads.
        $adGroupAdServiceClient = $googleAdsClient->getAdGroupAdServiceClient();
        $adGroupAdResponse = $adGroupAdServiceClient->mutateAdGroupAds(
            $customerId,
            $adGroupAdOperations
        );

        printf('Added %d ads:%s', count($adGroupAdResponse->getResults()), PHP_EOL);
        foreach ($adGroupAdResponse->getResults() as $result) {
            printf("Added an ad with resource name '%s'.%s", $result->getResourceName(), PHP_EOL);
        }
    }
}

AddAdCustomizer::main();

Python

#!/usr/bin/env python
# Copyright 2020 Google LLC
#
# 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
#
#     https://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.
"""Adds an ad customizer feed and associates it with a given customer.

It then adds an ad that uses the feed to populate dynamic data.
"""


import argparse
from datetime import datetime
import sys
from uuid import uuid4

from google.ads.google_ads.client import GoogleAdsClient
from google.ads.google_ads.errors import GoogleAdsException


def main(client, customer_id, ad_group_ids):
    """The main method that creates all necessary entities for the example.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        ad_group_ids: a list of ad group IDs.
    """
    feed_name = f"Ad customizer example feed {uuid4()}"
    ad_customizer_feed_resource_name = _create_add_customizer_feed(
        client, customer_id, feed_name
    )
    ad_customizer_feed_attributes = _get_feed_attributes(
        client, customer_id, ad_customizer_feed_resource_name
    )

    _create_ad_customizer_mapping(
        client,
        customer_id,
        ad_customizer_feed_resource_name,
        ad_customizer_feed_attributes,
    )

    feed_item_resource_names = _create_feed_items(
        client,
        customer_id,
        ad_customizer_feed_resource_name,
        ad_customizer_feed_attributes,
    )

    _create_feed_item_targets(
        client, customer_id, ad_group_ids, feed_item_resource_names
    )

    _create_ads_with_customizations(
        client, customer_id, ad_group_ids, feed_name
    )


def _create_add_customizer_feed(client, customer_id, feed_name):
    """Creates a feed to be used for ad customization.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        feed_name: the name of the feed to create.

    Returns:
        A str of a resource name for the newly created feed.
    """
    # Creates three feed attributes: a name, a price and a date.
    # The attribute names are arbitrary choices and will be used as
    # placeholders in the ad text fields.
    feed_attr_type_enum = client.get_type("FeedAttributeTypeEnum", version="v6")

    name_attr = client.get_type("FeedAttribute", version="v6")
    name_attr.type = feed_attr_type_enum.STRING
    name_attr.name = "Name"

    price_attr = client.get_type("FeedAttribute", version="v6")
    price_attr.type = feed_attr_type_enum.STRING
    price_attr.name = "Price"

    date_attr = client.get_type("FeedAttribute", version="v6")
    date_attr.type = feed_attr_type_enum.DATE_TIME
    date_attr.name = "Date"

    feed_operation = client.get_type("FeedOperation", version="v6")
    feed = feed_operation.create

    feed.name = feed_name
    feed.attributes.extend([name_attr, price_attr, date_attr])
    feed.origin = client.get_type("FeedOriginEnum", version="v6").USER

    feed_service = client.get_service("FeedService", version="v6")

    try:
        response = feed_service.mutate_feeds(customer_id, [feed_operation])
        resource_name = response.results[0].resource_name
        print(f"Added feed with resource name {resource_name}")
        return resource_name
    except GoogleAdsException as ex:
        _handle_google_ads_exception(ex)


def _get_feed_attributes(client, customer_id, feed_resource_name):
    """Retrieves attributes for a feed.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        feed_resource_name: the resource name of the feed.

    Returns:
        A dict of feed attributes, keyed by attribute name.
    """
    query = f"""
      SELECT
        feed.attributes,
        feed.name
      FROM feed
      WHERE
        feed.resource_name = "{feed_resource_name}"
    """
    ga_service = client.get_service("GoogleAdsService", version="v6")

    try:
        results = ga_service.search(customer_id, query, page_size=1)
        feed = list(results)[0].feed
        print(f"Found the following attributes for feed with name {feed.name}")
    except GoogleAdsException as ex:
        _handle_google_ads_exception(ex)

    feed_attr_type_enum = client.get_type("FeedAttributeTypeEnum", version="v6")
    feed_details = {}
    for feed_attribute in feed.attributes:
        name = feed_attribute.name
        feed_attr_id = feed_attribute.id
        feed_type = feed_attr_type_enum.FeedAttributeType.Name(
            feed_attribute.type
        )
        feed_details[name] = feed_attr_id
        print(f"\t{name} with id {feed_attr_id} and type {feed_type}.")

    return feed_details


def _create_ad_customizer_mapping(
    client, customer_id, ad_customizer_feed_resource_name, feed_details,
):
    """Creates a feed mapping for a given feed.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        ad_customizer_feed_resource_name: the resource name of the ad customizer
            feed.
        feed_details: a dict mapping feed attribute names to their IDs.
    """
    placeholder_field_enum = client.get_type(
        "AdCustomizerPlaceholderFieldEnum", version="v6"
    )

    # Map the feed attributes to ad customizer placeholder fields. For a full
    # list of ad customizer placeholder fields, see:
    # https://developers.google.com/google-ads/api/reference/rpc/latest/AdCustomizerPlaceholderFieldEnum.AdCustomizerPlaceholderField
    name_field_mapping = client.get_type("AttributeFieldMapping", version="v6")
    name_field_mapping.feed_attribute_id = feed_details["Name"]
    name_field_mapping.ad_customizer_field = placeholder_field_enum.STRING

    price_field_mapping = client.get_type("AttributeFieldMapping", version="v6")
    price_field_mapping.feed_attribute_id = feed_details["Price"]
    price_field_mapping.ad_customizer_field = placeholder_field_enum.PRICE

    date_field_mapping = client.get_type("AttributeFieldMapping", version="v6")
    date_field_mapping.feed_attribute_id = feed_details["Date"]
    date_field_mapping.ad_customizer_field = placeholder_field_enum.DATE

    feed_mapping_op = client.get_type("FeedMappingOperation", version="v6")
    feed_mapping = feed_mapping_op.create
    feed_mapping.feed = ad_customizer_feed_resource_name
    feed_mapping.placeholder_type = client.get_type(
        "PlaceholderTypeEnum", version="v6"
    ).AD_CUSTOMIZER
    feed_mapping.attribute_field_mappings.extend(
        [name_field_mapping, price_field_mapping, date_field_mapping]
    )

    feed_mapping_service = client.get_service(
        "FeedMappingService", version="v6"
    )

    try:
        response = feed_mapping_service.mutate_feed_mappings(
            customer_id, [feed_mapping_op]
        )
        for result in response.results:
            print(
                "Created feed mapping with resource name "
                f"{result.resource_name}"
            )
    except GoogleAdsException as ex:
        _handle_google_ads_exception(ex)


def _create_feed_items(
    client,
    customer_id,
    ad_customizer_feed_resource_name,
    ad_customizer_feed_attributes,
):
    """Creates two feed items to enable two different ad customizations.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        ad_customizer_feed_resource_name: the resource name of the ad customizer
            feed.
        ad_customizer_feed_attributes: a dict mapping feed attribute names to
            their IDs.

    Returns:
        A list of feed item resource name strs.
    """
    feed_item_operations = []
    feed_item_operations.append(
        _create_feed_item_operation(
            client,
            "Mars",
            "$1234.56",
            # Set the date to the 1st of the current month.
            datetime.now().replace(day=1).strftime("%Y%m%d %H%M%S"),
            ad_customizer_feed_resource_name,
            ad_customizer_feed_attributes,
        )
    )
    feed_item_operations.append(
        _create_feed_item_operation(
            client,
            "Venus",
            "$6543.21",
            # Set the date to the 15th of the current month.
            datetime.now().replace(day=15).strftime("%Y%m%d %H%M%S"),
            ad_customizer_feed_resource_name,
            ad_customizer_feed_attributes,
        )
    )

    feed_item_service = client.get_service("FeedItemService", version="v6")

    try:
        response = feed_item_service.mutate_feed_items(
            customer_id, feed_item_operations
        )
        return [feed_item.resource_name for feed_item in response.results]
    except GoogleAdsException as ex:
        _handle_google_ads_exception(ex)


def _create_feed_item_operation(
    client,
    name,
    price,
    date,
    ad_customizer_feed_resource_name,
    ad_customizer_feed_attributes,
):
    """Creates a FeedItemOperation.

    Args:
        client: an initialized GoogleAdsClient instance.
        name: a str value for the name attribute of the feed_item
        price: a str value for the price attribute of the feed_item
        date: a str value for the date attribute of the feed_item
        ad_customizer_feed_resource_name: the resource name of the ad customizer
            feed.
        ad_customizer_feed_attributes: a dict mapping feed attribute names to
            their IDs.

    Returns:
        A FeedItemOperation that creates a FeedItem
    """
    name_attr_value = client.get_type("FeedItemAttributeValue", version="v6")
    name_attr_value.feed_attribute_id = ad_customizer_feed_attributes["Name"]
    name_attr_value.string_value = name

    price_attr_value = client.get_type("FeedItemAttributeValue", version="v6")
    price_attr_value.feed_attribute_id = ad_customizer_feed_attributes["Price"]
    price_attr_value.string_value = price

    date_attr_value = client.get_type("FeedItemAttributeValue", version="v6")
    date_attr_value.feed_attribute_id = ad_customizer_feed_attributes["Date"]
    date_attr_value.string_value = date

    feed_item_op = client.get_type("FeedItemOperation", version="v6")
    feed_item = feed_item_op.create
    feed_item.feed = ad_customizer_feed_resource_name
    feed_item.attribute_values.extend(
        [name_attr_value, price_attr_value, date_attr_value]
    )

    return feed_item_op


def _create_feed_item_targets(
    client, customer_id, ad_group_ids, feed_item_resource_names
):
    """Restricts the feed items to work only with a specific ad group.

    This prevents the feed items from being used elsewhere and makes sure they
    are used only for customizing a specific ad group.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        ad_group_ids: a list of ad group IDs.
        feed_item_resource_names: a list of feed item resource name strs.
    """
    ad_group_service = client.get_service("AdGroupService", version="v6")
    feed_item_target_service = client.get_service(
        "FeedItemTargetService", version="v6"
    )
    # Bind each feed item to a specific ad group to make sure it will only be
    # used to customize ads inside that ad group; using the feed item elsewhere
    # will result in an error.
    for i, resource_name in enumerate(feed_item_resource_names):
        ad_group_id = ad_group_ids[i]

        feed_item_target_op = client.get_type(
            "FeedItemTargetOperation", version="v6"
        )
        feed_item_target = feed_item_target_op.create
        feed_item_target.feed_item = resource_name
        feed_item_target.ad_group = ad_group_service.ad_group_path(
            customer_id, ad_group_id
        )

        try:
            response = feed_item_target_service.mutate_feed_item_targets(
                customer_id, [feed_item_target_op]
            )
            print(
                "Added feed item target with resource name "
                f"{response.results[0].resource_name}"
            )
        except GoogleAdsException as ex:
            _handle_google_ads_exception(ex)


def _create_ads_with_customizations(
    client, customer_id, ad_group_ids, feed_name
):
    """Creates expanded text ads that use the ad customizer feed.

    The expanded text ads use the ad customizer feed to populate the
    placeholders.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        ad_group_ids: a list of ad group IDs.
        feed_name: the name of the feed to create.
    """
    ad_group_service = client.get_service("AdGroupService", version="v6")
    ad_group_ad_service = client.get_service("AdGroupAdService", version="v6")
    ad_group_ad_operations = []

    for ad_group_id in ad_group_ids:
        ad_group_ad_operation = client.get_type(
            "AdGroupAdOperation", version="v6"
        )
        ad_group_ad = ad_group_ad_operation.create
        ad_group_ad.ad_group = ad_group_service.ad_group_path(
            customer_id, ad_group_id
        )
        ad_group_ad.ad.final_urls.append("http://www.example.com")
        ad_group_ad.ad.expanded_text_ad.headline_part1 = (
            f"Luxury cruise to {{={feed_name}.Name}}"
        )
        ad_group_ad.ad.expanded_text_ad.headline_part2 = (
            f"Only {{={feed_name}.Price}}"
        )
        # See this documentation for an explanation of how countdown ad
        # customizers work: https://support.google.com/google-ads/answer/6193743?hl=en
        ad_group_ad.ad.expanded_text_ad.description = (
            f"Offer ends in {{=countdown({feed_name}.Date)}}!"
        )
        ad_group_ad_operations.append(ad_group_ad_operation)

    try:
        response = ad_group_ad_service.mutate_ad_group_ads(
            customer_id, ad_group_ad_operations
        )
        print(f"Added {len(response.results)} ads:")
        for ad in response.results:
            print(f"Added an ad with resource name {ad.resource_name}")
    except GoogleAdsException as ex:
        _handle_google_ads_exception(ex)


def _handle_google_ads_exception(exception):
    """Prints the details of a GoogleAdsException object.

    Args:
        exception: an instance of GoogleAdsException.
    """
    print(
        f'Request with ID "{exception.request_id}" failed with status '
        f'"{exception.error.code().name}" and includes the following errors:'
    )
    for error in exception.failure.errors:
        print(f'\tError with message "{error.message}".')
        if error.location:
            for field_path_element in error.location.field_path_elements:
                print(f"\t\tOn field: {field_path_element.field_name}")
    sys.exit(1)


if __name__ == "__main__":
    # GoogleAdsClient will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    google_ads_client = GoogleAdsClient.load_from_storage()

    parser = argparse.ArgumentParser(
        description=(
            "Adds an ad customizer feed and associates it with a customer."
        )
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-a",
        "--ad_group_ids",
        nargs=2,
        type=str,
        required=True,
        help="Space-delimited list of ad group IDs.",
    )
    args = parser.parse_args()

    main(google_ads_client, args.customer_id, args.ad_group_ids)

Perl

#!/usr/bin/perl -w
#
# Copyright 2019, Google LLC
#
# 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.
#
# Adds an ad customizer feed and associates it with the customer. Then it adds an
# ad that uses the feed to populate dynamic data.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::V6::Resources::FeedAttribute;
use Google::Ads::GoogleAds::V6::Resources::Feed;
use Google::Ads::GoogleAds::V6::Resources::AttributeFieldMapping;
use Google::Ads::GoogleAds::V6::Resources::FeedMapping;
use Google::Ads::GoogleAds::V6::Resources::FeedItemAttributeValue;
use Google::Ads::GoogleAds::V6::Resources::FeedItem;
use Google::Ads::GoogleAds::V6::Resources::FeedItemTarget;
use Google::Ads::GoogleAds::V6::Resources::Ad;
use Google::Ads::GoogleAds::V6::Resources::AdGroupAd;
use Google::Ads::GoogleAds::V6::Common::ExpandedTextAdInfo;
use Google::Ads::GoogleAds::V6::Enums::FeedAttributeTypeEnum
  qw(STRING DATE_TIME);
use Google::Ads::GoogleAds::V6::Enums::FeedOriginEnum qw(USER);
use Google::Ads::GoogleAds::V6::Enums::AdCustomizerPlaceholderFieldEnum;
use Google::Ads::GoogleAds::V6::Enums::PlaceholderTypeEnum qw(AD_CUSTOMIZER);
use Google::Ads::GoogleAds::V6::Services::FeedService::FeedOperation;
use
  Google::Ads::GoogleAds::V6::Services::FeedMappingService::FeedMappingOperation;
use Google::Ads::GoogleAds::V6::Services::FeedItemService::FeedItemOperation;
use
  Google::Ads::GoogleAds::V6::Services::FeedItemTargetService::FeedItemTargetOperation;
use Google::Ads::GoogleAds::V6::Services::AdGroupAdService::AdGroupAdOperation;
use Google::Ads::GoogleAds::V6::Utils::ResourceNames;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd qw(abs_path);
use Data::Uniqid qw(uniqid);
use POSIX qw(strftime mktime);

# We're doing only searches by resource_name in this example, we can set page size = 1.
use constant PAGE_SIZE => 1;
# We're creating two different ad groups to be dynamically populated by the same feed.
use constant NUMBER_OF_AD_GROUPS => 2;

# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
my $customer_id   = "INSERT_CUSTOMER_ID_HERE";
my $ad_group_id_1 = "INSERT_AD_GROUP_ID_1_HERE";
my $ad_group_id_2 = "INSERT_AD_GROUP_ID_2_HERE";
my $ad_group_ids  = [];

sub add_ad_customizer {
  my ($api_client, $customer_id, $ad_group_ids) = @_;

  die sprintf
    "Please pass exactly %d ad group IDs in the ad_group_ids parameter.\n",
    NUMBER_OF_AD_GROUPS
    if scalar @$ad_group_ids != NUMBER_OF_AD_GROUPS;

  my $feed_name = "Ad Customizer example feed " . uniqid();

  # Create a feed to be used for ad customization.
  my $ad_customizer_feed_resource_name =
    create_ad_customizer_feed($api_client, $customer_id, $feed_name);

  # Retrieve the attributes of the feed.
  my $ad_customizer_feed_attributes =
    get_feed_attributes($api_client, $customer_id,
    $ad_customizer_feed_resource_name);

  # Map the feed to the ad customizer placeholder fields.
  create_ad_customizer_mapping(
    $api_client, $customer_id,
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
  );

  # Create feed items to be used to customize ads.
  my $feed_item_resource_names = create_feed_items(
    $api_client, $customer_id,
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
  );

  # Set the feed to be used only with the specified ad groups.
  create_feed_item_targets($api_client, $customer_id, $ad_group_ids,
    $feed_item_resource_names);

  # Create ads that use the feed for customization.
  create_ads_with_customizations($api_client, $customer_id, $ad_group_ids,
    $feed_name);

  return 1;
}

# Creates a feed to be used for ad customization.
sub create_ad_customizer_feed {
  my ($api_client, $customer_id, $feed_name) = @_;

  # Create three feed attributes: a name, a price and a date. The attribute names
  # are arbitrary choices and will be used as placeholders in the ad text fields.
  my $name_attribute =
    Google::Ads::GoogleAds::V6::Resources::FeedAttribute->new({
      type => STRING,
      name => "Name"
    });

  my $price_attribute =
    Google::Ads::GoogleAds::V6::Resources::FeedAttribute->new({
      type => STRING,
      name => "Price"
    });

  my $date_attribute =
    Google::Ads::GoogleAds::V6::Resources::FeedAttribute->new({
      type => DATE_TIME,
      name => "Date"
    });

  # Create the feed.
  my $feed = Google::Ads::GoogleAds::V6::Resources::Feed->new({
    name       => $feed_name,
    attributes => [$name_attribute, $price_attribute, $date_attribute],
    origin     => USER
  });

  # Create a feed operation for creating a feed.
  my $feed_operation =
    Google::Ads::GoogleAds::V6::Services::FeedService::FeedOperation->new({
      create => $feed
    });

  # Issue a mutate request to add the feed.
  my $feeds_response = $api_client->FeedService()->mutate({
      customerId => $customer_id,
      operations => [$feed_operation]});

  my $feed_resource_name = $feeds_response->{results}[0]{resourceName};
  printf "Added feed with resource name '%s'.\n", $feed_resource_name;

  return $feed_resource_name;
}

# Retrieves attributes for a feed.
sub get_feed_attributes {
  my ($api_client, $customer_id, $feed_resource_name) = @_;

  my $search_query = "SELECT feed.attributes, feed.name FROM feed " .
    "WHERE feed.resource_name = '$feed_resource_name'";

  my $search_response = $api_client->GoogleAdsService()->search({
    customerId => $customer_id,
    query      => $search_query,
    pageSize   => PAGE_SIZE
  });

  my $feed         = $search_response->{results}[0]{feed};
  my $feed_details = {};
  printf "Found the following attributes for feed with name %s:\n",
    $feed->{name};

  foreach my $feed_attribute (@{$feed->{attributes}}) {
    $feed_details->{$feed_attribute->{name}} = $feed_attribute->{id};
    printf "\t'%s' with id %d and type '%s'\n", $feed_attribute->{name},
      $feed_attribute->{id}, $feed_attribute->{type};
  }
  return $feed_details;
}

# Creates a feed mapping for a given feed.
sub create_ad_customizer_mapping {
  my (
    $api_client, $customer_id,
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
  ) = @_;

  # Map the feed attribute IDs to the field ID constants.
  my $name_field_mapping =
    Google::Ads::GoogleAds::V6::Resources::AttributeFieldMapping->new({
      feedAttributeId => $ad_customizer_feed_attributes->{Name},
      adCustomizerField =>
        Google::Ads::GoogleAds::V6::Enums::AdCustomizerPlaceholderFieldEnum::STRING,
    });

  my $price_field_mapping =
    Google::Ads::GoogleAds::V6::Resources::AttributeFieldMapping->new({
      feedAttributeId => $ad_customizer_feed_attributes->{Price},
      adCustomizerField =>
        Google::Ads::GoogleAds::V6::Enums::AdCustomizerPlaceholderFieldEnum::PRICE,
    });

  my $date_field_mapping =
    Google::Ads::GoogleAds::V6::Resources::AttributeFieldMapping->new({
      feedAttributeId => $ad_customizer_feed_attributes->{Date},
      adCustomizerField =>
        Google::Ads::GoogleAds::V6::Enums::AdCustomizerPlaceholderFieldEnum::DATE,
    });

  # Create the feed mapping.
  my $feed_mapping = Google::Ads::GoogleAds::V6::Resources::FeedMapping->new({
      placeholderType => AD_CUSTOMIZER,
      feed            => $ad_customizer_feed_resource_name,
      attributeFieldMappings =>
        [$name_field_mapping, $price_field_mapping, $date_field_mapping]});

  # Create the operation.
  my $feed_mapping_operation =
    Google::Ads::GoogleAds::V6::Services::FeedMappingService::FeedMappingOperation
    ->new({
      create => $feed_mapping
    });

  # Issue a mutate request to add the feed mapping.
  my $feed_mappings_response = $api_client->FeedMappingService()->mutate({
      customerId => $customer_id,
      operations => [$feed_mapping_operation]});

  # Display the results.
  foreach my $result (@{$feed_mappings_response->{results}}) {
    printf "Created feed mapping with resource name '%s'.\n",
      $result->{resourceName};
  }
}

# Creates two different feed items to enable two different ad customizations.
sub create_feed_items {
  my (
    $api_client, $customer_id,
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
  ) = @_;

  my $feed_item_operations = [];

  my ($sec, $min, $hour, $mday, $mon, $year) = localtime(time);

  push @$feed_item_operations,
    create_feed_item_operation(
    "Mars",
    '$1234.56',
    strftime("%Y%m%d %H%M%S", localtime(mktime(0, 0, 0, 1, $mon, $year))),
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
    );

  push @$feed_item_operations, create_feed_item_operation(
    "Venus",
    '$6543.21',
    # Set the date to the 15th of the current month.
    strftime("%Y%m%d %H%M%S", localtime(mktime(0, 0, 0, 15, $mon, $year))),
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
  );

  # Add the feed items.
  my $feed_items_response = $api_client->FeedItemService()->mutate({
    customerId => $customer_id,
    operations => $feed_item_operations
  });

  my $feed_item_resource_names = [];
  # Displays the results.
  foreach my $result (@{$feed_items_response->{results}}) {
    printf "Created feed item with resource name '%s'.\n",
      $result->{resourceName};
    push @$feed_item_resource_names, $result->{resourceName};
  }

  return $feed_item_resource_names;
}

# Creates a FeedItemOperation.
sub create_feed_item_operation {
  my (
    $name, $price, $date,
    $ad_customizer_feed_resource_name,
    $ad_customizer_feed_attributes
  ) = @_;

  my $name_attribute_value =
    Google::Ads::GoogleAds::V6::Resources::FeedItemAttributeValue->new({
      feedAttributeId => $ad_customizer_feed_attributes->{Name},
      stringValue     => $name
    });

  my $price_attribute_value =
    Google::Ads::GoogleAds::V6::Resources::FeedItemAttributeValue->new({
      feedAttributeId => $ad_customizer_feed_attributes->{Price},
      stringValue     => $price
    });

  my $date_attribute_value =
    Google::Ads::GoogleAds::V6::Resources::FeedItemAttributeValue->new({
      feedAttributeId => $ad_customizer_feed_attributes->{Date},
      stringValue     => $date
    });

  my $feed_item = Google::Ads::GoogleAds::V6::Resources::FeedItem->new({
      feed => $ad_customizer_feed_resource_name,
      attributeValues =>
        [$name_attribute_value, $price_attribute_value, $date_attribute_value]}
  );

  return
    Google::Ads::GoogleAds::V6::Services::FeedItemService::FeedItemOperation->
    new({
      create => $feed_item
    });
}

# Restricts the feed items to work only with a specific ad group; this prevents
# the feed items from being used elsewhere and makes sure they are used only for
# customizing a specific ad group.
sub create_feed_item_targets {
  my ($api_client, $customer_id, $ad_group_ids, $feed_item_resource_names) = @_;

  # Bind each feed item to a specific ad group to make sure it will only be used
  # to customize ads inside that ad group; using the feed item elsewhere will
  # result in an error.
  for (my $i = 0 ; $i < scalar @$feed_item_resource_names ; $i++) {
    my $feed_item_resource_name = $feed_item_resource_names->[$i];
    my $ad_group_id             = $ad_group_ids->[$i];

    my $feed_item_target =
      Google::Ads::GoogleAds::V6::Resources::FeedItemTarget->new({
        feedItem => $feed_item_resource_name,
        adGroup  => Google::Ads::GoogleAds::V6::Utils::ResourceNames::ad_group(
          $customer_id, $ad_group_id
        )});

    # Create the operation.
    my $feed_item_target_operation =
      Google::Ads::GoogleAds::V6::Services::FeedItemTargetService::FeedItemTargetOperation
      ->new({
        create => $feed_item_target
      });

    # Issue a mutate request to add the feed item target.
    my $feed_item_targets_response =
      $api_client->FeedItemTargetService()->mutate({
        customerId => $customer_id,
        operations => [$feed_item_target_operation]});

    my $feed_item_target_resource_name =
      $feed_item_targets_response->{results}[0]{resourceName};
    printf "Added feed item target with resource name '%s'.\n",
      $feed_item_target_resource_name;
  }
}

# Creates expanded text ads that use the ad customizer feed to populate the placeholders.
sub create_ads_with_customizations {
  my ($api_client, $customer_id, $ad_group_ids, $feed_name) = @_;

  my $expanded_text_ad_info =
    Google::Ads::GoogleAds::V6::Common::ExpandedTextAdInfo->new({
      headlinePart1 => "Luxury cruise to {=$feed_name.Name}",
      headlinePart2 => "Only {=$feed_name.Price}",
      description   => "Offer ends in {=countdown($feed_name.Date)}!"
    });

  my $ad = Google::Ads::GoogleAds::V6::Resources::Ad->new({
      expandedTextAd => $expanded_text_ad_info,
      finalUrls      => ["http://www.example.com"]});

  my $ad_group_ad_operations = [];
  foreach my $ad_group_id (@$ad_group_ids) {
    my $ad_group_ad = Google::Ads::GoogleAds::V6::Resources::AdGroupAd->new({
        ad      => $ad,
        adGroup => Google::Ads::GoogleAds::V6::Utils::ResourceNames::ad_group(
          $customer_id, $ad_group_id
        )});

    push @$ad_group_ad_operations,
      Google::Ads::GoogleAds::V6::Services::AdGroupAdService::AdGroupAdOperation
      ->new({
        create => $ad_group_ad
      });
  }

  # Issue a mutate request to add the ads.
  my $ad_group_ads_response = $api_client->AdGroupAdService()->mutate({
    customerId => $customer_id,
    operations => $ad_group_ad_operations
  });

  my $ad_group_ad_results = $ad_group_ads_response->{results};
  printf "Added %d ads:\n", scalar @$ad_group_ad_results;
  foreach my $ad_group_ad_result (@$ad_group_ad_results) {
    printf "Added an ad with resource name '%s'.\n",
      $ad_group_ad_result->{resourceName};
  }
}

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

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

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

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s"  => \$customer_id,
  "ad_group_ids=i" => \@$ad_group_ids
);
$ad_group_ids = [$ad_group_id_1, $ad_group_id_2] unless @$ad_group_ids;

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2) if not check_params($customer_id, $ad_group_ids);

# Call the example.
add_ad_customizer($api_client, $customer_id =~ s/-//gr, $ad_group_ids);

=pod

=head1 NAME

add_ad_customizer

=head1 DESCRIPTION

Adds an ad customizer feed and associates it with the customer. Then it adds an
ad that uses the feed to populate dynamic data.

=head1 SYNOPSIS

add_ad_customizer.pl [options]

    -help                       Show the help message.
    -customer_id                The Google Ads customer ID.
    -ad_group_ids               The ad group IDs to bind the feed items to.

=cut