4.2 광고 캠페인 만들기
소개
이전 단계를 완료하면 판매자가 실적 최대화 소매 캠페인을 만들고 머신러닝을 사용하여 각 광고를 게재하는 데 가장 현명한 결정을 내릴 수 있도록 설정할 수 있습니다.
광고주가 Google Ads UI에서 실적 최대화 캠페인을 만들기 위해 수동으로 실행해야 하는 작업을 알아보려면 실적 최대화 캠페인 만들기를 참고하세요. 이 섹션에서는 Google Ads API를 사용하여 광고주가 쇼핑 광고를 게재하는 실적 최대화 캠페인을 만들 수 있도록 하는 방법을 설명합니다.
이 섹션에서는 구현 복잡성을 최소화하면서 판매자에게 가장 큰 가치를 제공하는 기본 캠페인 기능만 다룹니다. 각 캠페인에 대해 판매자에게 다음 세 가지 맞춤설정 옵션을 제공하는 것이 좋습니다.
캠페인 이름: 보고서에 캠페인이 표시되는 방식입니다.
예산: 캠페인에서 평균적으로 지출해야 하는 금액입니다.
지역 타겟팅: 캠페인에서 광고를 게재해야 하는 위치입니다.
또한 백엔드에서 아래에 나열된 몇 가지 매개변수를 더 지정해야 합니다.
입찰 전략
자동 입찰 전략은 입찰가를 설정할 때 캠페인에서 최대화할 항목을 정의합니다. 실적 최대화 캠페인에서는 두 가지 입찰 전략을 사용할 수 있습니다.
전환수 최대화: 캠페인은 지정된 예산으로 달성된 전환수 (예: 구매 수)를 최대화하려고 시도합니다.
전환 가치 극대화 (권장): 캠페인은 전환 추적 설정의 일부로 정의한 대로 지정된 예산에 대해 전환의 총 금전적 가치 (예: 구매에 지출된 금액)를 극대화하려고 시도합니다.
판매자의 기술적 복잡성과 환경을 간소화하려면 타겟 ROAS 없이 판매자를 대신하여 '전환 가치 극대화'를 선택하는 것이 좋습니다. 이는 리디렉션 옵션을 통해 사용할 수 있는 것과 일치합니다.
애셋 그룹 및 등록정보 그룹
애셋 그룹은 제품, 이미지, 동영상과 같은 캠페인의 광고 요소가 저장되는 곳입니다. 기본 필러에서는 판매자 센터의 제품만 다룹니다. 따라서 캠페인당 애셋 그룹이 하나만 있으면 되며, 이 애셋 그룹은 판매자를 대신하여 백엔드에서 만들 수 있습니다.
등록정보 그룹을 사용하면 판매자가 캠페인의 제품을 그룹으로 세분화할 수 있습니다(예: 특정 제품만 광고하기 위해). 이는 기본 필러에서는 권장되지 않는 고급 기능이지만 모든 캠페인에는 등록정보 그룹이 하나 이상 있어야 합니다. 따라서 캠페인당 등록정보 그룹이 하나만 있으면 되며, 이는 판매자를 대신하여 백엔드에서 만들 수 있습니다.
UX 가이드
이 섹션의 첫 번째 부분에서는 Google Ads 온보딩 흐름의 일부로 첫 번째 캠페인을 만드는 시나리오를 설명합니다. 두 번째 부분에서는 추가 캠페인을 만드는 방법을 설명합니다. 이는 판매자가 캠페인을 지역 타겟팅하도록 허용하는 경우에 특히 중요합니다.
Google Ads 온보딩 중 캠페인 만들기
Google Ads 계정을 연결하면 사용자에게 실적 최대화 캠페인을 설정하라는 메시지가 표시됩니다.
캠페인 이름은 의미 있는 이름(예: 스토어빌더 이름의 실적 최대화 캠페인)으로 자동 입력될 수 있습니다.
광고가 어떻게 표시될지 미리 볼 수 있는 썸네일과 실적 최대화 캠페인의 작동 방식에 관한 간단한 설명을 포함하는 것도 좋습니다. Google Ads API는 광고 미리보기를 생성하는 방법을 제공하지 않으므로 직접 구현해야 합니다. 광고 유형의 디자인이 변경될 수 있으므로 픽셀 정확하게 만들 필요는 없습니다.
판매자가 쿠폰을 사용할 수 있는 경우 메시지에 이를 명시해야 합니다.
이 단계에서 쇼핑 광고 정책 링크도 제공해야 합니다.
원하는 경우 판매자가 캠페인을 지역 타겟팅하도록 허용할 수 있습니다.
판매자가 캠페인 설정 단계에 도달하면 다음 두 가지 시나리오가 가능합니다.
결제가 설정되지 않았습니다. 이 절차의 일환으로 새 Google Ads 계정이 생성된 경우에는 항상 이 문제가 발생합니다.
연결된 Google Ads 계정에 이미 결제가 설정되어 있습니다 (예: 판매자가 다른 유형의 캠페인을 운영하는 데 이미 사용한 Google Ads 계정을 재사용함).
결제 시나리오가 손상된 경우 Google Ads 계정 연결 단계에서 이미 감지되었을 것입니다. 시나리오 1과 2의 유일한 차이점은 기본 작업의 동작과 클릭 유도 문구 (CTA)입니다.
시나리오 1의 경우 CTA는 '결제 설정'이라고 표시되어야 하며 결제 팝업을 열어야 합니다. 결제 정보를 제공하고 팝업이 닫히면 '검토 및 설정 완료' 단계로 진행합니다.
시나리오 2의 경우 CTA는 '계속'이라고 표시되어야 하며 '설정 검토 및 완료' 단계로 바로 진행되어야 합니다.
다음은 시나리오 1의 캠페인 설정 단계가 표시되는 방식의 예입니다.
판매자에게 캠페인 이름, 포함된 제품, 예산에 관한 추가 정보를 제공해야 합니다. 아래 예는 이와 같은 모습을 보여줍니다.
판매자가 광고하는 국가의 권장 기준점보다 낮은 예산을 입력하는 경우 최소 예산 추천이 포함된 알림을 제공합니다. 아래는 추천 내용의 예시입니다.
캠페인 설정 단계를 완료하고 결제 정보를 제공한 후(필요한 경우) 사용자는 '검토 및 완료' 단계로 진행합니다. 사용자는 캠페인을 시작하기 전에 캠페인 설정을 마지막으로 한 번 더 검토할 수 있습니다. 이 단계에서 UI는 다음과 같이 표시될 수 있습니다.
온보딩 흐름을 종료한 후에는 다음에 발생할 상황에 관한 미리보기를 사용자에게 표시하는 것이 좋습니다. 아래 예를 참고하세요.
온보딩을 종료하면 판매자가 개요 페이지로 이동해야 합니다. 아래 예는 이와 같은 모습을 보여줍니다.
디스플레이 캠페인 및 추가 캠페인 만들기
판매자가 기존 실적 최대화 캠페인을 보유하고 있거나, 특히 판매자가 지역 타겟팅을 허용하는 경우 2개 이상의 캠페인을 만들고 싶어 할 수 있으므로 UI는 여러 캠페인을 표시할 수 있어야 합니다. 판매자가 캠페인을 지역 타겟팅하도록 허용하는 예는 다음과 같습니다. 지역 타겟팅은 항상 판매자가 광고하는 국가보다 좁아야 합니다.
판매자가 운영하는 캠페인이 두 개 이상인 경우 개요 페이지에서 광고 측정항목을 집계합니다. 아래 예는 이와 같은 모습을 보여줍니다.
Google Ads 세부정보 보기에 개별 캠페인을 표시합니다. 아래는 이러한 예시입니다.
Google Ads 세부정보 보기는 추가 캠페인을 만들 때도 시작점으로 권장됩니다. 이 흐름이 어떻게 표시되는지 보여주는 예는 아래 애니메이션을 참고하세요. 예산 정보는 이 가이드 앞부분에서 다루었으므로 애니메이션에서는 예산 정보 입력을 건너뜁니다.
기술 안내
Google Ads 계정을 판매자 센터 계정에 연결한 후 다음 단계에 따라 소매업체용 실적 최대화 캠페인을 만들 수 있습니다.
- 예산 작업 만들기
- 캠페인 작업 만들기
- 캠페인 기준 만들기
- 등록정보 그룹 필터로 애셋 그룹 만들기
- 전환 목표 재정의
- 모든 작업을 함께 전송
실적 최대화 캠페인 관리에는 다음이 적용됩니다.
Google Ads API 버전 9 이상을 사용해야 합니다.
모든 변형 작업을 하나의 단일 변형 요청으로 전송해야 합니다. 따라서 먼저 모든 수정 작업을 만든 다음 단일 수정 요청으로 Google Ads API에 모두 전송해야 합니다.
모든 작업을 포함하는 단일 변형 요청을 전송하고 이러한 작업이 이러한 임시 ID를 사용하여 모두 함께 속한다는 것을 Google에 알리기 때문에 생성하는 모든 작업에 임시 ID를 사용합니다.
조합 이름
판매자가 Google Ads 플랫폼에 직접 액세스할 가능성이 있는 경우 파트너가 프로그래매틱 방식으로 캠페인을 만들었음을 명확하게 알 수 있도록 캠페인 이름을 더 신중하게 지정하는 것이 좋습니다. 이렇게 하면 판매자에게 Google Ads 플랫폼이 아닌 판매자 플랫폼 내에서 캠페인을 수정하는 것이 가장 좋다는 신호를 보낼 수 있습니다. 예를 들어 판매자가 이름을 맞춤설정하도록 허용하더라도 '플랫폼 X에서 생성'을 앞에 추가하거나 뒤에 추가하는 것이 좋습니다.
1단계: 예산 작업 만들기
코드 샘플은 캠페인 예산 만들기에서 확인할 수 있으므로 여기서는 가장 중요한 사항만 강조 표시합니다.
실적 최대화 캠페인에는 다른 모든 캠페인과 마찬가지로 예산이 필요하지만 다음과 같은 제한사항이 있습니다.
- 예산에 일일 예산 기간이 있어야 합니다.
- 예산을 공유할 수 없습니다.
예산의 게재 방법은 일반입니다. 일반 게재는 하루 종일 예상되는 광고 실적을 고려하여 평균 일일 예산 내에서 실적을 극대화하는 데 더 효과적입니다.
이 섹션에서 앞에서 언급했듯이, 만드는 모든 작업에 임시 ID를 사용해야 합니다. 따라서 코드 샘플에서
_BUDGET_TEMPORARY_ID
가 예산의 리소스 이름에 설정되어 있으므로 나중에 캠페인에서 참조할 수 있습니다.
2단계: 캠페인 및 입찰 전략 만들기
캠페인 및 입찰 전략 만들기의 코드 샘플을 참고하여 표준 실적 최대화 캠페인을 만드는 방법을 알아보세요. 다음은 몇 가지 추가 참고사항입니다.
코드 샘플은 입찰 전략에 전환 가치 극대화를 사용합니다. 이는 기반 요소에서 사용하는 것이 좋습니다. 전환 가치 극대화를 사용하면 타겟 ROAS (광고 투자수익)를 지정할 수 있습니다. 타겟 ROAS 값을 지정하지 않더라도 Java를 사용하지 않는 한 설정해야 합니다. Java의 경우 다음과 같습니다.
setMaximizeConversionValue(MaximizeConversionValue.newBuilder().build())
향후 버전에서는 타겟 CPA를 설정할 수 있는 전환수 최대화를 다른 입찰 전략 옵션으로 사용하는 것이 좋습니다.
URL 확장은 캠페인이 광고주의 판매자 센터에 지정된 URL뿐만 아니라 전체 도메인을 타겟팅할 수 있도록 하는 것입니다. 캠페인의 도달범위와 최종 실적이 제한되지 않도록 최종 URL 확장을 사용하는 것이 좋습니다. 그렇다고 해서 판매자 웹사이트의 모든 페이지에 광고가 게재될 수 있다는 의미는 아닙니다.
start_date
과end_date
은 선택사항입니다.
소매업체용 실적 최대화 캠페인의 경우 다음 필드가 포함된 ShoppingSetting
를 만들어 캠페인에 추가해야 합니다.
여기에서 사용할 판매자 센터 계정과 해당 계정에서 사용할 제품 피드를 지정합니다. 실적 최대화 소매업체 캠페인 만들기에 설명된 대로 다음 ShoppingSetting
필드를 설정해야 합니다.
merchant_id
(필수): 광고할 제품이 포함된 계정의 판매자 센터 ID입니다.feed_label
(필수 아닐 수 있음): 기존 제품 피드가 있는 기존 판매자 센터 계정을 사용하는 판매자가 있을 수 있는 경우 feed_label을 사용하여 캠페인에서 내가 만든 제품 피드를 사용하도록 지정해야 합니다. 이는 Content API의DatafeedTarget
에 매핑됩니다.
다음은 소매업체를 위한 실적 최대화 캠페인 및 입찰 전략을 만드는 작업의 예입니다. 여기서는 판매자 센터 계정에 하나의 제품 피드 (관리하는 피드)만 있다고 가정하여 feed_label을 설정하지 않습니다.
Python
mutate_operation = googleads_client.get_type("MutateOperation")
campaign = mutate_operation.campaign_operation.create
campaign.name = f"Performance Max campaign #{uuid4()}"
campaign.status = googleads_client.enums.CampaignStatusEnum.ENABLED
campaign.advertising_channel_type = ( googleads_client.enums.AdvertisingChannelTypeEnum.PERFORMANCE_MAX
)
# Add Merchant Center ID to create a Performance Max for Retail
# campaign. This ID should be linked to the Ads account
# where you are creating the campaign
campaign.shopping_setting.merchant_id = int(google_merchant_id)
campaign.maximize_conversion_value.target_roas = None
# Set the Final URL expansion opt out. This option is specific to
# Performance Max campaigns. If opted out (True), only the
# final URLs in the asset group or URLs specified in the
# advertiser's Google Merchant Center or business data feeds are
# targeted.
# If opted in (False), the entire domain will be targeted.
# For best results, set this value to false to opt in and allow
# URL expansions. You can optionally add exclusions to limit
# traffic to parts of your website.
campaign.url_expansion_opt_out = False
# Assign the resource name with a temporary ID.
campaign_service = googleads_client.get_service("CampaignService")
campaign.resource_name = campaign_service.campaign_path(
customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID
)
# Set the budget using the given budget resource name.
campaign.campaign_budget = campaign_service.campaign_budget_path(
customer_id, _BUDGET_TEMPORARY_ID
)
print("mutate_operation for step 2 - campaign and bidding strategy creation")
print(mutate_operation)
return mutate_operation
3단계: 캠페인 기준 만들기
코드 샘플은 캠페인 기준 만들기에서 확인할 수 있습니다. 다음은 가장 중요한 사항입니다.
코드 샘플에서 볼 수 있듯이 먼저 필요한 모든 서비스를 시작해야 캠페인 기준 LOCATION 및 LANGUAGE를 구성할 수 있습니다.
LOCATION 캠페인 기준을 설정할 때
campaign_criterion.negative
를 True로 설정하여 제외하려는 위치의 제외 타겟을 추가할 수 있습니다.2단계의 임시 ID와 동일한 ID를 사용하여 Google에 이 기준을 만들 캠페인을 알려야 합니다.
4단계: 등록정보 그룹 필터로 애셋 그룹 만들기
실적 최대화 캠페인 애셋 그룹 만들기에 설명된 대로 애셋 그룹은 테마를 중심으로 하거나 타겟 잠재고객과 관련된 확장 소재의 모음입니다. 애셋 그룹은 모든 광고를 조합하고 광고 목표에 적합한 모든 광고 형식에 대한 인벤토리를 구축하는 데 사용됩니다.
쇼핑 등록정보 그룹을 사용하여 제품을 그룹으로 분할하여 제품 세트를 포함하거나 제외할 수 있습니다. 이를 통해 캠페인에 표시할 수 있는 제품 집합을 필터링할 수 있습니다.
실적 최대화 등록정보 그룹에서 여러 제품 등록정보 그룹을 만드는 코드 샘플을 확인할 수 있지만, 기반 기둥의 경우 판매자의 모든 제품을 타겟팅하는 단일 캠페인을 설정하는 것이 좋습니다. 따라서 아래에 더 간단한 코드 샘플이 포함되어 있습니다. 모든 제품이 포함된 루트 AssetGroupListingGroupFilter
노드를 포함해야 합니다. 그렇지 않으면 광고가 게재되지 않습니다.
Python
googleads_service = googleads_client.get_service("GoogleAdsService")
operations = []
# Create the AssetGroup
mutate_operation = googleads_client.get_type("MutateOperation")
asset_group = mutate_operation.asset_group_operation.create
asset_group.name = f"Performance Max asset group #{uuid4()}"
asset_group.campaign = googleads_service.campaign_path(
customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID
)
asset_group.status = googleads_client.enums.AssetGroupStatusEnum.ENABLED
asset_group.resource_name = googleads_service.asset_group_path(
customer_id,
_ASSET_GROUP_TEMPORARY_ID,
)
operations.append(mutate_operation)
# Create a listing group and link it to the AssetGroup.
# The way you link a listing group to an AssetGroup is different
# than when you are linking headlines, descriptions, images, etc
# where you set the field_type of the asset_group_asset as HEADLINE, for example.
# https://developers.google.com/google-ads/api/reference/rpc/v13/AssetGroupListingGroupFilter
mutate_operation = googleads_client.get_type("MutateOperation")
asset_group_listing_group = mutate_operation.asset_group_listing_group_filter_operation.create
asset_group_listing_group.asset_group = googleads_service.asset_group_path(
customer_id,
_ASSET_GROUP_TEMPORARY_ID,
)
asset_group_listing_group.type_ = googleads_client.enums.ListingGroupFilterTypeEnum.UNIT_INCLUDED
# here are other options for ListingGroupFilterTypeEnum
# https://developers.google.com/google-ads/api/reference/rpc/v13/ListingGroupFilterTypeEnum.ListingGroupFilterType
asset_group_listing_group.vertical = googleads_client.enums.ListingGroupFilterVerticalEnum.SHOPPING
operations.append(mutate_operation)
print("mutate_operation for step 4 - create asset group and product group attached to that asset group")
print(operations)
return operations
애셋 그룹에 광고 제목, 내용 입력란, 이미지를 추가하여 캠페인 실적을 개선할 수 있습니다. 하지만 소매업체용 실적 최대화 캠페인에는 이 요건이 필요하지 않으므로 기본 필러에는 권장하지 않습니다.
5단계: 전환 목표 덮어쓰기
전환 추적 섹션에 설명된 대로 판매자가 기존 Google Ads 계정을 사용하도록 허용하는 경우 캠페인 수준에서 전환 목표를 설정하는 것이 좋습니다. 고객 또는 계정 수준에서 전환 목표를 재정의하면 됩니다. 캠페인 수준 전환 목표가 설정되지 않으면 캠페인이 고객 수준 전환 목표로 기본 설정됩니다.
코드 샘플은 캠페인 전환 목표 업데이트에서 확인할 수 있습니다. 먼저 계정의 전환 목표를 가져와야 합니다. 그런 다음 전환 목표를 반복하고 각 목표가 온라인 판매를 유도하는지 확인할 수 있습니다 (즉, 카테고리 PURCHASE, 출처 WEBSITE). 그렇지 않은 경우 입찰 가능 여부를 False로 설정합니다. 맞다면 입찰 가능 여부를 True로 설정합니다. 이렇게 하면 실적 최대화 캠페인이 온라인 판매 증대에 최적화됩니다.
6단계: 모든 변형 작업을 함께 전송
마지막으로 모든 변형 작업을 함께 전송해야 합니다. 아래 코드 샘플에서 볼 수 있듯이 연산은 서로 종속되므로 연산 순서가 중요합니다. 예를 들어 performance_max_campaign_operation
은 campaign_budget_operation
의 데이터를 사용하고 campaign_criterion_operations
은 performance_max_campaign_operation
의 데이터를 사용합니다.
Python
ga_service = googleads_client.get_service("GoogleAdsService")
response = ga_service.mutate(
customer_id=customer_id,
mutate_operations=[
# It's important to create the entities in this order
# because they depend on each other
campaign_budget_operation, # Step 1
performance_max_campaign_operation, # Step 2
# Expand the list of multiple operations into the list of
# other mutate operations
*campaign_criterion_operations, # Step 3
*asset_group_operations, # Step 4
*conversion_goal_operations, # Step 5
],
)
캠페인이 성공적으로 생성되면 API는 다음과 같은 결과를 반환합니다.
Python
mutate_operation_responses {
campaign_budget_result {
resource_name: "customers/7244690427/campaignBudgets/10689184019"
}
}
mutate_operation_responses {
campaign_result {
resource_name: "customers/7244690427/campaigns/16822128600"
}
}
mutate_operation_responses {
campaign_criterion_result {
resource_name: "customers/7244690427/campaignCriteria/16822128600~20009"
}
}
mutate_operation_responses {
campaign_criterion_result {
resource_name: "customers/7244690427/campaignCriteria/16822128600~20024"
}
}
mutate_operation_responses {
campaign_criterion_result {
resource_name: "customers/7244690427/campaignCriteria/16822128600~1003"
}
}
mutate_operation_responses {
asset_group_result {
resource_name: "customers/7244690427/assetGroups/6444254998"
}
}
mutate_operation_responses {
asset_group_listing_group_filter_result {
resource_name: "customers/7244690427/assetGroupListingGroupFilters/6444254998~6454725458"
}
}
mutate_operation_responses {
campaign_conversion_goal_result {
resource_name: "customers/7244690427/campaignConversionGoals/16822128600~PURCHASE~WEBSITE"
}
}
캠페인 설정을 수정할 때와 보고 목적으로 필요하므로 캠페인 및 예산의 resource_name
를 저장합니다.
자바
// Copyright 2021 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.shoppingads; import static com.google.ads.googleads.examples.utils.CodeSampleHelper.getPrintableDateTime; import static com.google.ads.googleads.v21.enums.EuPoliticalAdvertisingStatusEnum.EuPoliticalAdvertisingStatus.DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING; 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.lib.utils.FieldMasks; import com.google.ads.googleads.v21.common.ImageAsset; import com.google.ads.googleads.v21.common.LanguageInfo; import com.google.ads.googleads.v21.common.LocationInfo; import com.google.ads.googleads.v21.common.MaximizeConversionValue; import com.google.ads.googleads.v21.common.TextAsset; import com.google.ads.googleads.v21.enums.AdvertisingChannelTypeEnum.AdvertisingChannelType; import com.google.ads.googleads.v21.enums.AssetFieldTypeEnum.AssetFieldType; import com.google.ads.googleads.v21.enums.AssetGroupStatusEnum.AssetGroupStatus; import com.google.ads.googleads.v21.enums.BudgetDeliveryMethodEnum.BudgetDeliveryMethod; import com.google.ads.googleads.v21.enums.CampaignStatusEnum.CampaignStatus; import com.google.ads.googleads.v21.enums.ConversionActionCategoryEnum.ConversionActionCategory; import com.google.ads.googleads.v21.enums.ConversionOriginEnum.ConversionOrigin; import com.google.ads.googleads.v21.enums.ListingGroupFilterListingSourceEnum.ListingGroupFilterListingSource; import com.google.ads.googleads.v21.enums.ListingGroupFilterTypeEnum.ListingGroupFilterType; import com.google.ads.googleads.v21.errors.GoogleAdsError; import com.google.ads.googleads.v21.errors.GoogleAdsException; import com.google.ads.googleads.v21.resources.Asset; import com.google.ads.googleads.v21.resources.AssetGroup; import com.google.ads.googleads.v21.resources.AssetGroupAsset; import com.google.ads.googleads.v21.resources.AssetGroupListingGroupFilter; import com.google.ads.googleads.v21.resources.Campaign; import com.google.ads.googleads.v21.resources.Campaign.ShoppingSetting; import com.google.ads.googleads.v21.resources.CampaignAsset; import com.google.ads.googleads.v21.resources.CampaignBudget; import com.google.ads.googleads.v21.resources.CampaignConversionGoal; import com.google.ads.googleads.v21.resources.CampaignCriterion; import com.google.ads.googleads.v21.resources.CustomerConversionGoal; import com.google.ads.googleads.v21.services.AssetGroupAssetOperation; import com.google.ads.googleads.v21.services.AssetGroupListingGroupFilterOperation; import com.google.ads.googleads.v21.services.AssetGroupOperation; import com.google.ads.googleads.v21.services.AssetOperation; import com.google.ads.googleads.v21.services.CampaignAssetOperation; import com.google.ads.googleads.v21.services.CampaignBudgetOperation; import com.google.ads.googleads.v21.services.CampaignConversionGoalOperation; import com.google.ads.googleads.v21.services.CampaignCriterionOperation; import com.google.ads.googleads.v21.services.CampaignOperation; import com.google.ads.googleads.v21.services.GoogleAdsRow; import com.google.ads.googleads.v21.services.GoogleAdsServiceClient; import com.google.ads.googleads.v21.services.GoogleAdsServiceClient.SearchPagedResponse; import com.google.ads.googleads.v21.services.MutateGoogleAdsResponse; import com.google.ads.googleads.v21.services.MutateOperation; import com.google.ads.googleads.v21.services.MutateOperationResponse; import com.google.ads.googleads.v21.utils.ResourceNames; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.FieldDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.stream.Collectors; import org.joda.time.DateTime; /** * This example shows how to create a Performance Max retail campaign. * * <p>This will be created for "All products". * * <p>For more information about Performance Max retail campaigns, see * https://developers.google.com/google-ads/api/docs/performance-max/retail * * <p>Prerequisites: - You need to have access to a Merchant Center account. You can find * instructions to create a Merchant Center account here: * https://support.google.com/merchants/answer/188924. This account must be linked to your Google * Ads account. The integration instructions can be found at: * https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center - You need your * Google Ads account to track conversions. The different ways to track conversions can be found * here: https://support.google.com/google-ads/answer/1722054. - You must have at least one * conversion action in the account. For more about conversion actions, see * https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions */ public class AddPerformanceMaxRetailCampaign { // We specify temporary IDs that are specific to a single mutate request. Temporary IDs are always // negative and unique within one mutate request. // // <p>See https://developers.google.com/google-ads/api/docs/mutating/best-practices for further // details. // // <p>These temporary IDs are fixed because they are used in multiple places. private static final int BUDGET_TEMPORARY_ID = -1; private static final int PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID = -2; private static final int ASSET_GROUP_TEMPORARY_ID = -3; // There are also entities that will be created in the same request but do not // need to be fixed temporary IDs because they are referenced only once. private static long temporaryId = ASSET_GROUP_TEMPORARY_ID - 1; private static class AddPerformanceMaxRetailCampaignParams extends CodeSampleParams { @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true) private Long customerId; @Parameter( names = ArgumentNames.MERCHANT_CENTER_ACCOUNT_ID, required = true, description = "The Merchant Center account ID.") private long merchantCenterAccountId; @Parameter( names = ArgumentNames.SALES_COUNTRY, description = "The sales country of products to include in the campaign.") private String salesCountry = "US"; @Parameter( names = ArgumentNames.FINAL_URL, required = true, description = "The final url for the generated ads. Must have the same domain as the Merchant Center" + " account.") private String finalUrl; @Parameter( names = "--brandGuidelinesEnabled", arity = 1, description = "A boolean value indicating if the created campaign is enabled for brand guidelines") private boolean brandGuidelinesEnabled = false; } public static void main(String[] args) throws IOException { AddPerformanceMaxRetailCampaignParams params = new AddPerformanceMaxRetailCampaignParams(); 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.merchantCenterAccountId = Long.parseLong("INSERT_MERCHANT_CENTER_ACCOUNT_ID_HERE"); params.finalUrl = "INSERT_FINAL_URL_HERE"; // Optionally set the sales country. // params.salesCountry = "INSERT_SALES_COUNTRY_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 AddPerformanceMaxRetailCampaign() .runExample( googleAdsClient, params.customerId, params.merchantCenterAccountId, params.salesCountry, params.finalUrl, params.brandGuidelinesEnabled); } 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 customerId the client customer ID. * @param merchantCenterAccountId the Merchant Center account ID. * @param salesCountry sales country of products to include in the campaign. * @param finalUrl final URL for the asset group of the campaign. * @param brandGuidelinesEnabled indicates if the campaign is enabled for brand guidelines. */ private void runExample( GoogleAdsClient googleAdsClient, long customerId, long merchantCenterAccountId, String salesCountry, String finalUrl, boolean brandGuidelinesEnabled) throws IOException { // This campaign will override the customer conversion goals. For more information see // https://developers.google.com/google-ads/api/docs/conversions/goals/campaign-goals. // Retrieve the current list of customer conversion goals. List<CustomerConversionGoal> customerConversionGoals = getCustomerConversionGoals(googleAdsClient, customerId); // Performance Max campaigns require that repeated assets such as headlines // and descriptions be created before the campaign. // For the list of required assets for a Performance Max campaign, see // https://developers.google.com/google-ads/api/docs/performance-max/assets // // Creates the headlines. List<String> headlines = ImmutableList.of("Travel", "Travel Reviews", "Book travel"); List<String> headlineAssetResourceNames = createMultipleTextAssets(googleAdsClient, customerId, headlines); // Creates the descriptions. List<String> descriptions = ImmutableList.of("Take to the air!", "Fly to the sky!"); List<String> descriptionAssetResourceNames = createMultipleTextAssets(googleAdsClient, customerId, descriptions); // The below methods create and return MutateOperations that we later // provide to the GoogleAdsService.Mutate method in order to create the // entities in a single request. Since the entities for a Performance Max // campaign are closely tied to one-another, it's considered a best practice // to create them in a single Mutate request, so they all complete // successfully or fail entirely, leaving no orphaned entities. See: // https://developers.google.com/google-ads/api/docs/mutating/overview List<MutateOperation> mutateOperations = new ArrayList<>(); mutateOperations.add(createCampaignBudgetOperation(customerId)); mutateOperations.add( createPerformanceMaxCampaignOperation( customerId, merchantCenterAccountId, salesCountry, brandGuidelinesEnabled)); mutateOperations.addAll(createCampaignCriterionOperations(customerId)); String assetGroupResourceName = ResourceNames.assetGroup(customerId, ASSET_GROUP_TEMPORARY_ID); mutateOperations.add(createAssetGroupOperation(customerId, assetGroupResourceName, finalUrl)); // Retail Performance Max campaigns require listing groups, which are created via the // AssetGroupListingGroupFilter resource. mutateOperations.add(createAssetGroupListingGroupFilterOperation(assetGroupResourceName)); mutateOperations.addAll( createAssetAndAssetGroupAssetOperations( customerId, assetGroupResourceName, headlineAssetResourceNames, descriptionAssetResourceNames, brandGuidelinesEnabled)); mutateOperations.addAll(createConversionGoalOperations(customerId, customerConversionGoals)); try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { MutateGoogleAdsResponse response = googleAdsServiceClient.mutate(Long.toString(customerId), mutateOperations); printResponseDetails(response); } } /** Creates a MutateOperation that creates a new CampaignBudget. */ private MutateOperation createCampaignBudgetOperation(long customerId) { CampaignBudget campaignBudget = CampaignBudget.newBuilder() .setName("Performance Max retail campaign budget #" + getPrintableDateTime()) // The budget period already defaults to DAILY. .setAmountMicros(50_000_000) .setDeliveryMethod(BudgetDeliveryMethod.STANDARD) // A Performance Max campaign cannot use a shared campaign budget. .setExplicitlyShared(false) // Set a temporary ID in the budget's resource name, so it can be referenced // by the campaign in later steps. .setResourceName(ResourceNames.campaignBudget(customerId, BUDGET_TEMPORARY_ID)) .build(); return MutateOperation.newBuilder() .setCampaignBudgetOperation( CampaignBudgetOperation.newBuilder().setCreate(campaignBudget).build()) .build(); } /** Creates a MutateOperation that creates a new Performance Max campaign. */ private MutateOperation createPerformanceMaxCampaignOperation( long customerId, long merchantCenterAccountId, String salesCountry, boolean brandGuidelinesEnabled) { Campaign performanceMaxCampaign = Campaign.newBuilder() .setName("Performance Max retail campaign #" + getPrintableDateTime()) // Sets the campaign status as PAUSED. The campaign is the only entity in // the mutate request that should have its status set. .setStatus(CampaignStatus.PAUSED) // All Performance Max campaigns have an advertising_channel_type of // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. .setAdvertisingChannelType(AdvertisingChannelType.PERFORMANCE_MAX) // Bidding strategy must be set directly on the campaign. // Setting a portfolio bidding strategy by resource name is not supported. // Max Conversion and Maximize Conversion Value are the only strategies // supported for Performance Max campaigns. // An optional ROAS (Return on Advertising Spend) can be set for // maximize_conversion_value. The ROAS value must be specified as a ratio in // the API. It is calculated by dividing "total value" by "total spend". // For more information on Maximize Conversion Value, see the support // article: http://support.google.com/google-ads/answer/7684216. // A targetRoas of 3.5 corresponds to a 350% return on ad spend. // For first time users, it's recommended not to set a target ROAS value. Although // the target ROAS value is optional, you still need to define the enclosing // maximize_conversion_value. .setMaximizeConversionValue( MaximizeConversionValue.newBuilder() // .setTargetRoas(3.5) .build()) // Below is what you would use if you want to maximize conversions: // .setMaximizeConversions( // MaximizeConversions.newBuilder() // // The target CPA is optional. This is the average amount that you would like // // to spend per conversion action. // // .setTargetCpaMicros(1_000_000) // .build()) // Sets the shopping settings. .setShoppingSetting( ShoppingSetting.newBuilder() .setMerchantId(merchantCenterAccountId) // Optional: To use products only from a specific feed, set FeedLabel to the // feed label used in Merchant Center. See: // https://support.google.com/merchants/answer/12453549. // Removing the feedLabel field will use products from all feeds. // .setFeedLabel("INSERT_FEED_LABEL_HERE") .build()) // Sets the Final URL expansion opt out. This flag is specific to // Performance Max campaigns. If opted out (true), only the final URLs in // the asset group or URLs specified in the advertiser's Google Merchant // Center or business data feeds are targeted. // // If opted in (false), the entire domain will be targeted. For best // results, set this value to false to opt in and allow URL expansions. You // can optionally add exclusions to limit traffic to parts of your website. // // Sets to true for this Retail campaign so the final URLs will be limited to those // explicitly surfaced via Google Merchant Center. .setUrlExpansionOptOut(true) // Sets if the campaign is enabled for brand guidelines. For more information on brand // guidelines, see https://support.google.com/google-ads/answer/14934472. .setBrandGuidelinesEnabled(brandGuidelinesEnabled) // Assigns the resource name with a temporary ID. .setResourceName( ResourceNames.campaign(customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID)) // Sets the budget using the given budget resource name. .setCampaignBudget(ResourceNames.campaignBudget(customerId, BUDGET_TEMPORARY_ID)) // Declares whether this campaign serves political ads targeting the EU. .setContainsEuPoliticalAdvertising(DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING) // Optional fields. .setStartDate(new DateTime().plusDays(1).toString("yyyyMMdd")) .setEndDate(new DateTime().plusDays(365).toString("yyyyMMdd")) .build(); return MutateOperation.newBuilder() .setCampaignOperation( CampaignOperation.newBuilder().setCreate(performanceMaxCampaign).build()) .build(); } /** Creates a list of MutateOperations that create new campaign criteria. */ private List<MutateOperation> createCampaignCriterionOperations(long customerId) { String campaignResourceName = ResourceNames.campaign(customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID); List<CampaignCriterion> campaignCriteria = new ArrayList<>(); // Sets the LOCATION campaign criteria. // Targets all of New York City except Brooklyn. // Location IDs are listed here: // https://developers.google.com/google-ads/api/reference/data/geotargets // and they can also be retrieved using the GeoTargetConstantService as shown // here: https://developers.google.com/google-ads/api/docs/targeting/location-targeting campaignCriteria.add( CampaignCriterion.newBuilder() .setCampaign(campaignResourceName) // Adds one positive location target for New York City (ID=1023191), specifically adding // the positive criteria before the negative one. .setLocation( LocationInfo.newBuilder() .setGeoTargetConstant(ResourceNames.geoTargetConstant(1023191)) .build()) .setNegative(false) .build()); // Next adds the negative target for Brooklyn (ID=1022762). campaignCriteria.add( CampaignCriterion.newBuilder() .setCampaign(campaignResourceName) .setLocation( LocationInfo.newBuilder() .setGeoTargetConstant(ResourceNames.geoTargetConstant(1022762)) .build()) .setNegative(true) .build()); // Sets the LANGUAGE campaign criterion. campaignCriteria.add( CampaignCriterion.newBuilder() .setCampaign(campaignResourceName) // Sets the language. // For a list of all language codes, see: // https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7 .setLanguage( LanguageInfo.newBuilder() .setLanguageConstant(ResourceNames.languageConstant(1000)) // English .build()) .build()); // Returns a list of mutate operations with one operation per criterion. return campaignCriteria.stream() .map( criterion -> MutateOperation.newBuilder() .setCampaignCriterionOperation( CampaignCriterionOperation.newBuilder().setCreate(criterion).build()) .build()) .collect(Collectors.toList()); } /** * Creates multiple text assets and returns the list of resource names. * * <p>These repeated assets must be created in a separate request prior to creating the campaign. */ private List<String> createMultipleTextAssets( GoogleAdsClient googleAdsClient, long customerId, List<String> texts) { List<MutateOperation> mutateOperations = new ArrayList<>(); for (String text : texts) { Asset asset = Asset.newBuilder().setTextAsset(TextAsset.newBuilder().setText(text)).build(); AssetOperation assetOperation = AssetOperation.newBuilder().setCreate(asset).build(); mutateOperations.add(MutateOperation.newBuilder().setAssetOperation(assetOperation).build()); } List<String> assetResourceNames = new ArrayList<>(); // Creates the service client. try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { // Sends the operations in a single Mutate request. MutateGoogleAdsResponse response = googleAdsServiceClient.mutate(Long.toString(customerId), mutateOperations); for (MutateOperationResponse result : response.getMutateOperationResponsesList()) { if (result.hasAssetResult()) { assetResourceNames.add(result.getAssetResult().getResourceName()); } } printResponseDetails(response); } return assetResourceNames; } /** Creates a MutateOperation that create a new AssetGroup. */ private MutateOperation createAssetGroupOperation( long customerId, String assetGroupResourceName, String finalUrl) { List<MutateOperation> mutateOperations = new ArrayList<>(); String campaignResourceName = ResourceNames.campaign(customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID); // Creates the AssetGroup. AssetGroup assetGroup = AssetGroup.newBuilder() .setName("Performance Max retail asset group #" + getPrintableDateTime()) .setCampaign(campaignResourceName) .addFinalUrls(finalUrl) .addFinalMobileUrls(finalUrl) .setStatus(AssetGroupStatus.PAUSED) .setResourceName(assetGroupResourceName) .build(); AssetGroupOperation assetGroupOperation = AssetGroupOperation.newBuilder().setCreate(assetGroup).build(); return MutateOperation.newBuilder().setAssetGroupOperation(assetGroupOperation).build(); } /** Creates a list of MutateOperations that create a new AssetGroup. */ private List<MutateOperation> createAssetAndAssetGroupAssetOperations( long customerId, String assetGroupResourceName, List<String> headlineAssetResourceNames, List<String> descriptionAssetResourceNames, boolean brandGuidelinesEnabled) throws IOException { List<MutateOperation> mutateOperations = new ArrayList<>(); // For the list of required assets for a Performance Max campaign, see // https://developers.google.com/google-ads/api/docs/performance-max/assets // An AssetGroup is linked to an Asset by creating a new AssetGroupAsset // and providing: // the resource name of the AssetGroup // the resource name of the Asset // the field_type of the Asset in this AssetGroup. // To learn more about AssetGroups, see // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups // Links the previously created multiple text assets. // Links the headline assets. for (String resourceName : headlineAssetResourceNames) { mutateOperations.add( createAssetGroupAssetMutateOperation( AssetFieldType.HEADLINE, resourceName, assetGroupResourceName)); } // Links the description assets. for (String resourceName : descriptionAssetResourceNames) { mutateOperations.add( createAssetGroupAssetMutateOperation( AssetFieldType.DESCRIPTION, resourceName, assetGroupResourceName)); } // Creates and links the long headline text asset. mutateOperations.addAll( createAndLinkTextAsset( customerId, assetGroupResourceName, "Travel the World", AssetFieldType.LONG_HEADLINE)); // Creates and links the business name and logo assets. mutateOperations.addAll( createAndLinkBrandAssets( customerId, brandGuidelinesEnabled, "Interplanetary Cruises", "https://gaagl.page.link/1Crm", "Logo Image")); // Creates and links the image assets. // Creates and links the Marketing Image Asset. mutateOperations.addAll( createAndLinkImageAsset( customerId, assetGroupResourceName, "https://gaagl.page.link/Eit5", AssetFieldType.MARKETING_IMAGE, "Marketing Image")); // Creates and links the Square Marketing Image Asset. mutateOperations.addAll( createAndLinkImageAsset( customerId, assetGroupResourceName, "https://gaagl.page.link/bjYi", AssetFieldType.SQUARE_MARKETING_IMAGE, "Square Marketing Image")); return sortMutateOperations(mutateOperations); } /** Creates a list of MutateOperations that create a new linked text asset. */ List<MutateOperation> createAndLinkTextAsset( long customerId, String assetGroupResourceName, String text, AssetFieldType assetFieldType) { List<MutateOperation> mutateOperations = new ArrayList<>(); String assetResourceName = ResourceNames.asset(customerId, getNextTemporaryId()); // Creates the Text Asset. Asset asset = Asset.newBuilder() .setResourceName(assetResourceName) .setTextAsset(TextAsset.newBuilder().setText(text).build()) .build(); AssetOperation assetOperation = AssetOperation.newBuilder().setCreate(asset).build(); mutateOperations.add(MutateOperation.newBuilder().setAssetOperation(assetOperation).build()); // Creates an AssetGroupAsset to link the Asset to the AssetGroup. mutateOperations.add( createAssetGroupAssetMutateOperation( assetFieldType, assetResourceName, assetGroupResourceName)); return mutateOperations; } /** Creates a list of MutateOperations that create a new linked image asset. */ List<MutateOperation> createAndLinkImageAsset( long customerId, String assetGroupResourceName, String url, AssetFieldType assetFieldType, String assetName) throws IOException { List<MutateOperation> mutateOperations = new ArrayList<>(); String assetResourceName = ResourceNames.asset(customerId, getNextTemporaryId()); // Creates a media file. byte[] assetBytes = ByteStreams.toByteArray(new URL(url).openStream()); // Creates the Image Asset. Asset asset = Asset.newBuilder() .setResourceName(assetResourceName) .setImageAsset(ImageAsset.newBuilder().setData(ByteString.copyFrom(assetBytes)).build()) // Provides a unique friendly name to identify your asset. When there is an existing // image asset with the same content but a different name, the new name will be dropped // silently. .setName(assetName) .build(); AssetOperation assetOperation = AssetOperation.newBuilder().setCreate(asset).build(); mutateOperations.add(MutateOperation.newBuilder().setAssetOperation(assetOperation).build()); // Creates an AssetGroupAsset to link the Asset to the AssetGroup. mutateOperations.add( createAssetGroupAssetMutateOperation( assetFieldType, assetResourceName, assetGroupResourceName)); return mutateOperations; } /** Creates a list of MutateOperations that create linked brand assets. */ List<MutateOperation> createAndLinkBrandAssets( long customerId, boolean brandGuidelinesEnabled, String businessName, String logoUrl, String logoName) throws IOException { List<MutateOperation> mutateOperations = new ArrayList<>(); // Creates the brand name text asset. String businessNameAssetResourceName = ResourceNames.asset(customerId, getNextTemporaryId()); Asset businessNameAsset = Asset.newBuilder() .setResourceName(businessNameAssetResourceName) .setTextAsset(TextAsset.newBuilder().setText(businessName).build()) .build(); AssetOperation businessNameAssetOperation = AssetOperation.newBuilder().setCreate(businessNameAsset).build(); mutateOperations.add( MutateOperation.newBuilder().setAssetOperation(businessNameAssetOperation).build()); // Creates the logo image asset. String logoAssetResourceName = ResourceNames.asset(customerId, getNextTemporaryId()); // Creates a media file. byte[] logoBytes = ByteStreams.toByteArray(new URL(logoUrl).openStream()); Asset logoAsset = Asset.newBuilder() .setResourceName(logoAssetResourceName) .setImageAsset(ImageAsset.newBuilder().setData(ByteString.copyFrom(logoBytes)).build()) // Provides a unique friendly name to identify your asset. When there is an existing // image asset with the same content but a different name, the new name will be dropped // silently. .setName(logoName) .build(); AssetOperation logoImageAssetOperation = AssetOperation.newBuilder().setCreate(logoAsset).build(); mutateOperations.add( MutateOperation.newBuilder().setAssetOperation(logoImageAssetOperation).build()); if (brandGuidelinesEnabled) { // Creates CampaignAsset resources to link the Asset resources to the Campaign. mutateOperations.add( createCampaignAssetMutateOperation( customerId, AssetFieldType.BUSINESS_NAME, businessNameAssetResourceName)); mutateOperations.add( createCampaignAssetMutateOperation( customerId, AssetFieldType.LOGO, logoAssetResourceName)); } else { // Creates an AssetGroupAsset to link the Asset to the AssetGroup. mutateOperations.add( createAssetGroupAssetMutateOperation( AssetFieldType.BUSINESS_NAME, businessNameAssetResourceName, ResourceNames.assetGroup(customerId, ASSET_GROUP_TEMPORARY_ID))); mutateOperations.add( createAssetGroupAssetMutateOperation( AssetFieldType.LOGO, logoAssetResourceName, ResourceNames.assetGroup(customerId, ASSET_GROUP_TEMPORARY_ID))); } return mutateOperations; } /** Creates a MutateOperation to add an AssetGroupAsset. */ MutateOperation createAssetGroupAssetMutateOperation( AssetFieldType fieldType, String assetResourceName, String assetGroupResourceName) { AssetGroupAsset assetGroupAsset = AssetGroupAsset.newBuilder() .setFieldType(fieldType) .setAssetGroup(assetGroupResourceName) .setAsset(assetResourceName) .build(); AssetGroupAssetOperation assetGroupAssetOperation = AssetGroupAssetOperation.newBuilder().setCreate(assetGroupAsset).build(); return MutateOperation.newBuilder() .setAssetGroupAssetOperation(assetGroupAssetOperation) .build(); } /** Creates a MutateOperation to add a CampaignAsset. */ MutateOperation createCampaignAssetMutateOperation( long customerId, AssetFieldType fieldType, String assetResourceName) { CampaignAsset campaignAsset = CampaignAsset.newBuilder() .setFieldType(fieldType) .setCampaign(ResourceNames.campaign(customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID)) .setAsset(assetResourceName) .build(); CampaignAssetOperation campaignAssetOperation = CampaignAssetOperation.newBuilder().setCreate(campaignAsset).build(); return MutateOperation.newBuilder().setCampaignAssetOperation(campaignAssetOperation).build(); } /** * Sorts a list of mutate operations. * * <p>This sorts the list such that all asset operations precede all asset group asset and * campaign asset operations. If asset group assets are created before assets then an error will * be returned by the API. */ private List<MutateOperation> sortMutateOperations(List<MutateOperation> operations) { List<MutateOperation> sortedOperations = new ArrayList<>(); sortedOperations.addAll( operations.stream().filter(o -> o.hasAssetOperation()).collect(Collectors.toList())); sortedOperations.addAll( operations.stream() .filter(o -> o.hasAssetGroupAssetOperation()) .collect(Collectors.toList())); sortedOperations.addAll( operations.stream() .filter(o -> o.hasCampaignAssetOperation()) .collect(Collectors.toList())); return sortedOperations; } /** Retrieves the list of customer conversion goals. */ private static List<CustomerConversionGoal> getCustomerConversionGoals( GoogleAdsClient googleAdsClient, long customerId) { String query = "SELECT customer_conversion_goal.category, customer_conversion_goal.origin " + "FROM customer_conversion_goal"; List<CustomerConversionGoal> customerConversionGoals = new ArrayList<>(); try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { // The number of conversion goals is typically less than 50, so we use // GoogleAdsService.search instead of search_stream. SearchPagedResponse response = googleAdsServiceClient.search(Long.toString(customerId), query); for (GoogleAdsRow googleAdsRow : response.iterateAll()) { customerConversionGoals.add(googleAdsRow.getCustomerConversionGoal()); } } return customerConversionGoals; } /** Creates a list of MutateOperations that override customer conversion goals. */ private static List<MutateOperation> createConversionGoalOperations( long customerId, List<CustomerConversionGoal> customerConversionGoals) { List<MutateOperation> mutateOperations = new ArrayList<>(); // To override the customer conversion goals, we will change the // biddability of each of the customer conversion goals so that only // the desired conversion goal is biddable in this campaign. for (CustomerConversionGoal customerConversionGoal : customerConversionGoals) { ConversionActionCategory category = customerConversionGoal.getCategory(); ConversionOrigin origin = customerConversionGoal.getOrigin(); String campaignConversionGoalResourceName = ResourceNames.campaignConversionGoal( customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, category, origin); CampaignConversionGoal.Builder campaignConversionGoalBuilder = CampaignConversionGoal.newBuilder().setResourceName(campaignConversionGoalResourceName); // Change the biddability for the campaign conversion goal. // Set biddability to True for the desired (category, origin). // Set biddability to False for all other conversion goals. // Note: // 1- It is assumed that this Conversion Action // (category=PURCHASE, origin=WEBSITE) exists in this account. // 2- More than one goal can be biddable if desired. This example // shows only one. if (category == ConversionActionCategory.PURCHASE && origin == ConversionOrigin.WEBSITE) { campaignConversionGoalBuilder.setBiddable(true); } else { campaignConversionGoalBuilder.setBiddable(false); } CampaignConversionGoal campaignConversionGoal = campaignConversionGoalBuilder.build(); CampaignConversionGoalOperation campaignConversionGoalOperation = CampaignConversionGoalOperation.newBuilder() .setUpdate(campaignConversionGoal) .setUpdateMask(FieldMasks.allSetFieldsOf(campaignConversionGoal)) .build(); mutateOperations.add( MutateOperation.newBuilder() .setCampaignConversionGoalOperation(campaignConversionGoalOperation) .build()); } return mutateOperations; } /** Creates a MutateOperation that creates a new asset group listing group filter. */ private MutateOperation createAssetGroupListingGroupFilterOperation( String assetGroupResourceName) { // Creates a new asset group listing group filter containing the "default" listing group (All // products). AssetGroupListingGroupFilter listingGroupFilter = AssetGroupListingGroupFilter.newBuilder() .setAssetGroup(assetGroupResourceName) // Does not set the parentListingGroupFilter since this is the root node. For all other // nodes, this would refer to the parent listing group filter resource name. // .setParentListingGroupFilter("<PARENT FILTER RESOURCE NAME>") // Sets the type to UNIT_INCLUDED since this node has no children. .setType(ListingGroupFilterType.UNIT_INCLUDED) // Specifies that this uses the SHOPPING listing source, as required for a Performance // Max retail campaign. .setListingSource(ListingGroupFilterListingSource.SHOPPING) .build(); // Returns an operation to the list to create the listing group filter. return MutateOperation.newBuilder() .setAssetGroupListingGroupFilterOperation( AssetGroupListingGroupFilterOperation.newBuilder().setCreate(listingGroupFilter)) .build(); } /** * Prints the details of a MutateGoogleAdsResponse. * * <p>Parses the "response" oneof field name and uses it to extract the new entity's name and * resource name. */ private void printResponseDetails(MutateGoogleAdsResponse response) { // Parses the Mutate response to print details about the entities that were created by the // request. String suffix = "_result"; for (MutateOperationResponse result : response.getMutateOperationResponsesList()) { for (Entry<FieldDescriptor, Object> responseFields : result.getAllFields().entrySet()) { String fieldName = responseFields.getKey().getName(); String value = responseFields.getValue().toString().trim(); if (fieldName.endsWith(suffix)) { fieldName = fieldName.substring(0, fieldName.length() - suffix.length()); } System.out.printf("Created a(n) %s with %s.%n", fieldName, value); } } } /** Returns the next temporary ID and decreases it by one. */ private long getNextTemporaryId() { return temporaryId--; } }
C#
// Copyright 2021 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 CommandLine; using Google.Ads.Gax.Examples; using Google.Ads.Gax.Util; using Google.Ads.GoogleAds.Config; using Google.Ads.GoogleAds.Lib; using Google.Ads.GoogleAds.V21.Common; using Google.Ads.GoogleAds.V21.Errors; using Google.Ads.GoogleAds.V21.Resources; using Google.Ads.GoogleAds.V21.Services; using Google.Api.Gax; using Google.Protobuf; using System; using System.Collections.Generic; using System.Threading; using static Google.Ads.GoogleAds.V21.Enums.AdvertisingChannelTypeEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.AssetFieldTypeEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.AssetGroupStatusEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.BudgetDeliveryMethodEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.CampaignStatusEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.ConversionActionCategoryEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.ConversionOriginEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.EuPoliticalAdvertisingStatusEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.ListingGroupFilterListingSourceEnum.Types; using static Google.Ads.GoogleAds.V21.Enums.ListingGroupFilterTypeEnum.Types; using static Google.Ads.GoogleAds.V21.Resources.Campaign.Types; namespace Google.Ads.GoogleAds.Examples.V21 { /// <summary> /// This example shows how to create a Performance Max retail campaign. /// /// This will be created for "All products". /// /// For more information about Performance Max retail campaigns, see /// https://developers.google.com/google-ads/api/docs/performance-max/retail /// /// Prerequisites: /// - You need to have access to a Merchant Center account. You can find /// instructions to create a Merchant Center account here: /// https://support.google.com/merchants/answer/188924. /// This account must be linked to your Google Ads account. The integration /// instructions can be found at: /// https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center /// - You need your Google Ads account to track conversions. The different ways /// to track conversions can be found here: /// https://support.google.com/google-ads/answer/1722054. /// - You must have at least one conversion action in the account. For /// more about conversion actions, see /// https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions /// </summary> public class AddPerformanceMaxRetailCampaign : ExampleBase { /// <summary> /// Command line options for running the <see cref="AddPerformanceMaxRetailCampaign"/> /// example. /// </summary> public class Options : OptionsBase { /// <summary> /// The Google Ads customer ID. /// </summary> [Option("customerId", Required = true, HelpText = "The Google Ads customer ID.")] public long CustomerId { get; set; } /// <summary> /// The Merchant Center account ID. /// </summary> [Option("merchantCenterAccountId", Required = true, HelpText = "The Merchant Center account ID.")] public long MerchantCenterAccountId { get; set; } /// <summary> /// The final url for the generated ads. Must have the same domain as the Merchant /// Center account. /// </summary> [Option("finalUrl", Required = true, HelpText = "The final url for the generated ads." + "Must have the same domain as the Merchant Center account.")] public string FinalUrl { get; set; } /// <summary> /// Optional: A boolean value indicating if the campaign is enabled for brand /// guidelines. /// </summary> [Option("brandGuidelinesEnabled", Required = false, HelpText = "A boolean value indicating if the campaign is enabled for brand guidelines.")] public bool BrandGuidelinesEnabled { get; set; } } /// <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) { Options options = ExampleUtilities.ParseCommandLine<Options>(args); AddPerformanceMaxRetailCampaign codeExample = new AddPerformanceMaxRetailCampaign(); Console.WriteLine(codeExample.Description); codeExample.Run( new GoogleAdsClient(), options.CustomerId, options.MerchantCenterAccountId, options.FinalUrl, options.BrandGuidelinesEnabled ); } // We specify temporary IDs that are specific to a single mutate request. Temporary IDs are // always negative and unique within one mutate request. // // See https://developers.google.com/google-ads/api/docs/mutating/best-practices for further // details. // // These temporary IDs are fixed because they are used in multiple places. private const int TEMPORARY_ID_BUDGET = -1; private const int TEMPORARY_ID_CAMPAIGN = -2; private const int TEMPORARY_ID_ASSET_GROUP = -3; // There are also entities that will be created in the same request but do not need to be // fixed temporary IDs because they are referenced only once. private class AssetTemporaryResourceNameGenerator { private long customerId; private long next; public AssetTemporaryResourceNameGenerator(long customerId, long assetGroupId) { this.customerId = customerId; this.next = assetGroupId - 1; } public string Next() { long i = next; Interlocked.Decrement(ref next); return ResourceNames.Asset(customerId, i); } } /// <summary> /// Returns a description about the code example. /// </summary> public override string Description => "This example shows how to create a Performance Max retail campaign."; /// <summary> /// Runs the code example. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The Google Ads customer ID.</param> /// <param name="merchantCenterAccountId">The Merchant Center account ID.</param> /// <param name="finalUrl">The final URL.</param> /// <param name="brandGuidelinesEnabled">Whether or not to enable brand guidelines.</param> public void Run( GoogleAdsClient client, long customerId, long merchantCenterAccountId, string finalUrl, bool brandGuidelinesEnabled) { try { GoogleAdsServiceClient googleAdsServiceClient = client.GetService(Services.V21.GoogleAdsService); // This campaign will override the customer conversion goals. // Retrieve the current list of customer conversion goals. List<CustomerConversionGoal> customerConversionGoals = GetCustomerConversionGoals(client, customerId); // Performance Max campaigns require that repeated assets such as headlines and // descriptions be created before the campaign. // // For the list of required assets for a Performance Max campaign, see // https://developers.google.com/google-ads/api/docs/performance-max/assets // // Create the headlines. List<string> headlineAssetResourceNames = CreateMultipleTextAssets( client, customerId, new[] { "Travel", "Travel Reviews", "Book travel" } ); // Create the descriptions. List<string> descriptionAssetResourceNames = CreateMultipleTextAssets( client, customerId, new[] { "Take to the air!", "Fly to the sky!" } ); string tempResourceNameCampaignBudget = ResourceNames.CampaignBudget( customerId, TEMPORARY_ID_BUDGET ); string assetGroupResourceName = ResourceNames.AssetGroup( customerId, TEMPORARY_ID_ASSET_GROUP ); // The below methods create and return MutateOperations that we later provide to the // GoogleAdsService.Mutate method in order to create the entities in a single request. // Since the entities for a Performance Max campaign are closely tied to one-another, // it's considered a best practice to create them in a single Mutate request so they all // complete successfully or fail entirely, leaving no orphaned entities. // // See: https://developers.google.com/google-ads/api/docs/mutating/overview MutateOperation campaignBudgetOperation = CreateCampaignBudgetOperation( tempResourceNameCampaignBudget ); string tempResourceNameCampaign = ResourceNames.Campaign( customerId, TEMPORARY_ID_CAMPAIGN ); MutateOperation performanceMaxCampaignOperation = CreatePerformanceMaxCampaignOperation( tempResourceNameCampaign, tempResourceNameCampaignBudget, merchantCenterAccountId, brandGuidelinesEnabled ); List<MutateOperation> campaignCriterionOperations = CreateCampaignCriterionOperations(tempResourceNameCampaign); List<MutateOperation> assetGroupOperations = CreateAssetGroupOperations( tempResourceNameCampaign, assetGroupResourceName, finalUrl, headlineAssetResourceNames, descriptionAssetResourceNames, new AssetTemporaryResourceNameGenerator( customerId, TEMPORARY_ID_ASSET_GROUP ), client.Config, brandGuidelinesEnabled ); List<MutateOperation> conversionGoalOperations = CreateCustomerConversionGoalOperations( customerId, customerConversionGoals ); // Retail Performance Max campaigns require listing groups, which are created via the // AssetGroupListingGroupFilter resource. List<MutateOperation> assetGroupListingGroupOperations = CreateAssetGroupListingGroupOperations( assetGroupResourceName ); MutateGoogleAdsRequest request = new MutateGoogleAdsRequest { CustomerId = customerId.ToString() }; // It's important to create these entities in this order because they depend on // each other. // // Additionally, we take several lists of operations and flatten them into one // large list. request.MutateOperations.Add(campaignBudgetOperation); request.MutateOperations.Add(performanceMaxCampaignOperation); request.MutateOperations.AddRange(campaignCriterionOperations); request.MutateOperations.AddRange(assetGroupOperations); request.MutateOperations.AddRange(conversionGoalOperations); request.MutateOperations.AddRange(assetGroupListingGroupOperations); MutateGoogleAdsResponse response = googleAdsServiceClient.Mutate(request); PrintResponseDetails(response); } 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 MutateOperation that creates a new CampaignBudget. /// /// A temporary ID will be assigned to this campaign budget so that it can be /// referenced by other objects being created in the same Mutate request. /// </summary> /// <param name="budgetResourceName">The temporary resource name of the budget to /// create.</param> /// <returns>A MutateOperation that creates a CampaignBudget.</returns> private MutateOperation CreateCampaignBudgetOperation( string budgetResourceName) { MutateOperation operation = new MutateOperation { CampaignBudgetOperation = new CampaignBudgetOperation { Create = new CampaignBudget { Name = "Performance Max campaign budget #" + ExampleUtilities.GetRandomString(), // The budget period already defaults to Daily. AmountMicros = 50000000, DeliveryMethod = BudgetDeliveryMethod.Standard, // A Performance Max campaign cannot use a shared campaign budget. ExplicitlyShared = false, // Set a temporary ID in the budget's resource name so it can be referenced // by the campaign in later steps. ResourceName = budgetResourceName } } }; return operation; } /// Creates a MutateOperation that creates a new Performance Max campaign. /// <param name="campaignResourceName">The campaign resource name.</param> /// <param name="campaignBudgetResourceName">The campaign budget resource name.</param> /// <param name="merchantCenterAccountId">The Merchant Center account ID.</param> /// <param name="brandGuidelinesEnabled">Whether or not to enable brand guidelines.</param> /// <returns>A MutateOperations that will create this new campaign.</returns> private MutateOperation CreatePerformanceMaxCampaignOperation( string campaignResourceName, string campaignBudgetResourceName, long merchantCenterAccountId, bool brandGuidelinesEnabled) { MutateOperation operation = new MutateOperation() { CampaignOperation = new CampaignOperation() { Create = new Campaign() { Name = "Performance Max campaign #" + ExampleUtilities.GetRandomString(), // Set the campaign status as PAUSED. The campaign is the only entity in // the mutate request that should have its status set. Status = CampaignStatus.Paused, // All Performance Max campaigns have an advertising_channel_type of // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. AdvertisingChannelType = AdvertisingChannelType.PerformanceMax, // Bidding strategy must be set directly on the campaign. Setting a // portfolio bidding strategy by resource name is not supported. Max // Conversion and Maximize Conversion Value are the only strategies // supported for Performance Max campaigns. BiddingStrategyTYpe is // read-only and cannot be set by the API. An optional ROAS (Return on // Advertising Spend) can be set to enable the MaximizeConversionValue // bidding strategy. The ROAS value must be specified as a ratio in the API. // It is calculated by dividing "total value" by "total spend". // // For more information on Maximize Conversion Value, see the support // article: // http://support.google.com/google-ads/answer/7684216. // // A target_roas of 3.5 corresponds to a 350% return on ad spend. MaximizeConversionValue = new MaximizeConversionValue() { TargetRoas = 3.5 }, ShoppingSetting = new ShoppingSetting() { MerchantId = merchantCenterAccountId, // Optional: To use products only from a specific feed, set FeedLabel // to the feed label used in Merchant Center. // See: https://support.google.com/merchants/answer/12453549. // Omitting the FeedLabel field will use products from all feeds. // FeedLabel = "INSERT_FEED_LABEL_HERE" }, // Set the Final URL expansion opt out. This flag is specific to // Performance Max campaigns. If opted out (True), only the final URLs in // the asset group or URLs specified in the advertiser's Google Merchant // Center or business data feeds are targeted. // // If opted in (False), the entire domain will be targeted. For best // results, set this value to false to opt in and allow URL expansions. You // can optionally add exclusions to limit traffic to parts of your website. // // For a Retail campaign, we want the final URL's to be limited to those // explicitly surfaced via GMC. UrlExpansionOptOut = true, // Use the temporary resource name created earlier ResourceName = campaignResourceName, // Set the budget using the given budget resource name. CampaignBudget = campaignBudgetResourceName, // Set if the campaign is enabled for brand guidelines. For more information // on brand guidelines, see https://support.google.com/google-ads/answer/14934472. BrandGuidelinesEnabled = brandGuidelinesEnabled, // Declare whether or not this campaign contains political ads targeting the EU. ContainsEuPoliticalAdvertising = EuPoliticalAdvertisingStatus.DoesNotContainEuPoliticalAdvertising, // Optional fields StartDate = DateTime.Now.AddDays(1).ToString("yyyyMMdd"), EndDate = DateTime.Now.AddDays(365).ToString("yyyyMMdd") } } }; return operation; } /// <summary> /// Creates a list of MutateOperations that create new campaign criteria. /// </summary> /// <param name="campaignResourceName">The campaign resource name.</param> /// <returns>A list of MutateOperations that create new campaign criteria.</returns> private List<MutateOperation> CreateCampaignCriterionOperations( string campaignResourceName) { List<MutateOperation> operations = new List<MutateOperation>(); // Set the LOCATION campaign criteria. // Target all of New York City except Brooklyn. // Location IDs are listed here: // https://developers.google.com/google-ads/api/reference/data/geotargets // and they can also be retrieved using the GeoTargetConstantService as shown // here: https://developers.google.com/google-ads/api/docs/targeting/location-targeting // // We will add one positive location target for New York City (ID=1023191) // and one negative location target for Brooklyn (ID=1022762). // First, add the positive (negative = False) for New York City. MutateOperation operation1 = new MutateOperation() { CampaignCriterionOperation = new CampaignCriterionOperation() { Create = new CampaignCriterion() { Campaign = campaignResourceName, Location = new LocationInfo() { GeoTargetConstant = ResourceNames.GeoTargetConstant(1023191) }, Negative = false } } }; operations.Add(operation1); // Next add the negative target for Brooklyn. MutateOperation operation2 = new MutateOperation() { CampaignCriterionOperation = new CampaignCriterionOperation() { Create = new CampaignCriterion() { Campaign = campaignResourceName, Location = new LocationInfo() { GeoTargetConstant = ResourceNames.GeoTargetConstant(1022762) }, Negative = true } } }; operations.Add(operation2); // Set the LANGUAGE campaign criterion. MutateOperation operation3 = new MutateOperation() { CampaignCriterionOperation = new CampaignCriterionOperation() { Create = new CampaignCriterion() { Campaign = campaignResourceName, // Set the language. // For a list of all language codes, see: // https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7 Language = new LanguageInfo() { LanguageConstant = ResourceNames.LanguageConstant(1000) // English }, } } }; operations.Add(operation3); return operations; } /// <summary> /// Creates multiple text assets and returns the list of resource names. /// These repeated assets must be created in a separate request prior to /// creating the campaign. /// </summary> /// <param name="client">The Google Ads Client.</param> /// <param name="customerId">The customer's ID.</param> /// <param name="texts">The texts to add.</param> /// <returns>A list of asset resource names.</returns> private List<string> CreateMultipleTextAssets( GoogleAdsClient client, long customerId, string[] texts) { // Get the GoogleAdsService. GoogleAdsServiceClient googleAdsServiceClient = client.GetService(Services.V21.GoogleAdsService); MutateGoogleAdsRequest request = new MutateGoogleAdsRequest() { CustomerId = customerId.ToString() }; foreach (string text in texts) { request.MutateOperations.Add( new MutateOperation() { AssetOperation = new AssetOperation() { Create = new Asset() { TextAsset = new TextAsset() { Text = text } } } } ); } // Send the operations in a single Mutate request. MutateGoogleAdsResponse response = googleAdsServiceClient.Mutate(request); List<string> assetResourceNames = new List<string>(); foreach (MutateOperationResponse operationResponse in response.MutateOperationResponses) { MutateAssetResult assetResult = operationResponse.AssetResult; assetResourceNames.Add(assetResult.ResourceName); } PrintResponseDetails(response); return assetResourceNames; } /// <summary> /// Creates a list of MutateOperations that create a new asset_group. /// </summary> /// <param name="campaignResourceName">The campaign resource name.</param> /// <param name="assetGroupResourceName">The asset group resource name.</param> /// <param name="finalUrl">The final url.</param> /// <param name="headlineAssetResourceNames">The headline asset resource names.</param> /// <param name="descriptionAssetResourceNames">The description asset resource /// names.</param> /// <param name="resourceNameGenerator">A generator for unique temporary ID's.</param> /// <param name="config">The Google Ads config.</param> /// <param name="brandGuidelinesEnabled">Whether or not to enable brand guidelines.</param> /// <returns>A list of MutateOperations that create the new asset group.</returns> private List<MutateOperation> CreateAssetGroupOperations( string campaignResourceName, string assetGroupResourceName, string finalUrl, List<string> headlineAssetResourceNames, List<string> descriptionAssetResourceNames, AssetTemporaryResourceNameGenerator resourceNameGenerator, GoogleAdsConfig config, bool brandGuidelinesEnabled) { List<MutateOperation> operations = new List<MutateOperation>(); // For the list of required assets for a Performance Max campaign, see // https://developers.google.com/google-ads/api/docs/performance-max/assets // Create and link the long headline text asset. string longHeadlineResourceName = resourceNameGenerator.Next(); operations.Add( CreateTextAssetOperation( longHeadlineResourceName, "Travel the World" ) ); // Create the business name text asset. string businessNameResourceName = resourceNameGenerator.Next(); operations.Add( CreateTextAssetOperation( businessNameResourceName, "Interplanetary Cruises" ) ); // Create the Logo Asset. string logoResourceName = resourceNameGenerator.Next(); operations.Add( CreateImageAssetOperation( logoResourceName, "https://gaagl.page.link/1Crm", "Logo Image", config ) ); // Create the Marketing Image Asset. string marketingImageResourceName = resourceNameGenerator.Next(); operations.Add( CreateImageAssetOperation( marketingImageResourceName, "https://gaagl.page.link/Eit5", "Marketing Image", config ) ); // Create the Square Marketing Image Asset. string squareMarketingImageResourceName = resourceNameGenerator.Next(); operations.Add( CreateImageAssetOperation( squareMarketingImageResourceName, "https://gaagl.page.link/bjYi", "Square Marketing Image", config ) ); // An AssetGroup is linked to an Asset by creating a new AssetGroupAsset // and providing: // the resource name of the AssetGroup // the resource name of the Asset // the field_type of the Asset in this AssetGroup. // // To learn more about AssetGroups, see // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups // // Also, note that all asset creation operations must be before the // asset group creation operation and the asset group linking operations. // Create the AssetGroup operations.Add( new MutateOperation() { AssetGroupOperation = new AssetGroupOperation() { Create = new AssetGroup() { Name = "Performance Max asset group #" + ExampleUtilities.GetRandomString(), Campaign = campaignResourceName, FinalUrls = { finalUrl }, FinalMobileUrls = { finalUrl }, Status = AssetGroupStatus.Paused, ResourceName = assetGroupResourceName } } } ); // Link the previously created assets. // Link the headline assets. foreach (string resourceName in headlineAssetResourceNames) { operations.Add( new MutateOperation() { AssetGroupAssetOperation = new AssetGroupAssetOperation() { Create = new AssetGroupAsset() { FieldType = AssetFieldType.Headline, AssetGroup = assetGroupResourceName, Asset = resourceName } } } ); } // Link the description assets. foreach (string resourceName in descriptionAssetResourceNames) { operations.Add( new MutateOperation() { AssetGroupAssetOperation = new AssetGroupAssetOperation() { Create = new AssetGroupAsset() { FieldType = AssetFieldType.Description, AssetGroup = assetGroupResourceName, Asset = resourceName } } } ); } operations.Add( CreateLinkAssetOperation( AssetFieldType.LongHeadline, assetGroupResourceName, longHeadlineResourceName ) ); operations.Add( CreateLinkAssetOperation( AssetFieldType.BusinessName, assetGroupResourceName, businessNameResourceName, brandGuidelinesEnabled ) ); operations.Add( CreateLinkAssetOperation( AssetFieldType.Logo, assetGroupResourceName, logoResourceName, brandGuidelinesEnabled ) ); operations.Add( CreateLinkAssetOperation( AssetFieldType.MarketingImage, assetGroupResourceName, marketingImageResourceName ) ); operations.Add( CreateLinkAssetOperation( AssetFieldType.SquareMarketingImage, assetGroupResourceName, squareMarketingImageResourceName ) ); return operations; } /// <summary> /// Creates a MutateOperation that creates a new text asset. /// </summary> /// <param name="assetResourceName">The resource name of the text asset to be /// created.</param> /// <param name="text">The text of the asset to be created.</param> /// <returns>A MutateOperation that creates the new text asset.</returns> private MutateOperation CreateTextAssetOperation( string assetResourceName, string text) => new MutateOperation() { AssetOperation = new AssetOperation() { Create = new Asset() { ResourceName = assetResourceName, TextAsset = new TextAsset() { Text = text } } } }; /// <summary> /// Creates a MutateOperation that creates a new image asset. /// </summary> /// <param name="assetResourceName">The resource name of the text asset to be /// created.</param> /// <param name="url">The url of the image to be retrieved and put into an asset.</param> /// <param name="assetName">The asset name.</param> /// <param name="config">The Google Ads config.</param> /// <returns>A MutateOperation that creates a new image asset.</returns> private MutateOperation CreateImageAssetOperation( string assetResourceName, string url, string assetName, GoogleAdsConfig config) => new MutateOperation() { AssetOperation = new AssetOperation() { Create = new Asset() { ResourceName = assetResourceName, ImageAsset = new ImageAsset() { Data = ByteString.CopyFrom( MediaUtilities.GetAssetDataFromUrl(url, config) ) }, // Provide a unique friendly name to identify your asset. // When there is an existing image asset with the same content but a // different name, the new name will be dropped silently. Name = assetName } } }; /// <summary> /// Creates a MutateOperation that links an asset to an asset group. /// </summary> /// <param name="fieldType">The field type of the asset to be linked.</param> /// <param name="linkedEntityResourceName">The resource name of the entity (asset group or /// campaign) to link the asset to.</param> /// <param name="assetResourceName">The resource name of the text asset to be /// linked.</param> /// <param name="brandGuidelinesEnabled">Whether or not to enable brand guidelines.</param> /// <returns>A MutateOperation that links an asset to an asset group.</returns> private MutateOperation CreateLinkAssetOperation( AssetFieldType fieldType, string linkedEntityResourceName, string assetResourceName, bool brandGuidelinesEnabled = false) { if (brandGuidelinesEnabled) { return new MutateOperation() { CampaignAssetOperation = new CampaignAssetOperation() { Create = new CampaignAsset() { FieldType = fieldType, Campaign = linkedEntityResourceName, Asset = assetResourceName } } }; } else { return new MutateOperation() { AssetGroupAssetOperation = new AssetGroupAssetOperation() { Create = new AssetGroupAsset() { FieldType = fieldType, AssetGroup = linkedEntityResourceName, Asset = assetResourceName } } }; } } /// <summary> /// Retrieves the list of customer conversion goals. /// </summary> /// <param name="client">The Google Ads Client.</param> /// <param name="customerId">The customer's id.</param> /// <returns>A list customer conversion goals.</returns> private List<CustomerConversionGoal> GetCustomerConversionGoals( GoogleAdsClient client, long customerId) { // Get the GoogleAdsService. GoogleAdsServiceClient googleAdsServiceClient = client.GetService(Services.V21.GoogleAdsService); List<CustomerConversionGoal> conversionGoals = new List<CustomerConversionGoal>(); SearchGoogleAdsRequest request = new SearchGoogleAdsRequest() { CustomerId = customerId.ToString(), Query = @"SELECT customer_conversion_goal.category, customer_conversion_goal.origin FROM customer_conversion_goal" }; // The number of conversion goals is typically less than 50 so we use // GoogleAdsService.search instead of search_stream. PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> searchPagedResponse = googleAdsServiceClient.Search(request); // Iterate over the results and build the list of conversion goals. foreach (GoogleAdsRow row in searchPagedResponse) { conversionGoals.Add(row.CustomerConversionGoal); } return conversionGoals; } /// <summary> /// Creates a list of MutateOperations that override customer conversion goals. /// </summary> /// <param name="customerId">The customer's id.</param> /// <param name="conversionGoals">A list customer conversion goals.</param> /// <returns>A list customer conversion goal operations.</returns> private List<MutateOperation> CreateCustomerConversionGoalOperations( long customerId, List<CustomerConversionGoal> conversionGoals) { List<MutateOperation> operations = new List<MutateOperation>(); foreach (CustomerConversionGoal conversionGoal in conversionGoals) { CustomerConversionGoal newConversionGoal = new CustomerConversionGoal() { ResourceName = ResourceNames.CustomerConversionGoal( customerId, conversionGoal.Category, conversionGoal.Origin ), }; // Change the biddability for the campaign conversion goal. // Set biddability to True for the desired (category, origin). // Set biddability to False for all other conversion goals. // Note: // 1- It is assumed that this Conversion Action // (category=PURCHASE, origin=WEBSITE) exists in this account. // 2- More than one goal can be biddable if desired. This example // shows only one. newConversionGoal.Biddable = conversionGoal.Category == ConversionActionCategory.Purchase && conversionGoal.Origin == ConversionOrigin.Website; operations.Add( new MutateOperation() { CustomerConversionGoalOperation = new CustomerConversionGoalOperation() { Update = newConversionGoal, UpdateMask = FieldMasks.AllSetFieldsOf(newConversionGoal) } } ); } return operations; } /// <summary> /// Creates a list of MutateOperations that create a new asset group /// listing group filter. /// </summary> /// <param name="assetGroupResourceName">The resource name of the asset group.</param> /// <returns>A list of mutate operations.</returns> private List<MutateOperation> CreateAssetGroupListingGroupOperations( string assetGroupResourceName) { List<MutateOperation> operations = new List<MutateOperation>(); // Creates a new ad group criterion containing the "default" listing group (All // products). AssetGroupListingGroupFilter listingGroupFilter = new AssetGroupListingGroupFilter() { AssetGroup = assetGroupResourceName, // Since this is the root node, do not set the ParentListingGroupFilter. For all // other nodes, this would refer to the parent listing group filter resource name. // ParentListingGroupFilter = "<PARENT FILTER NAME>" // The UnitIncluded means this node has no children. Type = ListingGroupFilterType.UnitIncluded, // Because this is a Performance Max campaign for retail, we need to specify that // this is in the shopping listing source. ListingSource = ListingGroupFilterListingSource.Shopping }; AssetGroupListingGroupFilterOperation operation = new AssetGroupListingGroupFilterOperation() { Create = listingGroupFilter }; operations.Add( new MutateOperation() { AssetGroupListingGroupFilterOperation = operation } ); return operations; } /// <summary> /// Prints the details of a MutateGoogleAdsResponse. Parses the "response" oneof field name /// and uses it to extract the new entity's name and resource name. /// </summary> /// <param name="response">A MutateGoogleAdsResponse instance.</param> private void PrintResponseDetails(MutateGoogleAdsResponse response) { // Parse the Mutate response to print details about the entities that were created // in the request. foreach (MutateOperationResponse operationResponse in response.MutateOperationResponses) { string resourceName; string entityName = operationResponse.ResponseCase.ToString(); // Trim the substring "Result" from the end of the entity name. entityName = entityName.Remove(entityName.Length - 6); switch (operationResponse.ResponseCase) { case MutateOperationResponse.ResponseOneofCase.AdGroupResult: resourceName = operationResponse.AdGroupResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.AdGroupAdResult: resourceName = operationResponse.AdGroupAdResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.CampaignResult: resourceName = operationResponse.CampaignResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.CampaignBudgetResult: resourceName = operationResponse.CampaignBudgetResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.CampaignCriterionResult: resourceName = operationResponse.CampaignCriterionResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.SmartCampaignSettingResult: resourceName = operationResponse.SmartCampaignSettingResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.AssetResult: resourceName = operationResponse.AssetResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.AssetGroupAssetResult: resourceName = operationResponse.AssetGroupAssetResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.AssetGroupResult: resourceName = operationResponse.AssetGroupResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.AssetGroupListingGroupFilterResult: resourceName = operationResponse.AssetGroupListingGroupFilterResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.CampaignConversionGoalResult: resourceName = operationResponse.CampaignConversionGoalResult.ResourceName; break; case MutateOperationResponse.ResponseOneofCase.CustomerConversionGoalResult: resourceName = operationResponse.CustomerConversionGoalResult.ResourceName; break; default: resourceName = "<not found>"; break; } Console.WriteLine( $"Created a(n) {entityName} with resource name: '{resourceName}'."); } } } }
PHP
<?php /** * Copyright 2021 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\ShoppingAds; require __DIR__ . '/../../vendor/autoload.php'; use GetOpt\GetOpt; use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames; use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser; use Google\Ads\GoogleAds\Examples\Utils\Helper; use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder; use Google\Ads\GoogleAds\Lib\V21\GoogleAdsClient; use Google\Ads\GoogleAds\Lib\V21\GoogleAdsClientBuilder; use Google\Ads\GoogleAds\Lib\V21\GoogleAdsException; use Google\Ads\GoogleAds\Util\FieldMasks; use Google\Ads\GoogleAds\Util\V21\ResourceNames; use Google\Ads\GoogleAds\V21\Common\ImageAsset; use Google\Ads\GoogleAds\V21\Common\LanguageInfo; use Google\Ads\GoogleAds\V21\Common\LocationInfo; use Google\Ads\GoogleAds\V21\Common\MaximizeConversionValue; use Google\Ads\GoogleAds\V21\Common\TextAsset; use Google\Ads\GoogleAds\V21\Enums\AdvertisingChannelTypeEnum\AdvertisingChannelType; use Google\Ads\GoogleAds\V21\Enums\AssetFieldTypeEnum\AssetFieldType; use Google\Ads\GoogleAds\V21\Enums\AssetGroupStatusEnum\AssetGroupStatus; use Google\Ads\GoogleAds\V21\Enums\BudgetDeliveryMethodEnum\BudgetDeliveryMethod; use Google\Ads\GoogleAds\V21\Enums\CampaignStatusEnum\CampaignStatus; use Google\Ads\GoogleAds\V21\Enums\ConversionActionCategoryEnum\ConversionActionCategory; use Google\Ads\GoogleAds\V21\Enums\ConversionOriginEnum\ConversionOrigin; use Google\Ads\GoogleAds\V21\Enums\EuPoliticalAdvertisingStatusEnum\EuPoliticalAdvertisingStatus; use Google\Ads\GoogleAds\V21\Enums\ListingGroupFilterListingSourceEnum\ListingGroupFilterListingSource; use Google\Ads\GoogleAds\V21\Enums\ListingGroupFilterTypeEnum\ListingGroupFilterType; use Google\Ads\GoogleAds\V21\Errors\GoogleAdsError; use Google\Ads\GoogleAds\V21\Resources\Asset; use Google\Ads\GoogleAds\V21\Resources\AssetGroup; use Google\Ads\GoogleAds\V21\Resources\AssetGroupAsset; use Google\Ads\GoogleAds\V21\Resources\AssetGroupListingGroupFilter; use Google\Ads\GoogleAds\V21\Resources\Campaign; use Google\Ads\GoogleAds\V21\Resources\Campaign\ShoppingSetting; use Google\Ads\GoogleAds\V21\Resources\CampaignAsset; use Google\Ads\GoogleAds\V21\Resources\CampaignBudget; use Google\Ads\GoogleAds\V21\Resources\CampaignConversionGoal; use Google\Ads\GoogleAds\V21\Resources\CampaignCriterion; use Google\Ads\GoogleAds\V21\Services\AssetGroupAssetOperation; use Google\Ads\GoogleAds\V21\Services\AssetGroupListingGroupFilterOperation; use Google\Ads\GoogleAds\V21\Services\AssetGroupOperation; use Google\Ads\GoogleAds\V21\Services\AssetOperation; use Google\Ads\GoogleAds\V21\Services\CampaignAssetOperation; use Google\Ads\GoogleAds\V21\Services\CampaignBudgetOperation; use Google\Ads\GoogleAds\V21\Services\CampaignConversionGoalOperation; use Google\Ads\GoogleAds\V21\Services\CampaignCriterionOperation; use Google\Ads\GoogleAds\V21\Services\CampaignOperation; use Google\Ads\GoogleAds\V21\Services\GoogleAdsRow; use Google\Ads\GoogleAds\V21\Services\MutateGoogleAdsRequest; use Google\Ads\GoogleAds\V21\Services\MutateGoogleAdsResponse; use Google\Ads\GoogleAds\V21\Services\MutateOperation; use Google\Ads\GoogleAds\V21\Services\MutateOperationResponse; use Google\Ads\GoogleAds\V21\Services\SearchGoogleAdsRequest; use Google\ApiCore\ApiException; use Google\ApiCore\Serializer; /** * This example shows how to create a Performance Max retail campaign. * * This will be created for "All products". * * For more information about Performance Max retail campaigns, see * https://developers.google.com/google-ads/api/docs/performance-max/retail. * * Prerequisites: * - You need to have access to a Merchant Center account. You can find * instructions to create a Merchant Center account here: * https://support.google.com/merchants/answer/188924. * This account must be linked to your Google Ads account. The integration * instructions can be found at: * https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center * - You need your Google Ads account to track conversions. The different ways * to track conversions can be found here: * https://support.google.com/google-ads/answer/1722054. * - You must have at least one conversion action in the account. For more about conversion * actions, see * https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. */ class AddPerformanceMaxRetailCampaign { private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE'; private const MERCHANT_CENTER_ACCOUNT_ID = 'INSERT_MERCHANT_CENTER_ACCOUNT_ID_HERE'; // The final URL for the generated ads. Must have the same domain as the Merchant Center // account. private const FINAL_URL = 'INSERT_FINAL_URL_HERE'; // Optional: Indicates whether the created campaign is enabled for brand guidelines. private const BRAND_GUIDELINES_ENABLED = false; // We specify temporary IDs that are specific to a single mutate request. // Temporary IDs are always negative and unique within one mutate request. // // See https://developers.google.com/google-ads/api/docs/mutating/best-practices // for further details. // // These temporary IDs are fixed because they are used in multiple places. private const BUDGET_TEMPORARY_ID = -1; private const PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID = -2; private const ASSET_GROUP_TEMPORARY_ID = -3; // There are also entities that will be created in the same request but do not need to be fixed // temporary IDs because they are referenced only once. /** @var int the negative temporary ID used in bulk mutates. */ private static $nextTempId = self::ASSET_GROUP_TEMPORARY_ID - 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::MERCHANT_CENTER_ACCOUNT_ID => GetOpt::REQUIRED_ARGUMENT, ArgumentNames::FINAL_URL => GetOpt::REQUIRED_ARGUMENT, ArgumentNames::BRAND_GUIDELINES_ENABLED => GetOpt::OPTIONAL_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::MERCHANT_CENTER_ACCOUNT_ID] ?: self::MERCHANT_CENTER_ACCOUNT_ID, $options[ArgumentNames::FINAL_URL] ?: self::FINAL_URL, filter_var( $options[ArgumentNames::BRAND_GUIDELINES_ENABLED] ?: self::BRAND_GUIDELINES_ENABLED, FILTER_VALIDATE_BOOLEAN ) ); } 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 int $merchantCenterAccountId the Merchant Center account ID * @param string $finalUrl the final URL for the asset group of the campaign * @param bool $brandGuidelinesEnabled whether the created campaign will be enabled for brand * guidelines */ public static function runExample( GoogleAdsClient $googleAdsClient, int $customerId, int $merchantCenterAccountId, string $finalUrl, bool $brandGuidelinesEnabled ) { // This campaign will override the customer conversion goals. // Retrieves the current list of customer conversion goals. $customerConversionGoals = self::getCustomerConversionGoals( $googleAdsClient, $customerId ); // Performance Max campaigns require that repeated assets such as headlines // and descriptions be created before the campaign. // For the list of required assets for a Performance Max campaign, see // https://developers.google.com/google-ads/api/docs/performance-max/assets. // // Creates the headlines. $headlineAssetResourceNames = self::createMultipleTextAssets( $googleAdsClient, $customerId, ["Travel", "Travel Reviews", "Book travel"] ); // Creates the descriptions. $descriptionAssetResourceNames = self::createMultipleTextAssets( $googleAdsClient, $customerId, ["Take to the air!", "Fly to the sky!"] ); // It's important to create the below entities in this order because they depend on // each other. $operations = []; // The below methods create and return MutateOperations that we later // provide to the GoogleAdsService.Mutate method in order to create the // entities in a single request. Since the entities for a Performance Max // campaign are closely tied to one-another, it's considered a best practice // to create them in a single Mutate request so they all complete // successfully or fail entirely, leaving no orphaned entities. See: // https://developers.google.com/google-ads/api/docs/mutating/overview. $operations[] = self::createCampaignBudgetOperation($customerId); $operations[] = self::createPerformanceMaxCampaignOperation( $customerId, $merchantCenterAccountId, $brandGuidelinesEnabled ); $operations = array_merge($operations, self::createCampaignCriterionOperations($customerId)); $operations[] = self::createAssetGroupOperation($customerId, $finalUrl); $operations[] = self::createAssetGroupListingGroupFilterOperation($customerId); $operations = array_merge($operations, self::createAssetandAssetGroupAssetOperations( $customerId, $headlineAssetResourceNames, $descriptionAssetResourceNames, $brandGuidelinesEnabled )); $operations = array_merge($operations, self::createConversionGoalOperations( $customerId, $customerConversionGoals )); // Issues a mutate request to create everything and prints its information. $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); $response = $googleAdsServiceClient->mutate( MutateGoogleAdsRequest::build($customerId, $operations) ); self::printResponseDetails($response); } /** * Creates a MutateOperation that creates a new CampaignBudget. * * A temporary ID will be assigned to this campaign budget so that it can be * referenced by other objects being created in the same Mutate request. * * @param int $customerId the customer ID * @return MutateOperation the mutate operation that creates a campaign budget */ private static function createCampaignBudgetOperation(int $customerId): MutateOperation { // Creates a mutate operation that creates a campaign budget operation. return new MutateOperation([ 'campaign_budget_operation' => new CampaignBudgetOperation([ 'create' => new CampaignBudget([ // Sets a temporary ID in the budget's resource name so it can be referenced // by the campaign in later steps. 'resource_name' => ResourceNames::forCampaignBudget( $customerId, self::BUDGET_TEMPORARY_ID ), 'name' => 'Performance Max retail campaign budget #' . Helper::getPrintableDatetime(), // The budget period already defaults to DAILY. 'amount_micros' => 50000000, 'delivery_method' => BudgetDeliveryMethod::STANDARD, // A Performance Max campaign cannot use a shared campaign budget. 'explicitly_shared' => false ]) ]) ]); } /** * Creates a MutateOperation that creates a new Performance Max campaign. * * A temporary ID will be assigned to this campaign so that it can * be referenced by other objects being created in the same Mutate request. * * @param int $customerId the customer ID * @param int $merchantCenterAccountId the Merchant Center account ID * @param bool $brandGuidelinesEnabled whether the created campaign will be enabled for brand * guidelines * @return MutateOperation the mutate operation that creates the campaign */ private static function createPerformanceMaxCampaignOperation( int $customerId, int $merchantCenterAccountId, bool $brandGuidelinesEnabled ): MutateOperation { // Creates a mutate operation that creates a campaign operation. return new MutateOperation([ 'campaign_operation' => new CampaignOperation([ 'create' => new Campaign([ 'name' => 'Performance Max retail campaign #' . Helper::getPrintableDatetime(), // Assigns the resource name with a temporary ID. 'resource_name' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), // Sets the budget using the given budget resource name. 'campaign_budget' => ResourceNames::forCampaignBudget( $customerId, self::BUDGET_TEMPORARY_ID ), // The campaign is the only entity in the mutate request that should have its // status set. // Recommendation: Set the campaign to PAUSED when creating it to prevent // the ads from immediately serving. 'status' => CampaignStatus::PAUSED, // All Performance Max campaigns have an advertising_channel_type of // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. 'advertising_channel_type' => AdvertisingChannelType::PERFORMANCE_MAX, // Bidding strategy must be set directly on the campaign. // Setting a portfolio bidding strategy by resource name is not supported. // Max Conversion and Max Conversion Value are the only strategies supported // for Performance Max campaigns. // An optional ROAS (Return on Advertising Spend) can be set for // maximize_conversion_value. The ROAS value must be specified as a ratio in // the API. It is calculated by dividing "total value" by "total spend". // For more information on Max Conversion Value, see the support article: // http://support.google.com/google-ads/answer/7684216. // A target_roas of 3.5 corresponds to a 350% return on ad spend. 'maximize_conversion_value' => new MaximizeConversionValue([ 'target_roas' => 3.5 ]), // Below is what you would use if you want to maximize conversions // You can optionally set the 'target_cpa_micros' field on MaximizeConversions. // This is the average amount that you would like to spend per conversion // action. // 'maximize_conversions' => new MaximizeConversions(), // Sets the Final URL expansion opt out. This flag is specific to // Performance Max campaigns. If opted out (true), only the final URLs in // the asset group or URLs specified in the advertiser's Google Merchant // Center or business data feeds are targeted. // // If opted in (false), the entire domain will be targeted. For best // results, set this value to false to opt in and allow URL expansions. You // can optionally add exclusions to limit traffic to parts of your website. // // For a Retail campaign, we want the final URL's to be limited to those // explicitly surfaced via GMC. 'url_expansion_opt_out' => true, // Sets if the campaign is enabled for brand guidelines. For more information // on brand guidelines, see // https://support.google.com/google-ads/answer/14934472. 'brand_guidelines_enabled' => $brandGuidelinesEnabled, // Sets the shopping settings. 'shopping_setting' => new ShoppingSetting([ 'merchant_id' => $merchantCenterAccountId, // Optional: To use products only from a specific feed, set feed_label to // the feed label used in Merchant Center. // See: https://support.google.com/merchants/answer/12453549. // Removing the feed_label field will use products from all feeds. // 'feed_label' => 'INSERT_FEED_LABEL_HERE' ]), // Declare whether or not this campaign serves political ads targeting the EU. 'contains_eu_political_advertising' => EuPoliticalAdvertisingStatus::DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING // Optional fields. 'start_date' => date('Ymd', strtotime('+1 day')), 'end_date' => date('Ymd', strtotime('+365 days')) ]) ]) ]); } /** * Creates a list of MutateOperations that create new campaign criteria. * * @param int $customerId the customer ID * @return MutateOperation[] a list of MutateOperations that create the new campaign criteria */ private static function createCampaignCriterionOperations(int $customerId): array { $operations = []; // Sets the LOCATION campaign criteria. // Target all of New York City except Brooklyn. // Location IDs are listed here: // https://developers.google.com/google-ads/api/reference/data/geotargets // and they can also be retrieved using the GeoTargetConstantService as shown // here: https://developers.google.com/google-ads/api/docs/targeting/location-targeting $operations[] = new MutateOperation([ 'campaign_criterion_operation' => new CampaignCriterionOperation([ 'create' => new CampaignCriterion([ 'campaign' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), 'location' => new LocationInfo([ // Adds one positive location target for New York City (ID=1023191), // specifically adding the positive criteria before the negative one. 'geo_target_constant' => ResourceNames::forGeoTargetConstant(1023191) ]), 'negative' => false ]) ]) ]); // Next adds the negative target for Brooklyn. $operations[] = new MutateOperation([ 'campaign_criterion_operation' => new CampaignCriterionOperation([ 'create' => new CampaignCriterion([ 'campaign' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), 'location' => new LocationInfo([ // Next add the negative target for Brooklyn (ID=1022762). 'geo_target_constant' => ResourceNames::forGeoTargetConstant(1022762) ]), 'negative' => true ]) ]) ]); // Sets the LANGUAGE campaign criterion. $operations[] = new MutateOperation([ 'campaign_criterion_operation' => new CampaignCriterionOperation([ 'create' => new CampaignCriterion([ 'campaign' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), // Sets the language. // For a list of all language codes, see: // https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7 'language' => new LanguageInfo([ 'language_constant' => ResourceNames::forLanguageConstant(1000) // English ]) ]) ]) ]); return $operations; } /** * Creates multiple text assets and returns the list of resource names. * * These repeated assets must be created in a separate request prior to creating the campaign. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param string[] $texts a list of strings, each of which will be used to create a text asset * @return string[] a list of asset resource names */ private static function createMultipleTextAssets( GoogleAdsClient $googleAdsClient, int $customerId, array $texts ): array { // Here again, we use the GoogleAdService to create multiple text assets in a single // request. $operations = []; foreach ($texts as $text) { // Creates a mutate operation for a text asset. $operations[] = new MutateOperation([ 'asset_operation' => new AssetOperation([ 'create' => new Asset(['text_asset' => new TextAsset(['text' => $text])]) ]) ]); } // Issues a mutate request to add all assets. $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); /** @var MutateGoogleAdsResponse $mutateGoogleAdsResponse */ $mutateGoogleAdsResponse = $googleAdsServiceClient->mutate( MutateGoogleAdsRequest::build($customerId, $operations) ); $assetResourceNames = []; foreach ($mutateGoogleAdsResponse->getMutateOperationResponses() as $response) { /** @var MutateOperationResponse $response */ $assetResourceNames[] = $response->getAssetResult()->getResourceName(); } self::printResponseDetails($mutateGoogleAdsResponse); return $assetResourceNames; } /** * Creates a MutateOperation that creates a new asset group. * * A temporary ID will be assigned to this asset group so that it can * be referenced by other objects being created in the same Mutate request. * * @param int $customerId the customer ID * @return MutateOperation a mutate operation creates a new asset group. */ private static function createAssetGroupOperation( int $customerId, string $finalUrl ): MutateOperation { // Creates a new mutate operation that creates an asset group operation. return new MutateOperation([ 'asset_group_operation' => new AssetGroupOperation([ 'create' => new AssetGroup([ 'resource_name' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'name' => 'Performance Max retail asset group #' . Helper::getPrintableDatetime(), 'campaign' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), 'final_urls' => [$finalUrl], 'final_mobile_urls' => [$finalUrl], 'status' => AssetGroupStatus::PAUSED ]) ]) ]); } /** * Creates a MutateOperation that creates a new asset group listing group filter. * * A temporary ID will be assigned to this listing group filter so that it can be referenced by * other objects being created in the same Mutate request. * * @param int $customerId the customer ID * @return MutateOperation a MutateOperation that creates a new asset group listing group filter */ private static function createAssetGroupListingGroupFilterOperation( int $customerId ): MutateOperation { return new MutateOperation([ 'asset_group_listing_group_filter_operation' => new AssetGroupListingGroupFilterOperation([ // Creates a new asset group listing group filter containing the "default" // listing group (All products). 'create' => new AssetGroupListingGroupFilter([ 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), // Since this is the root node, do not set the 'parent_listing_group_filter' // field. For all other nodes, this would refer to the parent listing group // filter resource name. // // UNIT_INCLUDED means this node has no children. 'type' => ListingGroupFilterType::UNIT_INCLUDED, // Because this is a Performance Max campaign for retail, we need to specify // that this is in the shopping listing source. 'listing_source' => ListingGroupFilterListingSource::SHOPPING ]) ]) ]); } /** * Creates a list of MutateOperations that create new asset group asset and assets. * * A temporary ID will be assigned to this asset group so that it can * be referenced by other objects being created in the same mutate request. * * @param int $customerId the customer ID * @param string[] $headlineAssetResourceNames a list of headline resource names * @param string[] $descriptionAssetResourceNames a list of description resource names * @param bool $brandGuidelinesEnabled whether the created campaign will be enabled for brand * guidelines * @return MutateOperation[] a list of MutateOperations that create new asset group assets and * assets */ private static function createAssetandAssetGroupAssetOperations( int $customerId, array $headlineAssetResourceNames, array $descriptionAssetResourceNames, bool $brandGuidelinesEnabled ): array { $operations = []; // For the list of required assets for a Performance Max campaign, see // https://developers.google.com/google-ads/api/docs/performance-max/assets // An AssetGroup is linked to an Asset by creating a new AssetGroupAsset // and providing: // - the resource name of the AssetGroup // - the resource name of the Asset // - the field_type of the Asset in this AssetGroup. // // To learn more about AssetGroups, see // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. // Links the previously created multiple text assets. // Links the headline assets. foreach ($headlineAssetResourceNames as $resourceName) { $operations[] = new MutateOperation([ 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 'create' => new AssetGroupAsset([ 'asset' => $resourceName, 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'field_type' => AssetFieldType::HEADLINE ]) ]) ]); } // Links the description assets. foreach ($descriptionAssetResourceNames as $resourceName) { $operations[] = new MutateOperation([ 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 'create' => new AssetGroupAsset([ 'asset' => $resourceName, 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'field_type' => AssetFieldType::DESCRIPTION ]) ]) ]); } // Creates and links the long headline text asset. $operations = array_merge($operations, self::createAndLinkTextAsset( $customerId, 'Travel the World', AssetFieldType::LONG_HEADLINE )); // Creates and links the business name text asset. $operations = array_merge($operations, self::createAndLinkBrandAssets( $customerId, $brandGuidelinesEnabled, 'Interplanetary Cruises', 'https://gaagl.page.link/1Crm', 'Logo Image' )); // Creates and links the image assets. // Creates and links the Marketing Image Asset. $operations = array_merge($operations, self::createAndLinkImageAsset( $customerId, 'https://gaagl.page.link/Eit5', AssetFieldType::MARKETING_IMAGE, 'Marketing Image' )); // Creates and links the Square Marketing Image Asset. $operations = array_merge($operations, self::createAndLinkImageAsset( $customerId, 'https://gaagl.page.link/bjYi', AssetFieldType::SQUARE_MARKETING_IMAGE, 'Square Marketing Image' )); // After being created the list must be sorted so that all asset operations come before all // the asset group asset operations, otherwise the API will reject the request. return self::sortAssetAndAssetGroupAssetOperations($operations); } /** * Creates a list of MutateOperations that create a new linked text asset. * * @param int $customerId the customer ID * @param string $text the text of the asset to be created * @param int $fieldType the field type of the new asset in the AssetGroupAsset * @return MutateOperation[] a list of MutateOperations that create a new linked text asset */ private static function createAndLinkTextAsset( int $customerId, string $text, int $fieldType ): array { $operations = []; // Creates a new mutate operation that creates a text asset. $operations[] = new MutateOperation([ 'asset_operation' => new AssetOperation([ 'create' => new Asset([ 'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId), 'text_asset' => new TextAsset(['text' => $text]) ]) ]) ]); // Creates an asset group asset to link the asset to the asset group. $operations[] = new MutateOperation([ 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 'create' => new AssetGroupAsset([ 'asset' => ResourceNames::forAsset($customerId, self::$nextTempId), 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'field_type' => $fieldType ]) ]) ]); self::$nextTempId--; return $operations; } /** * Creates a list of MutateOperations that create a new linked image asset. * * @param int $customerId the customer ID * @param string $url the URL of the image to be retrieved and put into an asset * @param int $fieldType the field type of the new asset in the AssetGroupAsset * @param string $assetName the asset name * @return MutateOperation[] a list of MutateOperations that create a new linked image asset */ private static function createAndLinkImageAsset( int $customerId, string $url, int $fieldType, string $assetName ): array { $operations = []; // Creates a new mutate operation that creates an image asset. $operations[] = new MutateOperation([ 'asset_operation' => new AssetOperation([ 'create' => new Asset([ 'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId), // Provide a unique friendly name to identify your asset. // When there is an existing image asset with the same content but a different // name, the new name will be dropped silently. 'name' => $assetName, 'image_asset' => new ImageAsset(['data' => file_get_contents($url)]) ]) ]) ]); // Creates an asset group asset to link the asset to the asset group. $operations[] = new MutateOperation([ 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 'create' => new AssetGroupAsset([ 'asset' => ResourceNames::forAsset($customerId, self::$nextTempId), 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'field_type' => $fieldType ]) ]) ]); self::$nextTempId--; return $operations; } /** * Creates a list of MutateOperations that create linked brand assets. * * @param int $customerId the customer ID * @param bool $brandGuidelinesEnabled whether the created campaign will be enabled for brand * guidelines * @param string $businessName the business name text to be put into an asset * @param string $logoUrl the URL of the logo to be retrieved and put into an asset * @param string $logoName the asset name of the logo * @return MutateOperation[] a list of MutateOperations that create a new linked text asset */ private static function createAndLinkBrandAssets( int $customerId, bool $brandGuidelinesEnabled, string $businessName, string $logoUrl, string $logoName ): array { $operations = []; // Creates a new mutate operation that creates a text asset. $businessNameTempId = self::$nextTempId--; $operations[] = new MutateOperation([ 'asset_operation' => new AssetOperation([ 'create' => new Asset([ 'resource_name' => ResourceNames::forAsset($customerId, $businessNameTempId), 'text_asset' => new TextAsset(['text' => $businessName]) ]) ]) ]); $logoTempId = self::$nextTempId--; // Creates a new mutate operation that creates an image asset. $operations[] = new MutateOperation([ 'asset_operation' => new AssetOperation([ 'create' => new Asset([ 'resource_name' => ResourceNames::forAsset($customerId, $logoTempId), // Provide a unique friendly name to identify your asset. // When there is an existing image asset with the same content but a different // name, the new name will be dropped silently. 'name' => $logoName, 'image_asset' => new ImageAsset(['data' => file_get_contents($logoUrl)]) ]) ]) ]); if ($brandGuidelinesEnabled) { // Creates a campaign asset to link the business name and logo assets to the campaign. $operations[] = new MutateOperation([ 'campaign_asset_operation' => new CampaignAssetOperation([ 'create' => new CampaignAsset([ 'asset' => ResourceNames::forAsset($customerId, $businessNameTempId), 'campaign' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), 'field_type' => AssetFieldType::BUSINESS_NAME ]) ]) ]); $operations[] = new MutateOperation([ 'campaign_asset_operation' => new CampaignAssetOperation([ 'create' => new CampaignAsset([ 'asset' => ResourceNames::forAsset($customerId, $logoTempId), 'campaign' => ResourceNames::forCampaign( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), 'field_type' => AssetFieldType::LOGO ]) ]) ]); } else { // Creates an asset group asset to link the business name and logo assets to the asset // group. $operations[] = new MutateOperation([ 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 'create' => new AssetGroupAsset([ 'asset' => ResourceNames::forAsset($customerId, $businessNameTempId), 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'field_type' => AssetFieldType::BUSINESS_NAME ]) ]) ]); $operations[] = new MutateOperation([ 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 'create' => new AssetGroupAsset([ 'asset' => ResourceNames::forAsset($customerId, $logoTempId), 'asset_group' => ResourceNames::forAssetGroup( $customerId, self::ASSET_GROUP_TEMPORARY_ID ), 'field_type' => AssetFieldType::LOGO ]) ]) ]); } return $operations; } /** * Sorts a list of asset and asset group asset operations. This sorts the list such that all * asset operations precede all asset group asset operations. If asset group assets are created * before assets then an error will be returned by the API. * * @param MutateOperation[] $operations a list of asset and asset group asset mutate operations * @return MutateOperation[] a sorted list of asset and asset group asset mutate operations */ private static function sortAssetAndAssetGroupAssetOperations(array $operations): array { usort( $operations, function (MutateOperation $operation1, MutateOperation $operation2) { if (!is_null($operation1->getAssetOperation())) { return -1; } elseif (!is_null($operation1->getAssetOperation())) { return 0; } else { return 1; } } ); return $operations; } /** * Retrieves the list of customer conversion goals. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @return array list of dicts containing the category and origin of customer conversion goals */ private static function getCustomerConversionGoals( GoogleAdsClient $googleAdsClient, int $customerId ): array { $customerConversionGoals = []; $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); // Creates a query that retrieves all customer conversion goals. $query = 'SELECT customer_conversion_goal.category, customer_conversion_goal.origin ' . 'FROM customer_conversion_goal'; // The number of conversion goals is typically less than 50 so we use a search request // instead of search stream. $response = $googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query)); // Iterates over all rows in all pages and builds the list of conversion goals. foreach ($response->iterateAllElements() as $googleAdsRow) { /** @var GoogleAdsRow $googleAdsRow */ $customerConversionGoals[] = [ 'category' => $googleAdsRow->getCustomerConversionGoal()->getCategory(), 'origin' => $googleAdsRow->getCustomerConversionGoal()->getOrigin() ]; } return $customerConversionGoals; } /** * Creates a list of MutateOperations that override customer conversion goals. * * @param int $customerId the customer ID * @param array $customerConversionGoals the list of customer conversion goals that will be * overridden * @return MutateOperation[] a list of MutateOperations that update campaign conversion goals */ private static function createConversionGoalOperations( int $customerId, array $customerConversionGoals ): array { $operations = []; // To override the customer conversion goals, we will change the biddability of each of the // customer conversion goals so that only the desired conversion goal is biddable in this // campaign. foreach ($customerConversionGoals as $customerConversionGoal) { $campaignConversionGoal = new CampaignConversionGoal([ 'resource_name' => ResourceNames::forCampaignConversionGoal( $customerId, self::PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, ConversionActionCategory::name($customerConversionGoal['category']), ConversionOrigin::name($customerConversionGoal['origin']) ) ]); // Changes the biddability for the campaign conversion goal. // Sets biddability to true for the desired (category, origin). // Sets biddability to false for all other conversion goals. // Note: // 1- It is assumed that this Conversion Action // (category=PURCHASE, origin=WEBSITE) exists in this account. // 2- More than one goal can be biddable if desired. This example // shows only one. if ( $customerConversionGoal["category"] === ConversionActionCategory::PURCHASE && $customerConversionGoal["origin"] === ConversionOrigin::WEBSITE ) { $campaignConversionGoal->setBiddable(true); } else { $campaignConversionGoal->setBiddable(false); } $operations[] = new MutateOperation([ 'campaign_conversion_goal_operation' => new CampaignConversionGoalOperation([ 'update' => $campaignConversionGoal, // Sets the update mask on the operation. Here the update mask will be a list // of all the fields that were set on the update object. 'update_mask' => FieldMasks::allSetFieldsOf($campaignConversionGoal) ]) ]); } return $operations; } /** * Prints the details of a MutateGoogleAdsResponse. Parses the "response" oneof field name and * uses it to extract the new entity's name and resource name. * * @param MutateGoogleAdsResponse $mutateGoogleAdsResponse the mutate Google Ads response */ private static function printResponseDetails( MutateGoogleAdsResponse $mutateGoogleAdsResponse ): void { foreach ($mutateGoogleAdsResponse->getMutateOperationResponses() as $response) { /** @var MutateOperationResponse $response */ $getter = Serializer::getGetter($response->getResponse()); printf( "Created a(n) %s with '%s'.%s", preg_replace( '/Result$/', '', ucfirst(Serializer::toCamelCase($response->getResponse())) ), $response->$getter()->getResourceName(), PHP_EOL ); } } } AddPerformanceMaxRetailCampaign::main();
Python
#!/usr/bin/env python # Copyright 2021 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. """This example shows how to create a Performance Max retail campaign. This will be created for "All products". For more information about Performance Max retail campaigns, see https://developers.google.com/google-ads/api/docs/performance-max/retail Prerequisites: - You need to have access to a Merchant Center account. You can find instructions to create a Merchant Center account here: https://support.google.com/merchants/answer/188924. This account must be linked to your Google Ads account. The integration instructions can be found at: https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center - You need your Google Ads account to track conversions. The different ways to track conversions can be found here: https://support.google.com/google-ads/answer/1722054. - You must have at least one conversion action in the account. For more about conversion actions, see https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions """ import argparse from datetime import datetime, timedelta import sys from typing import Dict, List, Union from uuid import uuid4 from google.api_core import protobuf_helpers from examples.utils.example_helpers import get_image_bytes_from_url from google.ads.googleads.client import GoogleAdsClient from google.ads.googleads.errors import GoogleAdsException from google.ads.googleads.util import convert_snake_case_to_upper_case from google.ads.googleads.v21.enums.types.conversion_action_category import ( ConversionActionCategoryEnum, ) from google.ads.googleads.v21.enums.types.conversion_origin import ( ConversionOriginEnum, ) from google.ads.googleads.v21.enums.types.asset_field_type import ( AssetFieldTypeEnum, ) from google.ads.googleads.v21.resources.types.asset import Asset from google.ads.googleads.v21.resources.types.asset_group import AssetGroup from google.ads.googleads.v21.resources.types.asset_group_asset import ( AssetGroupAsset, ) from google.ads.googleads.v21.resources.types.asset_group_listing_group_filter import ( AssetGroupListingGroupFilter, ) from google.ads.googleads.v21.resources.types.campaign import Campaign from google.ads.googleads.v21.resources.types.campaign_asset import ( CampaignAsset, ) from google.ads.googleads.v21.resources.types.campaign_budget import ( CampaignBudget, ) from google.ads.googleads.v21.resources.types.campaign_conversion_goal import ( CampaignConversionGoal, ) from google.ads.googleads.v21.resources.types.campaign_criterion import ( CampaignCriterion, ) from google.ads.googleads.v21.services.services.asset_group_service import ( AssetGroupServiceClient, ) from google.ads.googleads.v21.services.services.asset_service import ( AssetServiceClient, ) from google.ads.googleads.v21.services.services.campaign_budget_service import ( CampaignBudgetServiceClient, ) from google.ads.googleads.v21.services.services.campaign_conversion_goal_service import ( CampaignConversionGoalServiceClient, ) from google.ads.googleads.v21.services.services.campaign_service import ( CampaignServiceClient, ) from google.ads.googleads.v21.services.services.geo_target_constant_service import ( GeoTargetConstantServiceClient, ) from google.ads.googleads.v21.services.services.google_ads_service import ( GoogleAdsServiceClient, ) from google.ads.googleads.v21.services.types.google_ads_service import ( MutateGoogleAdsResponse, MutateOperationResponse, SearchGoogleAdsRequest, SearchGoogleAdsResponse, ) from google.ads.googleads.v21.services.types.google_ads_service import ( MutateOperation, ) # We specify temporary IDs that are specific to a single mutate request. # Temporary IDs are always negative and unique within one mutate request. # # See https://developers.google.com/google-ads/api/docs/mutating/best-practices # for further details. # # These temporary IDs are fixed because they are used in multiple places. _BUDGET_TEMPORARY_ID: str = "-1" _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID: str = "-2" _ASSET_GROUP_TEMPORARY_ID: str = "-3" # There are also entities that will be created in the same request but do not # need to be fixed temporary IDs because they are referenced only once. _next_temp_id: int = int(_ASSET_GROUP_TEMPORARY_ID) - 1 def main( client: GoogleAdsClient, customer_id: str, merchant_center_account_id: int, final_url: str, brand_guidelines_enabled: bool, ) -> None: """The main method that creates all necessary entities for the example. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. merchant_center_account_id: The Merchant Center account ID. final_url: the final URL. brand_guidelines_enabled: a boolean value indicating if the campaign is enabled for brand guidelines. """ googleads_service: GoogleAdsServiceClient = client.get_service( "GoogleAdsService" ) # This campaign will override the customer conversion goals. # Retrieve the current list of customer conversion goals. customer_conversion_goals: List[ Dict[ str, Union[ ConversionActionCategoryEnum.ConversionActionCategory, ConversionOriginEnum.ConversionOrigin, ], ] ] = get_customer_conversion_goals(client, customer_id) # Performance Max campaigns require that repeated assets such as headlines # and descriptions be created before the campaign. # For the list of required assets for a Performance Max campaign, see # https://developers.google.com/google-ads/api/docs/performance-max/assets # # Create the headlines. headline_asset_resource_names: List[str] = create_multiple_text_assets( client, customer_id, [ "Travel", "Travel Reviews", "Book travel", ], ) # Create the descriptions. description_asset_resource_names: List[str] = create_multiple_text_assets( client, customer_id, [ "Take to the air!", "Fly to the sky!", ], ) # The below methods create and return MutateOperations that we later # provide to the GoogleAdsService.Mutate method in order to create the # entities in a single request. Since the entities for a Performance Max # campaign are closely tied to one-another, it's considered a best practice # to create them in a single Mutate request so they all complete # successfully or fail entirely, leaving no orphaned entities. See: # https://developers.google.com/google-ads/api/docs/mutating/overview campaign_budget_operation: MutateOperation = ( create_campaign_budget_operation( client, customer_id, ) ) performance_max_campaign_operation: MutateOperation = ( create_performance_max_campaign_operation( client, customer_id, merchant_center_account_id, brand_guidelines_enabled, ) ) campaign_criterion_operations: List[MutateOperation] = ( create_campaign_criterion_operations( client, customer_id, ) ) asset_group_operation: MutateOperation = create_asset_group_operation( client, customer_id, final_url ) listing_group_filter_operation: MutateOperation = ( create_listing_group_filter_operation(client, customer_id) ) asset_and_asset_group_asset_operations: List[MutateOperation] = ( create_asset_and_asset_group_asset_operations( client, customer_id, headline_asset_resource_names, description_asset_resource_names, brand_guidelines_enabled, ) ) conversion_goal_operations: List[MutateOperation] = ( create_conversion_goal_operations( client, customer_id, customer_conversion_goals, ) ) # Send the operations in a single Mutate request. response: MutateGoogleAdsResponse = googleads_service.mutate( customer_id=customer_id, mutate_operations=[ # It's important to create these entities in this order because # they depend on each other. campaign_budget_operation, performance_max_campaign_operation, # Expand the list of multiple operations into the list of # other mutate operations. *campaign_criterion_operations, asset_group_operation, listing_group_filter_operation, *asset_and_asset_group_asset_operations, *conversion_goal_operations, ], ) print_response_details(response) def create_campaign_budget_operation( client: GoogleAdsClient, customer_id: str, ) -> MutateOperation: """Creates a MutateOperation that creates a new CampaignBudget. A temporary ID will be assigned to this campaign budget so that it can be referenced by other objects being created in the same Mutate request. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. Returns: a MutateOperation that creates a CampaignBudget. """ mutate_operation: MutateOperation = client.get_type("MutateOperation") campaign_budget_operation = mutate_operation.campaign_budget_operation campaign_budget: CampaignBudget = campaign_budget_operation.create campaign_budget.name = f"Performance Max retail campaign budget #{uuid4()}" # The budget period already defaults to DAILY. campaign_budget.amount_micros = 50000000 campaign_budget.delivery_method = ( client.enums.BudgetDeliveryMethodEnum.STANDARD ) # A Performance Max campaign cannot use a shared campaign budget. campaign_budget.explicitly_shared = False # Set a temporary ID in the budget's resource name so it can be referenced # by the campaign in later steps. campaign_budget_service: CampaignBudgetServiceClient = client.get_service( "CampaignBudgetService" ) campaign_budget.resource_name = ( campaign_budget_service.campaign_budget_path( customer_id, _BUDGET_TEMPORARY_ID ) ) return mutate_operation def create_performance_max_campaign_operation( client: GoogleAdsClient, customer_id: str, merchant_center_account_id: int, brand_guidelines_enabled: bool, ) -> MutateOperation: """Creates a MutateOperation that creates a new Performance Max campaign. A temporary ID will be assigned to this campaign so that it can be referenced by other objects being created in the same Mutate request. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. merchant_center_account_id: The Merchant Center account ID. brand_guidelines_enabled: a boolean value indicating if the campaign is enabled for brand guidelines. Returns: a MutateOperation that creates a campaign. """ mutate_operation: MutateOperation = client.get_type("MutateOperation") campaign: Campaign = mutate_operation.campaign_operation.create campaign.name = f"Performance Max retail campaign #{uuid4()}" # Set the campaign status as PAUSED. The campaign is the only entity in # the mutate request that should have its status set. campaign.status = client.enums.CampaignStatusEnum.PAUSED # All Performance Max campaigns have an advertising_channel_type of # PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. campaign.advertising_channel_type = ( client.enums.AdvertisingChannelTypeEnum.PERFORMANCE_MAX ) # Bidding strategy must be set directly on the campaign. # Setting a portfolio bidding strategy by resource name is not supported. # Max Conversion and Max Conversion Value are the only strategies supported # for Performance Max campaigns. # An optional ROAS (Return on Advertising Spend) can be set for # maximize_conversion_value. The ROAS value must be specified as a ratio in # the API. It is calculated by dividing "total value" by "total spend". # For more information on Max Conversion Value, see the support article: # http://support.google.com/google-ads/answer/7684216. # A target_roas of 3.5 corresponds to a 350% return on ad spend. # campaign.maximize_conversion_value.target_roas = 3.5 # For first time users, it's recommended not to set a target ROAS. # Although target ROAS is optional, you still need to define it # even if you do not want to use it. campaign.maximize_conversion_value.target_roas = None # Below is what you would use if you want to maximize conversions # campaign.maximize_conversions.target_cpa_micros = None # The target CPA is optional. This is the average amount that you would # like to spend per conversion action. # Set the shopping settings. campaign.shopping_setting.merchant_id = merchant_center_account_id # Optional: To use products only from a specific feed, set # shopping_setting.feed_label to the feed label used in Merchant Center. # See: https://support.google.com/merchants/answer/12453549. # Omitting the shopping_setting.feed_label field will use products from all # feeds. # campaign.shopping_setting.feed_label = "INSERT_FEED_LABEL_HERE" # Set the Final URL expansion opt out. This flag is specific to # Performance Max campaigns. If opted out (True), only the final URLs in # the asset group or URLs specified in the advertiser's Google Merchant # If opted in (False), the entire domain will be targeted. For best # results, set this value to false to opt in and allow URL expansions. You # can optionally add exclusions to limit traffic to parts of your website. # For a Retail campaign, we want the final URL's to be limited to # those explicitly surfaced via GMC. campaign.url_expansion_opt_out = True # Set if the campaign is enabled for brand guidelines. For more information # on brand guidelines, see https://support.google.com/google-ads/answer/14934472. campaign.brand_guidelines_enabled = brand_guidelines_enabled # Assign the resource name with a temporary ID. campaign_service: CampaignServiceClient = client.get_service( "CampaignService" ) campaign.resource_name = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) # Set the budget using the given budget resource name. campaign_budget_service: CampaignBudgetServiceClient = client.get_service( "CampaignBudgetService" ) campaign.campaign_budget = campaign_budget_service.campaign_budget_path( customer_id, _BUDGET_TEMPORARY_ID ) # Declare whether or not this campaign serves political ads targeting the # EU. Valid values are: # CONTAINS_EU_POLITICAL_ADVERTISING # DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING campaign.contains_eu_political_advertising = ( client.enums.EuPoliticalAdvertisingStatusEnum.DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING ) # Optional fields campaign.start_date = (datetime.now() + timedelta(1)).strftime("%Y%m%d") campaign.end_date = (datetime.now() + timedelta(365)).strftime("%Y%m%d") return mutate_operation def create_campaign_criterion_operations( client: GoogleAdsClient, customer_id: str, ) -> List[MutateOperation]: """Creates a list of MutateOperations that create new campaign criteria. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. Returns: a list of MutateOperations that create new campaign criteria. """ campaign_service: CampaignServiceClient = client.get_service( "CampaignService" ) geo_target_constant_service: GeoTargetConstantServiceClient = ( client.get_service("GeoTargetConstantService") ) googleads_service: GoogleAdsServiceClient = client.get_service( "GoogleAdsService" ) operations: List[MutateOperation] = [] # Set the LOCATION campaign criteria. # Target all of New York City except Brooklyn. # Location IDs are listed here: # https://developers.google.com/google-ads/api/reference/data/geotargets # and they can also be retrieved using the GeoTargetConstantService as shown # here: https://developers.google.com/google-ads/api/docs/targeting/location-targeting mutate_operation_nyc: MutateOperation = client.get_type("MutateOperation") campaign_criterion_nyc: CampaignCriterion = ( mutate_operation_nyc.campaign_criterion_operation.create ) campaign_criterion_nyc.campaign = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) # Adds one positive location target for New York City (ID=1023191), # specifically adding the positive criteria before the negative one. campaign_criterion_nyc.location.geo_target_constant = ( geo_target_constant_service.geo_target_constant_path("1023191") ) campaign_criterion_nyc.negative = False operations.append(mutate_operation_nyc) # Next add the negative target for Brooklyn (ID=1022762). mutate_operation_brooklyn: MutateOperation = client.get_type( "MutateOperation" ) campaign_criterion_brooklyn: CampaignCriterion = ( mutate_operation_brooklyn.campaign_criterion_operation.create ) campaign_criterion_brooklyn.campaign = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) campaign_criterion_brooklyn.location.geo_target_constant = ( geo_target_constant_service.geo_target_constant_path("1022762") ) campaign_criterion_brooklyn.negative = True operations.append(mutate_operation_brooklyn) # Set the LANGUAGE campaign criterion. mutate_operation_lang: MutateOperation = client.get_type("MutateOperation") campaign_criterion_lang: CampaignCriterion = ( mutate_operation_lang.campaign_criterion_operation.create ) campaign_criterion_lang.campaign = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) # Set the language. # For a list of all language codes, see: # https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7 campaign_criterion_lang.language.language_constant = ( googleads_service.language_constant_path("1000") ) # English operations.append(mutate_operation_lang) return operations def create_multiple_text_assets( client: GoogleAdsClient, customer_id: str, texts: List[str] ) -> List[str]: """Creates multiple text assets and returns the list of resource names. These repeated assets must be created in a separate request prior to creating the campaign. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. texts: a list of strings, each of which will be used to create a text asset. Returns: asset_resource_names: a list of asset resource names. """ # Here again we use the GoogleAdService to create multiple text # assets in a single request. googleads_service: GoogleAdsServiceClient = client.get_service( "GoogleAdsService" ) operations: List[MutateOperation] = [] for text_content in texts: mutate_operation: MutateOperation = client.get_type("MutateOperation") asset: Asset = mutate_operation.asset_operation.create asset.text_asset.text = text_content operations.append(mutate_operation) # Send the operations in a single Mutate request. response: MutateGoogleAdsResponse = googleads_service.mutate( customer_id=customer_id, mutate_operations=operations, ) asset_resource_names: List[str] = [] for result in response.mutate_operation_responses: if result._pb.HasField("asset_result"): asset_resource_names.append(result.asset_result.resource_name) print_response_details(response) return asset_resource_names def create_asset_group_operation( client: GoogleAdsClient, customer_id: str, final_url: str ) -> MutateOperation: """Creates a MutateOperation that creates a new asset group. A temporary ID will be assigned to this asset group so that it can be referenced by other objects being created in the same Mutate request. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. final_url: the final URL. Returns: a MutateOperation that creates a new asset group. """ campaign_service: CampaignServiceClient = client.get_service( "CampaignService" ) asset_group_service: AssetGroupServiceClient = client.get_service( "AssetGroupService" ) # Create the AssetGroup. mutate_operation: MutateOperation = client.get_type("MutateOperation") asset_group: AssetGroup = mutate_operation.asset_group_operation.create asset_group.name = f"Performance Max retail asset group #{uuid4()}" asset_group.campaign = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) asset_group.final_urls.append(final_url) asset_group.final_mobile_urls.append(final_url) asset_group.status = client.enums.AssetGroupStatusEnum.PAUSED asset_group.resource_name = asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) return mutate_operation def create_listing_group_filter_operation( client: GoogleAdsClient, customer_id: str ) -> MutateOperation: """Creates a MutateOperation that creates a new listing group filter. A temporary ID will be assigned to this listing group filter so that it can be referenced by other objects being created in the same Mutate request. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. Returns: a MutateOperation that creates a new listing group filter. """ asset_group_service: AssetGroupServiceClient = client.get_service( "AssetGroupService" ) # Creates a new ad group criterion containing the "default" listing # group (All products). mutate_operation: MutateOperation = client.get_type("MutateOperation") asset_group_listing_group: AssetGroupListingGroupFilter = ( mutate_operation.asset_group_listing_group_filter_operation.create ) asset_group_listing_group.asset_group = ( asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) ) asset_group_listing_group.type_ = ( client.enums.ListingGroupFilterTypeEnum.UNIT_INCLUDED ) # Because this is a Performance Max campaign for retail, we need to specify # that this is in the shopping listing source. asset_group_listing_group.listing_source = ( client.enums.ListingGroupFilterListingSourceEnum.SHOPPING ) return mutate_operation def create_asset_and_asset_group_asset_operations( client: GoogleAdsClient, customer_id: str, headline_asset_resource_names: List[str], description_asset_resource_names: List[str], brand_guidelines_enabled: bool, ) -> List[MutateOperation]: """Creates a list of MutateOperations that create a new asset_group. A temporary ID will be assigned to this asset group so that it can be referenced by other objects being created in the same Mutate request. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. headline_asset_resource_names: a list of headline resource names. description_asset_resource_names: a list of description resource names. brand_guidelines_enabled: a boolean value indicating if the campaign is enabled for brand guidelines. Returns: MutateOperations that create a new asset group and related assets. """ asset_group_service: AssetGroupServiceClient = client.get_service( "AssetGroupService" ) operations: List[MutateOperation] = [] # For the list of required assets for a Performance Max campaign, see # https://developers.google.com/google-ads/api/docs/performance-max/assets # An AssetGroup is linked to an Asset by creating a new AssetGroupAsset # and providing: # the resource name of the AssetGroup # the resource name of the Asset # the field_type of the Asset in this AssetGroup. # # To learn more about AssetGroups, see # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups # Link the previously created multiple text assets. # Link the headline assets. for resource_name in headline_asset_resource_names: mutate_operation: MutateOperation = client.get_type("MutateOperation") asset_group_asset: AssetGroupAsset = ( mutate_operation.asset_group_asset_operation.create ) asset_group_asset.field_type = client.enums.AssetFieldTypeEnum.HEADLINE asset_group_asset.asset_group = asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) asset_group_asset.asset = resource_name operations.append(mutate_operation) # Link the description assets. for resource_name in description_asset_resource_names: mutate_operation: MutateOperation = client.get_type("MutateOperation") asset_group_asset: AssetGroupAsset = ( mutate_operation.asset_group_asset_operation.create ) asset_group_asset.field_type = ( client.enums.AssetFieldTypeEnum.DESCRIPTION ) asset_group_asset.asset_group = asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) asset_group_asset.asset = resource_name operations.append(mutate_operation) # Create and link the long headline text asset. mutate_operations_long_headline: List[MutateOperation] = ( create_and_link_text_asset( client, customer_id, "Travel the World", client.enums.AssetFieldTypeEnum.LONG_HEADLINE, ) ) operations.extend(mutate_operations_long_headline) # Create and link the business name and logo asset. mutate_operations_brand: List[MutateOperation] = ( create_and_link_brand_assets( client, customer_id, brand_guidelines_enabled, "Interplanetary Cruises", "https://gaagl.page.link/1Crm", "Logo Image", ) ) operations.extend(mutate_operations_brand) # Create and link the image assets. # Create and link the Marketing Image Asset. mutate_operations_marketing_image: List[MutateOperation] = ( create_and_link_image_asset( client, customer_id, "https://gaagl.page.link/Eit5", client.enums.AssetFieldTypeEnum.MARKETING_IMAGE, "Marketing Image", ) ) operations.extend(mutate_operations_marketing_image) # Create and link the Square Marketing Image Asset. mutate_operations_square_image: List[MutateOperation] = ( create_and_link_image_asset( client, customer_id, "https://gaagl.page.link/bjYi", client.enums.AssetFieldTypeEnum.SQUARE_MARKETING_IMAGE, "Square Marketing Image", ) ) operations.extend(mutate_operations_square_image) # After being created the list must be sorted so that all asset # operations come before all the asset group asset operations, # otherwise the API will reject the request. return sort_asset_and_asset_group_asset_operations(operations) def create_and_link_text_asset( client: GoogleAdsClient, customer_id: str, text: str, field_type: AssetFieldTypeEnum.AssetFieldType, ) -> List[MutateOperation]: """Creates a list of MutateOperations that create a new linked text asset. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. text: the text of the asset to be created. field_type: the field_type of the new asset in the AssetGroupAsset. Returns: MutateOperations that create a new linked text asset. """ global _next_temp_id operations: List[MutateOperation] = [] asset_service: AssetServiceClient = client.get_service("AssetService") asset_group_service: AssetGroupServiceClient = client.get_service( "AssetGroupService" ) # Create the Text Asset. asset_temp_resource_name = asset_service.asset_path( customer_id, str(_next_temp_id) ) mutate_operation_asset: MutateOperation = client.get_type("MutateOperation") asset: Asset = mutate_operation_asset.asset_operation.create asset.resource_name = asset_temp_resource_name asset.text_asset.text = text operations.append(mutate_operation_asset) # Create an AssetGroupAsset to link the Asset to the AssetGroup. mutate_operation_group_asset: MutateOperation = client.get_type( "MutateOperation" ) asset_group_asset: AssetGroupAsset = ( mutate_operation_group_asset.asset_group_asset_operation.create ) asset_group_asset.field_type = field_type asset_group_asset.asset_group = asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) asset_group_asset.asset = asset_temp_resource_name operations.append(mutate_operation_group_asset) _next_temp_id -= 1 return operations def create_and_link_image_asset( client: GoogleAdsClient, customer_id: str, url: str, field_type: AssetFieldTypeEnum.AssetFieldType, asset_name: str, ) -> List[MutateOperation]: """Creates a list of MutateOperations that create a new linked image asset. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. url: the url of the image to be retrieved and put into an asset. field_type: the field_type of the new asset in the AssetGroupAsset. asset_name: the asset name. Returns: MutateOperations that create a new linked image asset. """ global _next_temp_id operations: List[MutateOperation] = [] asset_service: AssetServiceClient = client.get_service("AssetService") asset_group_service: AssetGroupServiceClient = client.get_service( "AssetGroupService" ) # Create the Image Asset. asset_temp_resource_name = asset_service.asset_path( customer_id, str(_next_temp_id) ) mutate_operation_asset: MutateOperation = client.get_type("MutateOperation") asset: Asset = mutate_operation_asset.asset_operation.create asset.resource_name = asset_temp_resource_name asset.type_ = client.enums.AssetTypeEnum.IMAGE # Provide a unique friendly name to identify your asset. # When there is an existing image asset with the same content but a different # name, the new name will be dropped silently. asset.name = asset_name asset.image_asset.data = get_image_bytes_from_url(url) operations.append(mutate_operation_asset) # Create an AssetGroupAsset to link the Asset to the AssetGroup. mutate_operation_group_asset: MutateOperation = client.get_type( "MutateOperation" ) asset_group_asset: AssetGroupAsset = ( mutate_operation_group_asset.asset_group_asset_operation.create ) asset_group_asset.field_type = field_type asset_group_asset.asset_group = asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) asset_group_asset.asset = asset_temp_resource_name operations.append(mutate_operation_group_asset) _next_temp_id -= 1 return operations def sort_asset_and_asset_group_asset_operations( operations: List[MutateOperation], ) -> List[MutateOperation]: """Sorts a list of asset and asset group asset operations. This sorts the list such that all asset operations precede all asset group asset operations. If asset group assets are created before assets then an error will be returned by the API. Args: operations: a list of asset and asset group asset operations. Returns: a sorted list of asset and asset group asset operations. """ def sorter(operation: MutateOperation) -> bool: """Determines whether the operation creates an asset group asset. Args: operation: a MutateOperation instance. Returns: True if the MutateOperation creates an asset group asset. """ # Check if the oneof field 'asset_group_asset_operation' is set. return ( operation.asset_group_asset_operation != type(operation.asset_group_asset_operation)() ) return sorted(operations, key=sorter) def get_customer_conversion_goals( client: GoogleAdsClient, customer_id: str ) -> List[ Dict[ str, Union[ ConversionActionCategoryEnum.ConversionActionCategory, ConversionOriginEnum.ConversionOrigin, ], ] ]: """Retrieves the list of customer conversion goals. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. Returns: a list of dicts containing the category and origin of customer conversion goals. """ ga_service: GoogleAdsServiceClient = client.get_service("GoogleAdsService") customer_conversion_goals: List[ Dict[ str, Union[ ConversionActionCategoryEnum.ConversionActionCategory, ConversionOriginEnum.ConversionOrigin, ], ] ] = [] query: str = """ SELECT customer_conversion_goal.category, customer_conversion_goal.origin FROM customer_conversion_goal """ # The number of conversion goals is typically less than 50 so we use # GoogleAdsService.search instead of search_stream. search_request: SearchGoogleAdsRequest = client.get_type( "SearchGoogleAdsRequest" ) search_request.customer_id = customer_id search_request.query = query results: SearchGoogleAdsResponse = ga_service.search(request=search_request) # Iterate over the results and build the list of conversion goals. for row in results: customer_conversion_goals.append( { "category": row.customer_conversion_goal.category, "origin": row.customer_conversion_goal.origin, } ) return customer_conversion_goals def create_conversion_goal_operations( client: GoogleAdsClient, customer_id: str, customer_conversion_goals: List[ Dict[ str, Union[ ConversionActionCategoryEnum.ConversionActionCategory, ConversionOriginEnum.ConversionOrigin, ], ] ], ) -> List[MutateOperation]: """Creates a list of MutateOperations that override customer conversion goals. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. customer_conversion_goals: the list of customer conversion goals that will be overridden. Returns: MutateOperations that update campaign conversion goals. """ campaign_conversion_goal_service: CampaignConversionGoalServiceClient = ( client.get_service("CampaignConversionGoalService") ) operations: List[MutateOperation] = [] # To override the customer conversion goals, we will change the # biddability of each of the customer conversion goals so that only # the desired conversion goal is biddable in this campaign. for customer_goal_dict in customer_conversion_goals: mutate_operation: MutateOperation = client.get_type("MutateOperation") campaign_conversion_goal: CampaignConversionGoal = ( mutate_operation.campaign_conversion_goal_operation.update ) category_enum_value: ( ConversionActionCategoryEnum.ConversionActionCategory ) = customer_goal_dict["category"] origin_enum_value: ConversionOriginEnum.ConversionOrigin = ( customer_goal_dict["origin"] ) campaign_conversion_goal.resource_name = ( campaign_conversion_goal_service.campaign_conversion_goal_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, category_enum_value.name, origin_enum_value.name, ) ) # Change the biddability for the campaign conversion goal. # Set biddability to True for the desired (category, origin). # Set biddability to False for all other conversion goals. # Note: # 1- It is assumed that this Conversion Action # (category=PURCHASE, origin=WEBSITE) exists in this account. # 2- More than one goal can be biddable if desired. This example # shows only one. if ( category_enum_value == client.enums.ConversionActionCategoryEnum.PURCHASE and origin_enum_value == client.enums.ConversionOriginEnum.WEBSITE ): biddable = True else: biddable = False campaign_conversion_goal.biddable = biddable field_mask = protobuf_helpers.field_mask( None, campaign_conversion_goal._pb ) client.copy_from( mutate_operation.campaign_conversion_goal_operation.update_mask, field_mask, ) operations.append(mutate_operation) return operations def create_and_link_brand_assets( client: GoogleAdsClient, customer_id: str, brand_guidelines_enabled: bool, business_name: str, logo_url: str, logo_name: str, ) -> List[MutateOperation]: """Creates a list of MutateOperations that create linked brand assets. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. brand_guidelines_enabled: a boolean value indicating if the campaign is enabled for brand guidelines. business_name: the business name text to be put into an asset. logo_url: the url of the logo to be retrieved and put into an asset. logo_name: the asset name of the logo. Returns: MutateOperations that create linked brand assets. """ global _next_temp_id operations: List[MutateOperation] = [] asset_service: AssetServiceClient = client.get_service("AssetService") # Create the Text Asset. text_asset_temp_id: int = _next_temp_id _next_temp_id -= 1 text_asset_resource_name = asset_service.asset_path( customer_id, str(text_asset_temp_id) ) text_mutate_operation: MutateOperation = client.get_type("MutateOperation") text_asset_obj: Asset = text_mutate_operation.asset_operation.create text_asset_obj.resource_name = text_asset_resource_name text_asset_obj.text_asset.text = business_name operations.append(text_mutate_operation) # Create the Image Asset. image_asset_temp_id: int = _next_temp_id _next_temp_id -= 1 image_asset_resource_name = asset_service.asset_path( customer_id, str(image_asset_temp_id) ) image_mutate_operation: MutateOperation = client.get_type("MutateOperation") image_asset_obj: Asset = image_mutate_operation.asset_operation.create image_asset_obj.resource_name = image_asset_resource_name # Provide a unique friendly name to identify your asset. # When there is an existing image asset with the same content but a different # name, the new name will be dropped silently. image_asset_obj.name = logo_name image_asset_obj.type_ = client.enums.AssetTypeEnum.IMAGE image_asset_obj.image_asset.data = get_image_bytes_from_url(logo_url) operations.append(image_mutate_operation) if brand_guidelines_enabled: # Create CampaignAsset resources to link the Asset resources to the Campaign. campaign_service: CampaignServiceClient = client.get_service( "CampaignService" ) business_name_ca_mutate_op: MutateOperation = client.get_type( "MutateOperation" ) business_name_campaign_asset: CampaignAsset = ( business_name_ca_mutate_op.campaign_asset_operation.create ) business_name_campaign_asset.field_type = ( client.enums.AssetFieldTypeEnum.BUSINESS_NAME ) business_name_campaign_asset.campaign = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) business_name_campaign_asset.asset = text_asset_resource_name operations.append(business_name_ca_mutate_op) logo_ca_mutate_op: MutateOperation = client.get_type("MutateOperation") logo_campaign_asset: CampaignAsset = ( logo_ca_mutate_op.campaign_asset_operation.create ) logo_campaign_asset.field_type = client.enums.AssetFieldTypeEnum.LOGO logo_campaign_asset.campaign = campaign_service.campaign_path( customer_id, _PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ) logo_campaign_asset.asset = image_asset_resource_name operations.append(logo_ca_mutate_op) else: # Create AssetGroupAsset resources to link the Asset resources to the AssetGroup. asset_group_service: AssetGroupServiceClient = client.get_service( "AssetGroupService" ) business_name_aga_mutate_op: MutateOperation = client.get_type( "MutateOperation" ) business_name_asset_group_asset: AssetGroupAsset = ( business_name_aga_mutate_op.asset_group_asset_operation.create ) business_name_asset_group_asset.field_type = ( client.enums.AssetFieldTypeEnum.BUSINESS_NAME ) business_name_asset_group_asset.asset_group = ( asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) ) business_name_asset_group_asset.asset = text_asset_resource_name operations.append(business_name_aga_mutate_op) logo_aga_mutate_op: MutateOperation = client.get_type("MutateOperation") logo_asset_group_asset: AssetGroupAsset = ( logo_aga_mutate_op.asset_group_asset_operation.create ) logo_asset_group_asset.field_type = client.enums.AssetFieldTypeEnum.LOGO logo_asset_group_asset.asset_group = ( asset_group_service.asset_group_path( customer_id, _ASSET_GROUP_TEMPORARY_ID, ) ) logo_asset_group_asset.asset = image_asset_resource_name operations.append(logo_aga_mutate_op) return operations def print_response_details(response: MutateGoogleAdsResponse) -> None: """Prints the details of a MutateGoogleAdsResponse. Parses the "response" oneof field name and uses it to extract the new entity's name and resource name. Args: response: a MutateGoogleAdsResponse object. """ # Parse the Mutate response to print details about the entities that # were created by the request. suffix: str = "_result" for result_item in response.mutate_operation_responses: # Ensure result_item is MutateOperationResponse, not just Any result_pb: MutateOperationResponse = result_item for field_descriptor, value in result_pb._pb.ListFields(): field_name_str: str = field_descriptor.name if field_name_str.endswith(suffix): name = field_name_str[: -len(suffix)] else: name = field_name_str print( f"Created a(n) {convert_snake_case_to_upper_case(name)} with " f"{str(value).strip()}." ) if __name__ == "__main__": parser = argparse.ArgumentParser( description=("Creates a Performance Max retail campaign.") ) # 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( "-m", "--merchant_center_account_id", type=int, required=True, help="The Merchant Center account ID.", ) parser.add_argument( "-u", "--final_url", type=str, required=False, default="http://www.example.com", help="The final URL for the asset group of the campaign.", ) parser.add_argument( "-b", "--brand_guidelines_enabled", type=bool, default=False, help=( "A boolean value indicating if the created campaign is enabled " "for brand guidelines." ), ) args: argparse.Namespace = parser.parse_args() # GoogleAdsClient will read the google-ads.yaml configuration file in the # home directory if none is specified. googleads_client: GoogleAdsClient = GoogleAdsClient.load_from_storage( version="v21" ) try: main( googleads_client, args.customer_id, args.merchant_center_account_id, args.final_url, args.brand_guidelines_enabled, ) except GoogleAdsException as ex: print( f'Request with ID "{ex.request_id}" failed with status ' f'"{ex.error.code().name}" and includes the following errors:' ) for error in ex.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)
Ruby
#!/usr/bin/env ruby # Encoding: utf-8 # # Copyright 2021 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. # # This example shows how to create a Performance Max retail campaign. # # This will be created for "All products". # # For more information about Performance Max retail campaigns, see # https://developers.google.com/google-ads/api/docs/performance-max/retail # # Prerequisites: # - You need to have access to a Merchant Center account. You can find # instructions to create a Merchant Center account here: # https://support.google.com/merchants/answer/188924. # This account must be linked to your Google Ads account. The integration # instructions can be found at: # https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center # - You need your Google Ads account to track conversions. The different ways # to track conversions can be found here: # https://support.google.com/google-ads/answer/1722054. # - You must have at least one conversion action in the account. For # more about conversion actions, see # https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions require 'optparse' require 'date' require 'open-uri' require 'google/ads/google_ads' # We specify temporary IDs that are specific to a single mutate request. # Temporary IDs are always negative and unique within one mutate request. # # See https://developers.google.com/google-ads/api/docs/mutating/best-practices # for further details. # # These temporary IDs are fixed because they are used in multiple places. BUDGET_TEMPORARY_ID = "-1" PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID = "-2" ASSET_GROUP_TEMPORARY_ID = "-3" # There are also entities that will be created in the same request but do not # need to be fixed temporary IDs because they are referenced only once. def next_temp_id @id ||= ASSET_GROUP_TEMPORARY_ID.to_i @id -= 1 end def add_performance_max_retail_campaign( customer_id, merchant_center_account_id, final_url, brand_guidelines_enabled) # GoogleAdsClient will read a config file from # ENV['HOME']/google_ads_config.rb when called without parameters client = Google::Ads::GoogleAds::GoogleAdsClient.new # This campaign will override the customer conversion goals. # Retrieve the current list of customer conversion goals. customer_conversion_goals = _get_customer_conversion_goals( client, customer_id) # Performance Max campaigns require that repeated assets such as headlines # and descriptions be created before the campaign. # For the list of required assets for a Performance Max campaign, see # https://developers.google.com/google-ads/api/docs/performance-max/assets # # Create the headlines. headline_asset_resource_names = create_multiple_text_assets( client, customer_id, [ "Travel", "Travel Reviews", "Book travel", ]) # Create the descriptions. description_asset_resource_names = create_multiple_text_assets( client, customer_id, [ "Take to the air!", "Fly to the sky!", ]) # The below methods create and return MutateOperations that we later # provide to the GoogleAdsService.Mutate method in order to create the # entities in a single request. Since the entities for a Performance Max # campaign are closely tied to one-another, it's considered a best practice # to create them in a single Mutate request so they all complete # successfully or fail entirely, leaving no orphaned entities. See: # https://developers.google.com/google-ads/api/docs/mutating/overview campaign_budget_operation = create_campaign_budget_operation( client, customer_id, ) performance_max_campaign_operation = create_performance_max_campaign_operation( client, customer_id, merchant_center_account_id, brand_guidelines_enabled, ) campaign_criterion_operations = create_campaign_criterion_operations( client, customer_id, ) asset_group_operation = create_asset_group_operation( client, customer_id, final_url, ) listing_group_filter_operation = create_listing_group_filter_operation( client, customer_id, ) asset_and_asset_group_asset_operations = create_asset_and_asset_group_asset_operations( client, customer_id, headline_asset_resource_names, description_asset_resource_names, brand_guidelines_enabled, ) conversion_goal_operations = create_conversion_goal_operations( client, customer_id, customer_conversion_goals, ) # Send the operations in a single Mutate request. response = client.service.google_ads.mutate( customer_id: customer_id, mutate_operations: [ # It's important to create these entities in this order because # they depend on each other. campaign_budget_operation, performance_max_campaign_operation, # Expand the list of multiple operations into the list of # other mutate operations campaign_criterion_operations, asset_group_operation, listing_group_filter_operation, asset_and_asset_group_asset_operations, conversion_goal_operations, ].flatten) print_response_details(response) end # Creates a MutateOperation that creates a new CampaignBudget. # # A temporary ID will be assigned to this campaign budget so that it can be # referenced by other objects being created in the same Mutate request. def create_campaign_budget_operation(client, customer_id) client.operation.mutate do |m| m.campaign_budget_operation = client.operation.create_resource.campaign_budget do |cb| cb.name = "Performance Max campaign budget #{SecureRandom.uuid}" # The budget period already defaults to DAILY. cb.amount_micros = 50_000_000 cb.delivery_method = :STANDARD # A Performance Max campaign cannot use a shared campaign budget. cb.explicitly_shared = false # Set a temporary ID in the budget's resource name so it can be referenced # by the campaign in later steps. cb.resource_name = client.path.campaign_budget(customer_id, BUDGET_TEMPORARY_ID) end end end # Creates a MutateOperation that creates a new Performance Max campaign. # # A temporary ID will be assigned to this campaign so that it can # be referenced by other objects being created in the same Mutate request. def create_performance_max_campaign_operation( client, customer_id, merchant_center_account_id, brand_guidelines_enabled) client.operation.mutate do |m| m.campaign_operation = client.operation.create_resource.campaign do |c| c.name = "Performance Max retail campaign #{SecureRandom.uuid}" # Set the campaign status as PAUSED. The campaign is the only entity in # the mutate request that should have its status set. c.status = :PAUSED # All Performance Max campaigns have an advertising_channel_type of # PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. c.advertising_channel_type = :PERFORMANCE_MAX # Bidding strategy must be set directly on the campaign. # Setting a portfolio bidding strategy by resource name is not supported. # Max Conversion and Maximize Conversion Value are the only strategies # supported for Performance Max campaigns. # An optional ROAS (Return on Advertising Spend) can be set for # maximize_conversion_value. The ROAS value must be specified as a ratio in # the API. It is calculated by dividing "total value" by "total spend". # For more information on Maximize Conversion Value, see the support # article: http://support.google.com/google-ads/answer/7684216. # A target_roas of 3.5 corresponds to a 350% return on ad spend. c.bidding_strategy_type = :MAXIMIZE_CONVERSION_VALUE c.maximize_conversion_value = client.resource.maximize_conversion_value do |mcv| mcv.target_roas = 3.5 end # Set the shopping settings. c.shopping_setting = client.resource.shopping_setting do |ss| ss.merchant_id = merchant_center_account_id # Optional: To use products only from a specific feed, set feed_label # to the feed label used in Merchant Center. # See: https://support.google.com/merchants/answer/12453549. # Omitting the feed_label field will use products from all feeds. # feed_label = "INSERT_FEED_LABEL_HERE" end # Set the Final URL expansion opt out. This flag is specific to # Performance Max campaigns. If opted out (true), only the final URLs in # the asset group or URLs specified in the advertiser's Google Merchant # Center or business data feeds are targeted. # If opted in (false), the entire domain will be targeted. For best # results, set this value to false to opt in and allow URL expansions. You # can optionally add exclusions to limit traffic to parts of your website. # # For a Retail campaign, we want the final URLs to be limited to those # explicitly surfaced via GMC. c.url_expansion_opt_out = true # Set if the campaign is enabled for brand guidelines. For more # information on brand guidelines, see # https://support.google.com/google-ads/answer/14934472. c.brand_guidelines_enabled = brand_guidelines_enabled # Assign the resource name with a temporary ID. c.resource_name = client.path.campaign(customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID) # Set the budget using the given budget resource name. c.campaign_budget = client.path.campaign_budget(customer_id, BUDGET_TEMPORARY_ID) # Declare whether or not this campaign serves political ads targeting the EU # Valid values are CONTAINS_EU_POLITICAL_ADVERTISING and # DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING c.contains_eu_political_advertising = :DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING # Optional fields c.start_date = DateTime.parse((Date.today + 1).to_s).strftime('%Y%m%d') c.end_date = DateTime.parse(Date.today.next_year.to_s).strftime('%Y%m%d') end end end # Creates a list of MutateOperations that create new campaign criteria. def create_campaign_criterion_operations(client, customer_id) operations = [] # Set the LOCATION campaign criteria. # Target all of New York City except Brooklyn. # Location IDs are listed here: # https://developers.google.com/google-ads/api/reference/data/geotargets # and they can also be retrieved using the GeoTargetConstantService as shown # here: https://developers.google.com/google-ads/api/docs/targeting/location-targeting operations << client.operation.mutate do |m| m.campaign_criterion_operation = client.operation.create_resource.campaign_criterion do |cc| cc.campaign = client.path.campaign( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID) # Adds one positive location target for New York City (ID=1023191), # specifically adding the positive criteria before the negative one. cc.location = client.resource.location_info do |li| li.geo_target_constant = client.path.geo_target_constant("1023191") end cc.negative = false end end # Next add the negative target for Brooklyn (ID=1022762). operations << client.operation.mutate do |m| m.campaign_criterion_operation = client.operation.create_resource.campaign_criterion do |cc| cc.campaign = client.path.campaign( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID) cc.location = client.resource.location_info do |li| li.geo_target_constant = client.path.geo_target_constant("1022762") end cc.negative = true end end # Set the LANGUAGE campaign criterion. operations << client.operation.mutate do |m| m.campaign_criterion_operation = client.operation.create_resource.campaign_criterion do |cc| cc.campaign = client.path.campaign( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID) # Set the language. # For a list of all language codes, see: # https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7 cc.language = client.resource.language_info do |li| li.language_constant = client.path.language_constant("1000") # English end end end operations end # Creates multiple text assets and returns the list of resource names. # These repeated assets must be created in a separate request prior to creating # the campaign. def create_multiple_text_assets(client, customer_id, texts) operations = texts.map do |text| client.operation.mutate do |m| m.asset_operation = client.operation.create_resource.asset do |asset| asset.text_asset = client.resource.text_asset do |text_asset| text_asset.text = text end end end end # Send the operations in a single Mutate request. response = client.service.google_ads.mutate( customer_id: customer_id, mutate_operations: operations, ) asset_resource_names = [] response.mutate_operation_responses.each do |result| if result.asset_result asset_resource_names.append(result.asset_result.resource_name) end end print_response_details(response) asset_resource_names end # Creates a MutateOperation that creates a new asset_group. # # A temporary ID will be assigned to this asset group so that it can # be referenced by other objects being created in the same Mutate request. def create_asset_group_operation( client, customer_id, final_url) # Create the AssetGroup client.operation.mutate do |m| m.asset_group_operation = client.operation.create_resource.asset_group do |ag| ag.name = "Performance Max retail asset group #{SecureRandom.uuid}" ag.campaign = client.path.campaign( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID) ag.final_urls << final_url ag.final_mobile_urls << final_url ag.status = :PAUSED ag.resource_name = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID, ) end end end # Creates a MutateOperation that creates a new listing group filter. # A temporary ID will be assigned to this listing group filter so that it can # be referenced by other objects being created in the same Mutate request. def create_listing_group_filter_operation(client, customer_id) client.operation.mutate do |m| m.asset_group_listing_group_filter_operation = client.operation.create_resource.asset_group_listing_group_filter do |aglg| aglg.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID, ) aglg.type = :UNIT_INCLUDED # Because this is a Performance Max campaign for retail, we need to # specify that this is in the shopping listing source. aglg.listing_source = :SHOPPING end end end # Creates a list of MutateOperations that create a new asset_group. # A temporary ID will be assigned to this asset group so that it can be # referenced by other objects being created in the same Mutate request. def create_asset_and_asset_group_asset_operations( client, customer_id, headline_asset_resource_names, description_asset_resource_names, brand_guidelines_enabled) operations = [] # For the list of required assets for a Performance Max campaign, see # https://developers.google.com/google-ads/api/docs/performance-max/assets # # An AssetGroup is linked to an Asset by creating a new AssetGroupAsset # and providing: # the resource name of the AssetGroup # the resource name of the Asset # the field_type of the Asset in this AssetGroup. # # To learn more about AssetGroups, see # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups # Link the previously created multiple text assets. # Link the headline assets. headline_asset_resource_names.each do |resource_name| operations << client.operation.mutate do |m| m.asset_group_asset_operation = client.operation.create_resource .asset_group_asset do |aga| aga.field_type = :HEADLINE aga.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID) aga.asset = resource_name end end end # Link the description assets. description_asset_resource_names.each do |resource_name| operations << client.operation.mutate do |m| m.asset_group_asset_operation = client.operation.create_resource .asset_group_asset do |aga| aga.field_type = :DESCRIPTION aga.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID) aga.asset = resource_name end end end # Create and link the long headline text asset. operations += create_and_link_text_asset( client, customer_id, "Travel the World", :LONG_HEADLINE) # Create and link the business name and logo asset. operations += create_and_link_brand_assets( client, customer_id, brand_guidelines_enabled, "Interplanetary Cruises", "https://gaagl.page.link/1Crm", "Logo Image") # Create and link the image assets. # Create and link the Marketing Image Asset. operations += create_and_link_image_asset( client, customer_id, "https://gaagl.page.link/Eit5", :MARKETING_IMAGE, "Marketing Image") # Create and link the Square Marketing Image Asset. operations += create_and_link_image_asset( client, customer_id, "https://gaagl.page.link/bjYi", :SQUARE_MARKETING_IMAGE, "Square Marketing Image") # After being created the list must be sorted so that all asset # operations come before all the asset group asset operations, # otherwise the API will reject the request. sort_asset_and_asset_group_asset_operations(operations) end # Creates a list of MutateOperations that create a new linked text asset. def create_and_link_text_asset(client, customer_id, text, field_type) operations = [] temp_id = next_temp_id # Create the Text Asset. operations << client.operation.mutate do |m| m.asset_operation = client.operation.create_resource.asset do |a| a.resource_name = client.path.asset(customer_id, temp_id) a.text_asset = client.resource.text_asset do |text_asset| text_asset.text = text end end end # Create an AssetGroupAsset to link the Asset to the AssetGroup. operations << client.operation.mutate do |m| m.asset_group_asset_operation = client.operation.create_resource .asset_group_asset do |aga| aga.field_type = field_type aga.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID) aga.asset = client.path.asset(customer_id, temp_id) end end operations end # Creates a list of MutateOperations that create a new linked image asset. def create_and_link_image_asset(client, customer_id, url, field_type, asset_name) operations = [] temp_id = next_temp_id # Create the Image Asset. operations << client.operation.mutate do |m| m.asset_operation = client.operation.create_resource.asset do |a| a.resource_name = client.path.asset(customer_id, temp_id) a.type = :IMAGE # Provide a unique friendly name to identify your asset. # When there is an existing image asset with the same content but a different # name, the new name will be dropped silently. a.name = asset_name a.image_asset = client.resource.image_asset do |image_asset| image_asset.data = get_image_bytes(url) end end end # Create an AssetGroupAsset to link the Asset to the AssetGroup. operations << client.operation.mutate do |m| m.asset_group_asset_operation = client.operation.create_resource .asset_group_asset do |aga| aga.field_type = field_type aga.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID) aga.asset = client.path.asset(customer_id, temp_id) end end operations end # Sorts a list of asset and asset group asset operations. This sorts the list # such that all asset operations precede all asset group asset operations. If # asset group assets are created before assets then an error will be returned # by the API. def sort_asset_and_asset_group_asset_operations(operations) operations.sort_by do |operation| if operation.asset_group_asset_operation 1 else 0 end end end def _get_customer_conversion_goals(client, customer_id) query = <<~EOD SELECT customer_conversion_goal.category, customer_conversion_goal.origin FROM customer_conversion_goal EOD customer_conversion_goals = [] ga_service = client.service.google_ads # The number of conversion goals is typically less than 50 so we use # GoogleAdsService.search instead of search_stream. response = ga_service.search( customer_id: customer_id, query: query, ) # Iterate over the results and build the list of conversion goals. response.each do |row| customer_conversion_goals << { "category" => row.customer_conversion_goal.category, "origin" => row.customer_conversion_goal.origin } end customer_conversion_goals end def create_conversion_goal_operations(client, customer_id, customer_conversion_goals) campaign_conversion_goal_service = client.service.campaign_conversion_goal operations = [] # To override the customer conversion goals, we will change the # biddability of each of the customer conversion goals so that only # the desired conversion goal is biddable in this campaign. customer_conversion_goals.each do |customer_conversion_goal| operations << client.operation.mutate do |m| m.campaign_conversion_goal_operation = client.operation.campaign_conversion_goal do |op| op.update = client.resource.campaign_conversion_goal do |ccg| ccg.resource_name = client.path.campaign_conversion_goal( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, customer_conversion_goal["category"].to_s, customer_conversion_goal["origin"].to_s) # Change the biddability for the campaign conversion goal. # Set biddability to True for the desired (category, origin). # Set biddability to False for all other conversion goals. # Note: # 1- It is assumed that this Conversion Action # (category=PURCHASE, origin=WEBSITE) exists in this account. # 2- More than one goal can be biddable if desired. This example # shows only one. ccg.biddable = (customer_conversion_goal["category"] == :PURCHASE && customer_conversion_goal["origin"] == :WEBSITE) end op.update_mask = Google::Ads::GoogleAds::FieldMaskUtil.all_set_fields_of(op.update) end end end operations end # Creates a list of MutateOperations that create linked brand assets. def create_and_link_brand_assets( client, customer_id, brand_guidelines_enabled, business_name, logo_url, logo_name) operations = [] # Create the Text Asset. text_asset_temp_id = next_temp_id operations << client.operation.mutate do |m| m.asset_operation = client.operation.create_resource.asset do |a| a.resource_name = client.path.asset(customer_id, text_asset_temp_id) a.text_asset = client.resource.text_asset do |text_asset| text_asset.text = business_name end end end # Create the Image Asset. image_asset_temp_id = next_temp_id operations << client.operation.mutate do |m| m.asset_operation = client.operation.create_resource.asset do |a| a.resource_name = client.path.asset(customer_id, image_asset_temp_id) # Provide a unique friendly name to identify your asset. # When there is an existing image asset with the same content but a different # name, the new name will be dropped silently. a.name = logo_name a.type = :IMAGE a.image_asset = client.resource.image_asset do |image_asset| image_asset.data = get_image_bytes(logo_url) end end end if brand_guidelines_enabled # Create CampaignAsset resources to link the Asset resources to the Campaign. operations << client.operation.mutate do |m| m.campaign_asset_operation = client.operation.create_resource. campaign_asset do |ca| ca.field_type = :BUSINESS_NAME ca.campaign = client.path.campaign( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, ) ca.asset = client.path.asset(customer_id, text_asset_temp_id) end end operations << client.operation.mutate do |m| m.campaign_asset_operation = client.operation.create_resource. campaign_asset do |ca| ca.field_type = :LOGO ca.campaign = client.path.campaign( customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, ) ca.asset = client.path.asset(customer_id, image_asset_temp_id) end end else # Create AssetGroupAsset resources to link the Asset resources to the AssetGroup. operations << client.operation.mutate do |m| m.asset_group_asset_operation = client.operation.create_resource. asset_group_asset do |aga| aga.field_type = :BUSINESS_NAME aga.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID, ) aga.asset = client.path.asset(customer_id, text_asset_temp_id) end end operations << client.operation.mutate do |m| m.asset_group_asset_operation = client.operation.create_resource. asset_group_asset do |aga| aga.field_type = :LOGO aga.asset_group = client.path.asset_group( customer_id, ASSET_GROUP_TEMPORARY_ID, ) aga.asset = client.path.asset(customer_id, image_asset_temp_id) end end end operations end # Loads image data from a URL. def get_image_bytes(url) URI.open(url).read end # Prints the details of a MutateGoogleAdsResponse. def print_response_details(response) # Parse the mutate response to print details about the entities that # were created by the request. suffix = "_result" response.mutate_operation_responses.each do |result| result.to_h.select {|k, v| v }.each do |name, value| if name.to_s.end_with?(suffix) name = name.to_s.delete_suffix(suffix) end puts "Created a(n) #{::Google::Ads::GoogleAds::Utils.camelize(name)} " \ "with #{value.to_s.strip}." end end end if __FILE__ == $0 options = {} # 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. options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' options[:merchant_center_account_id] = 'INSERT_MERCHANT_CENTER_ACCOUNT_ID_HERE' options[:final_url] = 'INSERT_FINAL_URL_HERE' options[:brand_guidelines_enabled] = false OptionParser.new do |opts| opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) opts.separator '' opts.separator 'Options:' opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| options[:customer_id] = v end opts.on('-m', '--merchant-center-account-id MERCHANT-CENTER-ACCOUNT-ID', Integer, 'Merchant Center Account ID') do |v| options[:merchant_center_account_id] = v end opts.on('-f', '--final-url FINAL-URL', String, 'Final URL') do |v| options[:final_url] = v end opts.on('-B', '--brand-guidelines-enabled', 'Enable brand guidelines (optional)') do options[:brand_guidelines_enabled] = true end opts.separator '' opts.separator 'Help:' opts.on_tail('-h', '--help', 'Show this message') do puts opts exit end end.parse! begin add_performance_max_retail_campaign( options.fetch(:customer_id).tr("-", ""), options.fetch(:merchant_center_account_id), options.fetch(:final_url), options[:brand_guidelines_enabled]) rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e e.failure.errors.each do |error| STDERR.printf("Error with message: %s\n", error.message) if error.location error.location.field_path_elements.each do |field_path_element| STDERR.printf("\tOn field: %s\n", field_path_element.field_name) end end error.error_code.to_h.each do |k, v| next if v == :UNSPECIFIED STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) end end raise end end
Perl
#!/usr/bin/perl -w # # Copyright 2021, 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. # # This example shows how to create a Performance Max retail campaign. # # This will be created for "All products". # # For more information about Performance Max retail campaigns, see # https://developers.google.com/google-ads/api/docs/performance-max/retail. # # Prerequisites: # - You need to have access to a Merchant Center account. You can find # instructions to create a Merchant Center account here: # https://support.google.com/merchants/answer/188924. # This account must be linked to your Google Ads account. The integration # instructions can be found at: # https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center. # - You need your Google Ads account to track conversions. The different ways # to track conversions can be found here: # https://support.google.com/google-ads/answer/1722054. # - You must have at least one conversion action in the account. For more about # conversion actions, see # https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 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::Utils::MediaUtils; use Google::Ads::GoogleAds::Utils::FieldMasks; use Google::Ads::GoogleAds::V21::Resources::CampaignBudget; use Google::Ads::GoogleAds::V21::Resources::Campaign; use Google::Ads::GoogleAds::V21::Resources::ShoppingSetting; use Google::Ads::GoogleAds::V21::Resources::CampaignCriterion; use Google::Ads::GoogleAds::V21::Resources::CampaignAsset; use Google::Ads::GoogleAds::V21::Resources::Asset; use Google::Ads::GoogleAds::V21::Resources::AssetGroup; use Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset; use Google::Ads::GoogleAds::V21::Resources::CampaignConversionGoal; use Google::Ads::GoogleAds::V21::Resources::AssetGroupListingGroupFilter; use Google::Ads::GoogleAds::V21::Common::MaximizeConversionValue; use Google::Ads::GoogleAds::V21::Common::LocationInfo; use Google::Ads::GoogleAds::V21::Common::LanguageInfo; use Google::Ads::GoogleAds::V21::Common::TextAsset; use Google::Ads::GoogleAds::V21::Common::ImageAsset; use Google::Ads::GoogleAds::V21::Enums::BudgetDeliveryMethodEnum qw(STANDARD); use Google::Ads::GoogleAds::V21::Enums::CampaignStatusEnum; use Google::Ads::GoogleAds::V21::Enums::AdvertisingChannelTypeEnum qw(PERFORMANCE_MAX); use Google::Ads::GoogleAds::V21::Enums::AssetGroupStatusEnum; use Google::Ads::GoogleAds::V21::Enums::AssetFieldTypeEnum qw(HEADLINE DESCRIPTION LONG_HEADLINE BUSINESS_NAME LOGO MARKETING_IMAGE SQUARE_MARKETING_IMAGE); use Google::Ads::GoogleAds::V21::Enums::ConversionActionCategoryEnum qw(PURCHASE); use Google::Ads::GoogleAds::V21::Enums::ConversionOriginEnum qw(WEBSITE); use Google::Ads::GoogleAds::V21::Enums::ListingGroupFilterTypeEnum qw(UNIT_INCLUDED); use Google::Ads::GoogleAds::V21::Enums::ListingGroupFilterListingSourceEnum qw(SHOPPING); use Google::Ads::GoogleAds::V21::Enums::EuPoliticalAdvertisingStatusEnum qw(DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING); use Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation; use Google::Ads::GoogleAds::V21::Services::CampaignBudgetService::CampaignBudgetOperation; use Google::Ads::GoogleAds::V21::Services::CampaignService::CampaignOperation; use Google::Ads::GoogleAds::V21::Services::CampaignCriterionService::CampaignCriterionOperation; use Google::Ads::GoogleAds::V21::Services::AssetService::AssetOperation; use Google::Ads::GoogleAds::V21::Services::AssetGroupService::AssetGroupOperation; use Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation; use Google::Ads::GoogleAds::V21::Services::CampaignConversionGoalService::CampaignConversionGoalOperation; use Google::Ads::GoogleAds::V21::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation; use Google::Ads::GoogleAds::V21::Services::CampaignAssetService::CampaignAssetOperation; use Google::Ads::GoogleAds::V21::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); # We specify temporary IDs that are specific to a single mutate request. # Temporary IDs are always negative and unique within one mutate request. # # See https://developers.google.com/google-ads/api/docs/mutating/best-practices # for further details. # # These temporary IDs are fixed because they are used in multiple places. use constant BUDGET_TEMPORARY_ID => -1; use constant PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID => -2; use constant ASSET_GROUP_TEMPORARY_ID => -3; # There are also entities that will be created in the same request but do not # need to be fixed temporary IDs because they are referenced only once. our $next_temp_id = ASSET_GROUP_TEMPORARY_ID - 1; sub add_performance_max_retail_campaign { my ($api_client, $customer_id, $merchant_center_account_id, $final_url, $brand_guidelines_enabled) = @_; # This campaign will override the customer conversion goals. # Retrieve the current list of customer conversion goals. my $customer_conversion_goals = get_customer_conversion_goals($api_client, $customer_id); # Performance Max campaigns require that repeated assets such as headlines # and descriptions be created before the campaign. # For the list of required assets for a Performance Max campaign, see # https://developers.google.com/google-ads/api/docs/performance-max/assets. # # Create the headlines. my $headline_asset_resource_names = create_multiple_text_assets($api_client, $customer_id, ["Travel", "Travel Reviews", "Book travel"]); # Create the descriptions. my $description_asset_resource_names = create_multiple_text_assets($api_client, $customer_id, ["Take to the air!", "Fly to the sky!"]); # It's important to create the below entities in this order because they depend # on each other. my $operations = []; # The below methods create and return MutateOperations that we later provide to # the GoogleAdsService->mutate() method in order to create the entities in a # single request. Since the entities for a Performance Max campaign are closely # tied to one-another, it's considered a best practice to create them in a # single mutate request so they all complete successfully or fail entirely, # leaving no orphaned entities. See: # https://developers.google.com/google-ads/api/docs/mutating/overview. push @$operations, create_campaign_budget_operation($customer_id); push @$operations, create_performance_max_campaign_operation($customer_id, $merchant_center_account_id, $brand_guidelines_enabled); push @$operations, @{create_campaign_criterion_operations($customer_id)}; push @$operations, create_asset_group_operation($customer_id, $final_url); push @$operations, create_listing_group_filter_operation($customer_id); push @$operations, @{ create_asset_and_asset_group_asset_operations( $customer_id, $headline_asset_resource_names, $description_asset_resource_names, $brand_guidelines_enabled )}; push @$operations, @{create_conversion_goal_operations($customer_id, $customer_conversion_goals)}; # Issue a mutate request to create everything and print its information. my $mutate_google_ads_response = $api_client->GoogleAdsService()->mutate({ customerId => $customer_id, mutateOperations => $operations }); print_response_details($mutate_google_ads_response); return 1; } # Creates a MutateOperation that creates a new CampaignBudget. # # A temporary ID will be assigned to this campaign budget so that it can be # referenced by other objects being created in the same mutate request. sub create_campaign_budget_operation { my ($customer_id) = @_; # Create a mutate operation that creates a campaign budget operation. return Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ campaignBudgetOperation => Google::Ads::GoogleAds::V21::Services::CampaignBudgetService::CampaignBudgetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::CampaignBudget->new( { # Set a temporary ID in the budget's resource name so it can be # referenced by the campaign in later steps. resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign_budget( $customer_id, BUDGET_TEMPORARY_ID ), name => "Performance Max retail campaign budget #" . uniqid(), # The budget period already defaults to DAILY. amountMicros => 50000000, deliveryMethod => STANDARD, # A Performance Max campaign cannot use a shared campaign budget. explicitlyShared => "false", })})}); } # Creates a MutateOperation that creates a new Performance Max campaign. # # A temporary ID will be assigned to this campaign so that it can be referenced # by other objects being created in the same mutate request. sub create_performance_max_campaign_operation { my ($customer_id, $merchant_center_account_id, $brand_guidelines_enabled) = @_; # Create a mutate operation that creates a campaign operation. return Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ campaignOperation => Google::Ads::GoogleAds::V21::Services::CampaignService::CampaignOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::Campaign->new({ # Assign the resource name with a temporary ID. resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), name => "Performance Max retail campaign #'" . uniqid(), # Set the budget using the given budget resource name. campaignBudget => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign_budget( $customer_id, BUDGET_TEMPORARY_ID ), # Set the campaign status as PAUSED. The campaign is the only entity in # the mutate request that should have its status set. status => Google::Ads::GoogleAds::V21::Enums::CampaignStatusEnum::PAUSED, # All Performance Max campaigns have an advertisingChannelType of # PERFORMANCE_MAX. The advertisingChannelSubType should not be set. advertisingChannelType => PERFORMANCE_MAX, # Bidding strategy must be set directly on the campaign. # Setting a portfolio bidding strategy by resource name is not supported. # Max Conversion and Max Conversion Value are the only strategies # supported for Performance Max campaigns. # An optional ROAS (Return on Advertising Spend) can be set for # maximizeConversionValue. The ROAS value must be specified as a ratio in # the API. It is calculated by dividing "total value" by "total spend". # For more information on Max Conversion Value, see the support article: # http://support.google.com/google-ads/answer/7684216. # A targetRoas of 3.5 corresponds to a 350% return on ad spend. # For first time users, it's recommended not to set a target ROAS. # maximizeConversionValue => # Google::Ads::GoogleAds::V21::Common::MaximizeConversionValue-> # new({ # targetRoas => 3.5 # } # ), # Below is what you would use if you want to maximize conversions. # maximizeConversions => # Google::Ads::GoogleAds::V21::Common::MaximizeConversions-> # new({ # targetCpaMicros => 1000000 # } # ), # The target CPA is optional. This is the average amount that you would # like to spend per conversion action. # Set the shopping settings. shoppingSetting => Google::Ads::GoogleAds::V21::Resources::ShoppingSetting->new({ merchantId => $merchant_center_account_id, # Optional: To use products only from a specific feed, set feedLabel # to the feed label used in Merchant Center. # See: https://support.google.com/merchants/answer/12453549. # Omitting the feedLabel field will use products from all feeds. # feedLabel => "INSERT_FEED_LABEL_HERE" } ), # Set the final URL expansion opt out. This flag is specific to # Performance Max campaigns. If opted out (true), only the final URLs in # the asset group or URLs specified in the advertiser's Google Merchant # Center or business data feeds are targeted. # If opted in (false), the entire domain will be targeted. For best # results, set this value to false to opt in and allow URL expansions. You # can optionally add exclusions to limit traffic to parts of your website. # # For a Retail campaign, we want the final URL to be limited to those # explicitly surfaced via GMC. urlExpansionOptOut => "true", # Declare whether or not this campaign serves political ads targeting the EU. # Valid values are CONTAINS_EU_POLITICAL_ADVERTISING and # DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING. containsEuPoliticalAdvertising => DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING, # Set if the campaign is enabled for brand guidelines. For more information # on brand guidelines, see https://support.google.com/google-ads/answer/14934472. brandGuidelinesEnabled => $brand_guidelines_enabled, # Optional fields. startDate => strftime("%Y%m%d", localtime(time + 60 * 60 * 24)), endDate => strftime("%Y%m%d", localtime(time + 60 * 60 * 24 * 365)), })})}); } # Creates a list of MutateOperations that create new campaign criteria. sub create_campaign_criterion_operations { my ($customer_id) = @_; my $operations = []; # Set the LOCATION campaign criteria. # Target all of New York City except Brooklyn. # Location IDs are listed here: # https://developers.google.com/google-ads/api/reference/data/geotargets # and they can also be retrieved using the GeoTargetConstantService as shown # here: https://developers.google.com/google-ads/api/docs/targeting/location-targeting. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ campaignCriterionOperation => Google::Ads::GoogleAds::V21::Services::CampaignCriterionService::CampaignCriterionOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::CampaignCriterion->new({ campaign => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), # Adds one positive location target for New York City (ID=1023191), # specifically adding the positive criteria before the negative one. location => Google::Ads::GoogleAds::V21::Common::LocationInfo->new({ geoTargetConstant => Google::Ads::GoogleAds::V21::Utils::ResourceNames::geo_target_constant( 1023191)} ), negative => "false" })})}); # Next add the negative target for Brooklyn (ID=1022762). push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ campaignCriterionOperation => Google::Ads::GoogleAds::V21::Services::CampaignCriterionService::CampaignCriterionOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::CampaignCriterion->new({ campaign => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), location => Google::Ads::GoogleAds::V21::Common::LocationInfo->new({ geoTargetConstant => Google::Ads::GoogleAds::V21::Utils::ResourceNames::geo_target_constant( 1022762)} ), negative => "true" })})}); # Set the LANGUAGE campaign criterion. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ campaignCriterionOperation => Google::Ads::GoogleAds::V21::Services::CampaignCriterionService::CampaignCriterionOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::CampaignCriterion->new({ campaign => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), # Set the language. # For a list of all language codes, see: # https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7. language => Google::Ads::GoogleAds::V21::Common::LanguageInfo->new({ languageConstant => Google::Ads::GoogleAds::V21::Utils::ResourceNames::language_constant( 1000) # English })})})}); return $operations; } # Creates multiple text assets and returns the list of resource names. # # These repeated assets must be created in a separate request prior to # creating the campaign. sub create_multiple_text_assets { my ($api_client, $customer_id, $texts) = @_; # Here again we use the GoogleAdService to create multiple text assets in a # single request. my $operations = []; foreach my $text (@$texts) { # Create a mutate operation for a text asset. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ assetOperation => Google::Ads::GoogleAds::V21::Services::AssetService::AssetOperation-> new({ create => Google::Ads::GoogleAds::V21::Resources::Asset->new({ textAsset => Google::Ads::GoogleAds::V21::Common::TextAsset->new({ text => $text })})})}); } # Issue a mutate request to add all assets. my $mutate_google_ads_response = $api_client->GoogleAdsService()->mutate({ customerId => $customer_id, mutateOperations => $operations }); my $asset_resource_names = []; foreach my $response (@{$mutate_google_ads_response->{mutateOperationResponses}}) { push @$asset_resource_names, $response->{assetResult}{resourceName}; } print_response_details($mutate_google_ads_response); return $asset_resource_names; } # Creates a MutateOperation that creates a new asset group. # # A temporary ID will be assigned to this asset group so that it can be referenced # by other objects being created in the same mutate request. sub create_asset_group_operation { my ($customer_id, $final_url) = @_; # Create a mutate operation that creates an asset group operation. return Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetGroupOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupService::AssetGroupOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroup->new({ resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), name => "Performance Max retail asset group #" . uniqid(), campaign => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), finalUrls => [$final_url], finalMobileUrls => [$final_url], status => Google::Ads::GoogleAds::V21::Enums::AssetGroupStatusEnum::PAUSED })})}); } # Creates a MutateOperation that creates a new listing group filter. # A temporary ID will be assigned to this listing group filter so that it # can be referenced by other objects being created in the same Mutate request. sub create_listing_group_filter_operation { my ($customer_id) = @_; # Creates a new ad group criterion containing the "default" listing group # (All products). my $listing_group_filter = Google::Ads::GoogleAds::V21::Resources::AssetGroupListingGroupFilter->new({ assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), # Since this is the root node, do not set the parentListingGroupFilter. # For all other nodes, this would refer to the parent listing group filter # resource name. # parentListingGroupFilter => "<PARENT FILTER NAME>" # The subdivision type means this node has children. This type is used for # the root node as well. type => UNIT_INCLUDED, # Because this is a Performance Max campaign for retail, we need to specify # that this is in the shopping listing source. listingSource => SHOPPING }); return Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetGroupListingGroupFilterOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation ->new({ create => $listing_group_filter })}); } # Creates a list of MutateOperations that create a new asset_group. # A temporary ID will be assigned to this asset group so that it can # be referenced by other objects being created in the same Mutate request. sub create_asset_and_asset_group_asset_operations { my ( $customer_id, $headline_asset_resource_names, $description_asset_resource_names, $brand_guidelines_enabled ) = @_; my $operations = []; # For the list of required assets for a Performance Max campaign, see # https://developers.google.com/google-ads/api/docs/performance-max/assets. # An AssetGroup is linked to an Asset by creating a new AssetGroupAsset # and providing: # - the resource name of the AssetGroup # - the resource name of the Asset # - the fieldType of the Asset in this AssetGroup # # To learn more about AssetGroups, see # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. # Link the previously created multiple text assets. # Link the headline assets. foreach my $resource_name (@$headline_asset_resource_names) { push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ assetGroupAssetOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset->new({ asset => $resource_name, assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), fieldType => HEADLINE })})}); } # Link the description assets. foreach my $resource_name (@$description_asset_resource_names) { push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ assetGroupAssetOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset->new({ asset => $resource_name, assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), fieldType => DESCRIPTION })})}); } # Create and link the long headline text asset. push @$operations, @{create_and_link_text_asset($customer_id, "Travel the World", LONG_HEADLINE)}; # Create and link the business name and logo asset. push @$operations, @{ create_and_link_brand_assets( $customer_id, $brand_guidelines_enabled, "Interplanetary Cruises", "https://gaagl.page.link/1Crm", "Logo Image" )}; # Create and link the image assets. # Create and link the marketing image asset. push @$operations, @{ create_and_link_image_asset( $customer_id, "https://gaagl.page.link/Eit5", MARKETING_IMAGE, "Marketing Image" )}; # Create and link the square marketing image asset. push @$operations, @{ create_and_link_image_asset( $customer_id, "https://gaagl.page.link/bjYi", SQUARE_MARKETING_IMAGE, "Square Marketing Image" )}; # After being created the list must be sorted so that all asset # operations come before all the asset group asset operations, # otherwise the API will reject the request. return sort_asset_and_asset_group_asset_operations($operations); } # Creates a list of MutateOperations that create a new linked text asset. sub create_and_link_text_asset { my ($customer_id, $text, $field_type) = @_; my $operations = []; # Create a new mutate operation for a text asset. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetOperation => Google::Ads::GoogleAds::V21::Services::AssetService::AssetOperation-> new({ create => Google::Ads::GoogleAds::V21::Resources::Asset->new({ resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $next_temp_id ), textAsset => Google::Ads::GoogleAds::V21::Common::TextAsset->new({ text => $text })})})}); # Create an asset group asset to link the asset to the asset group. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetGroupAssetOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset->new({ asset => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $next_temp_id ), assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), fieldType => $field_type })})}); $next_temp_id--; return $operations; } # Creates a list of MutateOperations that create a new linked image asset. sub create_and_link_image_asset { my ($customer_id, $url, $field_type, $asset_name) = @_; my $operations = []; # Create a new mutate operation for an image asset. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetOperation => Google::Ads::GoogleAds::V21::Services::AssetService::AssetOperation-> new({ create => Google::Ads::GoogleAds::V21::Resources::Asset->new({ resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $next_temp_id ), # Provide a unique friendly name to identify your asset. # When there is an existing image asset with the same content but a different # name, the new name will be dropped silently. name => $asset_name, imageAsset => Google::Ads::GoogleAds::V21::Common::ImageAsset->new({ data => get_base64_data_from_url($url)})})})}); # Create an asset group asset to link the asset to the asset group. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetGroupAssetOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset->new({ asset => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $next_temp_id ), assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), fieldType => $field_type })})}); $next_temp_id--; return $operations; } # Creates a list of MutateOperations that create linked brand assets. sub create_and_link_brand_assets { my ($customer_id, $brand_guidelines_enabled, $business_name, $logo_url, $logo_name) = @_; my $operations = []; # Create the text asset. my $text_asset_temp_id = $next_temp_id--; push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetOperation => Google::Ads::GoogleAds::V21::Services::AssetService::AssetOperation-> new({ create => Google::Ads::GoogleAds::V21::Resources::Asset->new({ resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $text_asset_temp_id ), textAsset => Google::Ads::GoogleAds::V21::Common::TextAsset->new({ text => $business_name })})})}); # Create the image asset. my $image_asset_temp_id = $next_temp_id--; push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation-> new({ assetOperation => Google::Ads::GoogleAds::V21::Services::AssetService::AssetOperation-> new({ create => Google::Ads::GoogleAds::V21::Resources::Asset->new({ resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $image_asset_temp_id ), # Provide a unique friendly name to identify your asset. # When there is an existing image asset with the same content but a different # name, the new name will be dropped silently. name => $logo_name, imageAsset => Google::Ads::GoogleAds::V21::Common::ImageAsset->new({ data => get_base64_data_from_url($logo_url)})})})}); if ($brand_guidelines_enabled) { # Create CampaignAsset resources to link the Asset resources to the Campaign. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ campaignAssetOperation => Google::Ads::GoogleAds::V21::Services::CampaignAssetService::CampaignAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::CampaignAsset->new({ fieldType => BUSINESS_NAME, campaign => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), asset => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $text_asset_temp_id )})})}); push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ campaignAssetOperation => Google::Ads::GoogleAds::V21::Services::CampaignAssetService::CampaignAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::CampaignAsset->new({ fieldType => LOGO, campaign => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID ), asset => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $image_asset_temp_id )})})}); } else { # Create AssetGroupAsset resources to link the Asset resources to the AssetGroup. push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ assetGroupAssetOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset->new({ asset => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $text_asset_temp_id ), assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), fieldType => BUSINESS_NAME })})}); push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ assetGroupAssetOperation => Google::Ads::GoogleAds::V21::Services::AssetGroupAssetService::AssetGroupAssetOperation ->new({ create => Google::Ads::GoogleAds::V21::Resources::AssetGroupAsset->new({ asset => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset( $customer_id, $image_asset_temp_id ), assetGroup => Google::Ads::GoogleAds::V21::Utils::ResourceNames::asset_group( $customer_id, ASSET_GROUP_TEMPORARY_ID ), fieldType => LOGO })})}); } return $operations; } # Sorts a list of asset and asset group asset operations. # # This sorts the list such that all asset operations precede # all asset group asset operations. If asset group assets are # created before assets then an error will be returned by the API. sub sort_asset_and_asset_group_asset_operations { my ($operations) = @_; sub sorter { if (defined $a->{assetOperation}) { return -1; } else { return 1; } } my @operations_sorted = sort sorter @$operations; return \@operations_sorted; } # Retrieves the list of customer conversion goals. sub get_customer_conversion_goals { my ($api_client, $customer_id) = @_; my $customer_conversion_goals = []; # Create a query that retrieves all customer conversion goals. my $query = "SELECT customer_conversion_goal.category, customer_conversion_goal.origin " . "FROM customer_conversion_goal"; # The number of conversion goals is typically less than 50 so we use # GoogleAdsService->search() method instead of search_stream(). my $search_response = $api_client->GoogleAdsService()->search({ customerId => $customer_id, query => $query }); # Iterate over the results and build the list of conversion goals. foreach my $google_ads_row (@{$search_response->{results}}) { push @$customer_conversion_goals, { category => $google_ads_row->{customerConversionGoal}{category}, origin => $google_ads_row->{customerConversionGoal}{origin}}; } return $customer_conversion_goals; } # Creates a list of MutateOperations that override customer conversion goals. sub create_conversion_goal_operations { my ($customer_id, $customer_conversion_goals) = @_; my $operations = []; # To override the customer conversion goals, we will change the biddability of # each of the customer conversion goals so that only the desired conversion goal # is biddable in this campaign. foreach my $customer_conversion_goal (@$customer_conversion_goals) { my $campaign_conversion_goal = Google::Ads::GoogleAds::V21::Resources::CampaignConversionGoal->new({ resourceName => Google::Ads::GoogleAds::V21::Utils::ResourceNames::campaign_conversion_goal( $customer_id, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID, $customer_conversion_goal->{category}, $customer_conversion_goal->{origin})}); # Change the biddability for the campaign conversion goal. # Set biddability to true for the desired (category, origin). # Set biddability to false for all other conversion goals. # Note: # 1- It is assumed that this Conversion Action # (category=PURCHASE, origin=WEBSITE) exists in this account. # 2- More than one goal can be biddable if desired. This example # shows only one. if ( $customer_conversion_goal->{category} eq PURCHASE && $customer_conversion_goal->{origin} eq WEBSITE) { $campaign_conversion_goal->{biddable} = "true"; } else { $campaign_conversion_goal->{biddable} = "false"; } push @$operations, Google::Ads::GoogleAds::V21::Services::GoogleAdsService::MutateOperation ->new({ campaignConversionGoalOperation => Google::Ads::GoogleAds::V21::Services::CampaignConversionGoalService::CampaignConversionGoalOperation ->new({ update => $campaign_conversion_goal, # Set the update mask on the operation. Here the update mask will be # a list of all the fields that were set on the update object. updateMask => all_set_fields_of($campaign_conversion_goal)})}); } return $operations; } # Prints the details of a MutateGoogleAdsResponse. # Parses the "response" oneof field name and uses it to extract the new entity's # name and resource name. sub print_response_details { my ($mutate_google_ads_response) = @_; foreach my $response (@{$mutate_google_ads_response->{mutateOperationResponses}}) { my $result_type = [keys %$response]->[0]; printf "Created a(n) %s with '%s'.\n", ucfirst $result_type =~ s/Result$//r, $response->{$result_type}{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); my $customer_id = undef; my $merchant_center_account_id = undef; my $sales_country = "US"; my $final_url = "http://www.example.com"; my $brand_guidelines_enabled = "false"; # Parameters passed on the command line will override any parameters set in code. GetOptions( "customer_id=s" => \$customer_id, "merchant_center_account_id=i" => \$merchant_center_account_id, "sales_country=s" => \$sales_country, "final_url=s" => \$final_url, "brand_guidelines_enabled=s" => \$brand_guidelines_enabled ); # 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, $merchant_center_account_id, $sales_country, $final_url); # Call the example. add_performance_max_retail_campaign($api_client, $customer_id =~ s/-//gr, $merchant_center_account_id, $sales_country, $final_url, $brand_guidelines_enabled); =pod =head1 NAME add_performance_max_retail_campaign =head1 DESCRIPTION This example shows how to create a Performance Max retail campaign. This will be created for "All products". For more information about Performance Max retail campaigns, see https://developers.google.com/google-ads/api/docs/performance-max/retail. Prerequisites: - You need to have access to a Merchant Center account. You can find instructions to create a Merchant Center account here: https://support.google.com/merchants/answer/188924. This account must be linked to your Google Ads account. The integration instructions can be found at: https://developers.google.com/google-ads/api/docs/shopping-ads/merchant-center. - You need your Google Ads account to track conversions. The different ways to track conversions can be found here: https://support.google.com/google-ads/answer/1722054. - You must have at least one conversion action in the account. For more about conversion actions, see https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. =head1 SYNOPSIS add_performance_max_retail_campaign.pl [options] -help Show the help message. -customer_id The Google Ads customer ID. -merchant_center_account_id The Merchant Center account ID. -sales_country [optional] The sales country of products to include in the campaign. -final_url [optional] The final URL for the asset group of the campaign. -brand_guidelines_enabled [optional] A boolean value indicating if the campaign is enabled for brand guidelines. Defaults to false. =cut