**Important:** This feature is available to allowlisted accounts only. Contact your Google representative if you need access to the feature.

<br />

Google offers a variety of products and audiences you can use to build
campaigns, though you might not know how to formulate a plan. To help with this,
[`ReachPlanService`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService.md.txt) provides discovery methods you can use
to [generate a curve](https://developers.google.com/google-ads/api/docs/reach-forecasting/generate-curve.md.txt).

## Audience targeting

The first component of a media plan is audience targeting, which can include the
following target criteria:

- **Audiences:** Target affinity, in-market, and user list audiences using `targeting.audience_targeting` ([`AudienceTargeting`](https://developers.google.com/google-ads/api/reference/rpc/v26/AudienceTargeting.md.txt)). Query supported criteria using [`ReachPlanService.ListPlannableUserInterests`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableUserInterests.md.txt) and [`ReachPlanService.ListPlannableUserLists`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableUserLists.md.txt).
- **Age ranges:** Specify target age groups using the [`ReachPlanAgeRange`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanAgeRangeEnum.ReachPlanAgeRange.md.txt) enum.
- **Genders:** Target specific genders using the [`GenderInfo`](https://developers.google.com/google-ads/api/reference/rpc/v26/GenderInfo.md.txt) criterion.
- **Devices:** Target specific devices using the [`DeviceInfo`](https://developers.google.com/google-ads/api/reference/rpc/v26/DeviceInfo.md.txt) criterion.
- **Locations:** Find supported location IDs using [`ListPlannableLocations`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableLocations.md.txt).
- **Networks:** View supported networks for a location and product, which may vary by product and region, using [`ListPlannableProducts`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableProducts.md.txt).
- **Parental status:** Target specific parental statuses using the [`ParentalStatusInfo`](https://developers.google.com/google-ads/api/reference/rpc/v26/ParentalStatusInfo.md.txt) criterion (available in `v25` and later).
- **YouTube Select lineups:** Target specific lineups using [`YouTubeSelectSettings`](https://developers.google.com/google-ads/api/reference/rpc/v26/YouTubeSelectSettings.md.txt) within your reach plans to reach engaged audiences on curated content across YouTube.

## Product mix

In addition to the targeting information, you must specify a product mix in
order to generate a curve.

The [`ListPlannableProducts`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableProducts.md.txt) method on
[`ReachPlanService`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService.md.txt) shows the latest available formats for
a given location ID. These are represented by the following fields in
[`ProductMetadata`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductMetadata.md.txt):

- [`plannable_product_code`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductMetadata.md.txt#plannable_product_code)
- [`plannable_product_name`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductMetadata.md.txt#plannable_product_name)
- [`plannable_product_description`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductMetadata.md.txt#plannable_product_description) (available in `v24` and later)
- `plannable_product_core_attributes` (available in `v24` and later): A collection of attributes ([`ProductCoreAttributes`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductCoreAttributes.md.txt)) used to describe a product's capabilities, including:
  - [`marketing_objective`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductCoreAttributes.md.txt#marketing_objective)
  - [`cost_model`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductCoreAttributes.md.txt#cost_model)
  - [`buying_method`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductCoreAttributes.md.txt#buying_method)
- [`plannable_targeting`](https://developers.google.com/google-ads/api/reference/rpc/v26/ProductMetadata.md.txt#plannable_targeting): Allowed targeting settings for the product, such as age ranges, genders, devices, networks, and (in `v25` and later) parental statuses.

Keep in mind that these values change periodically, so it is best to provide
your users with live results rather than persisting responses offline.

Here is a code example of how to get the product mix for some target criteria:


### Java

```java
private void showPlannableProducts(
    ReachPlanServiceClient reachPlanServiceClient, String locationId) {
  ListPlannableProductsRequest request =
      ListPlannableProductsRequest.newBuilder().setPlannableLocationId(locationId).build();

  ListPlannableProductsResponse response = reachPlanServiceClient.listPlannableProducts(request);

  System.out.printf("Plannable Products for location %s:%n", locationId);
  for (ProductMetadata product : response.getProductMetadataList()) {
    System.out.printf("%s:%n", product.getPlannableProductCode());
    System.out.println("Age Ranges:");
    for (ReachPlanAgeRange ageRange : product.getPlannableTargeting().getAgeRangesList()) {
      System.out.printf("\t- %s%n", ageRange);
    }
    System.out.println("Genders:");
    for (GenderInfo gender : product.getPlannableTargeting().getGendersList()) {
      System.out.printf("\t- %s%n", gender.getType());
    }
    System.out.println("Devices:");
    for (DeviceInfo device : product.getPlannableTargeting().getDevicesList()) {
      System.out.printf("\t- %s%n", device.getType());
    }
  }
}
      
```

### C#

```c#
public void ShowPlannableProducts(
    ReachPlanServiceClient reachPlanService, string locationId)
{
    ListPlannableProductsRequest request = new ListPlannableProductsRequest
    {
        PlannableLocationId = locationId
    };
    ListPlannableProductsResponse response = reachPlanService.ListPlannableProducts(
        request);

    Console.WriteLine($"Plannable Products for location {locationId}:");
    foreach (ProductMetadata product in response.ProductMetadata)
    {
        Console.WriteLine($"{product.PlannableProductCode}:");
        Console.WriteLine("Age Ranges:");
        foreach (ReachPlanAgeRange ageRange in product.PlannableTargeting.AgeRanges)
        {
            Console.WriteLine($"\t- {ageRange}");
        }

        Console.WriteLine("Genders:");
        foreach (GenderInfo gender in product.PlannableTargeting.Genders)
        {
            Console.WriteLine($"\t- {gender.Type}");
        }

        Console.WriteLine("Devices:");
        foreach (DeviceInfo device in product.PlannableTargeting.Devices)
        {
            Console.WriteLine($"\t- {device.Type}");
        }
    }
}
      
```

### PHP

```php
private static function showPlannableProducts(GoogleAdsClient $googleAdsClient)
{
    $response = $googleAdsClient->getReachPlanServiceClient()->listPlannableProducts(
        ListPlannableProductsRequest::build(self::LOCATION_ID)
    );

    print 'Plannable Products for Location ID ' . self::LOCATION_ID . ':' . PHP_EOL;
    foreach ($response->getProductMetadata() as $product) {
        /** @var ProductMetadata $product */
        print $product->getPlannableProductCode() . ':' . PHP_EOL;
        print 'Age Ranges:' . PHP_EOL;
        foreach ($product->getPlannableTargeting()->getAgeRanges() as $ageRange) {
            /** @var ReachPlanAgeRange $ageRange */
            printf("\t- %s%s", ReachPlanAgeRange::name($ageRange), PHP_EOL);
        }
        print 'Genders:' . PHP_EOL;
        foreach ($product->getPlannableTargeting()->getGenders() as $gender) {
            /** @var GenderInfo $gender */
            printf("\t- %s%s", GenderType::name($gender->getType()), PHP_EOL);
        }
        print 'Devices:' . PHP_EOL;
        foreach ($product->getPlannableTargeting()->getDevices() as $device) {
            /** @var DeviceInfo $device */
            printf("\t- %s%s", Device::name($device->getType()), PHP_EOL);
        }
    }
}
      
```

### Python

```python
def show_plannable_products(client: GoogleAdsClient, location_id: str):
    """Lists plannable products for a given location.

    Args:
        client: an initialized GoogleAdsClient instance.
        location_id: The location ID to plan for.
    """
    reach_plan_service: ReachPlanServiceClient = client.get_service(
        "ReachPlanService"
    )
    response: ListPlannableProductsResponse = (
        reach_plan_service.list_plannable_products(
            plannable_location_id=location_id
        )
    )
    print(f"Plannable Products for Location ID {location_id}")

    product_metadata: ProductMetadata
    for product_metadata in response.product_metadata:
        print(
            f"{product_metadata.plannable_product_code} : "
            f"{product_metadata.plannable_product_name}"
        )

        print("Age Ranges:")
        age_range: ReachPlanAgeRangeEnum
        for age_range in product_metadata.plannable_targeting.age_ranges:
            print(f"\t- {age_range.name}")

        print("Genders:")
        gender: GenderInfo
        for gender in product_metadata.plannable_targeting.genders:
            print(f"\t- {gender.type_.name}")

        print("Devices:")
        device: DeviceInfo
        for device in product_metadata.plannable_targeting.devices:
            print(f"\t- {device.type_.name}")
      
```

### Ruby

```ruby
def show_plannable_products(reach_plan_service)
  response = reach_plan_service.list_plannable_products(
    plannable_location_id: LOCATION_ID,
  )

  puts "Plannable Products for Location ID #{LOCATION_ID}:"

  response.product_metadata.each do |product|
    puts "#{product.plannable_product_code}:"
    puts "Age Ranges:"
    product.plannable_targeting.age_ranges.each do |age_range|
      puts "\t- #{age_range}"
    end
    puts "Genders:"
    product.plannable_targeting.genders.each do |gender|
      puts "\t- #{gender.type}"
    end
    puts "Devices:"
    product.plannable_targeting.devices.each do |device|
      puts "\t- #{device.type}"
    end
  end
end
      
```

### Perl

```perl
sub show_plannable_products {
  my ($reach_plan_service, $location_id) = @_;

  my $response = $reach_plan_service->list_plannable_products({
    plannableLocationId => $location_id
  });

  printf "Plannable Products for location %d:\n", $location_id;
  foreach my $product (@{$response->{productMetadata}}) {
    printf "%s : '%s'\n", $product->{plannableProductCode},
      $product->{plannableProductName};
    print "Age Ranges:\n";
    foreach my $age_range (@{$product->{plannableTargeting}{ageRanges}}) {
      printf "\t- %s\n", $age_range;
    }
    print "Genders:\n";
    foreach my $gender (@{$product->{plannableTargeting}{genders}}) {
      printf "\t- %s\n", $gender->{type};
    }
    print "Devices:\n";
    foreach my $device (@{$product->{plannableTargeting}{devices}}) {
      printf "\t- %s\n", $device->{type};
    }
  }
}
      
```

### curl

> [!NOTE]
> **Note:** While a direct REST code sample for this step isn't provided here, you can achieve this using a manual REST request.   
>
> Refer to the Google Ads API REST interface documentation and the method-specific reference pages. You will need to construct the JSON payload based on the proto definitions.   
>
> Key Resources:
>
> - [Using REST](https://developers.google.com/google-ads/api/rest/overview.md.txt)
> - [REST Interface Structure](https://developers.google.com/google-ads/api/rest/design/overview.md.txt)
> - [JSON Field Mappings](https://developers.google.com/google-ads/api/rest/design/json-mappings.md.txt)
> - Consult the [REST API Reference](https://developers.google.com/google-ads/api/reference/rpc/latest/overview) for the specific service and method.

<br />

To generate a curve you would then assign a budget to each product:


### Java

```java
private void forecastManualMix(
    ReachPlanServiceClient reachPlanServiceClient,
    long customerId,
    String locationId,
    String currencyCode,
    long budgetMicros) {
  List<PlannedProduct> productMix = new ArrayList<>();

  // Set up a ratio to split the budget between two products.
  double trueviewAllocation = 0.15;
  double bumperAllocation = 1 - trueviewAllocation;

  // See listPlannableProducts on ReachPlanService to retrieve a list
  // of valid PlannableProductCode's for a given location:
  // https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
  productMix.add(
      PlannedProduct.newBuilder()
          .setPlannableProductCode("TRUEVIEW_IN_STREAM")
          .setBudgetMicros((long) (budgetMicros * bumperAllocation))
          .build());
  productMix.add(
      PlannedProduct.newBuilder()
          .setPlannableProductCode("BUMPER")
          .setBudgetMicros((long) (budgetMicros * bumperAllocation))
          .build());

  GenerateReachForecastRequest request =
      buildReachRequest(customerId, productMix, locationId, currencyCode);

  getReachCurve(reachPlanServiceClient, request);
}
      
```

### C#

```c#
public void ForecastMix(ReachPlanServiceClient reachPlanService, string customerId,
    string locationId, string currencyCode, long budgetMicros)
{
    List<PlannedProduct> productMix = new List<PlannedProduct>();

    // Set up a ratio to split the budget between two products.
    double trueviewAllocation = 0.15;
    double bumperAllocation = 1 - trueviewAllocation;

    // See listPlannableProducts on ReachPlanService to retrieve a list
    // of valid PlannableProductCode's for a given location:
    // https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
    productMix.Add(new PlannedProduct
    {
        PlannableProductCode = "TRUEVIEW_IN_STREAM",
        BudgetMicros = Convert.ToInt64(budgetMicros * trueviewAllocation)
    });
    productMix.Add(new PlannedProduct
    {
        PlannableProductCode = "BUMPER",
        BudgetMicros = Convert.ToInt64(budgetMicros * bumperAllocation)
    });

    GenerateReachForecastRequest request =
        BuildReachRequest(customerId, productMix, locationId, currencyCode);

    GetReachCurve(reachPlanService, request);
}
      
```

### PHP

```php
private static function forecastManualMix(GoogleAdsClient $googleAdsClient, int $customerId)
{
    // Set up a ratio to split the budget between two products.
    $trueviewAllocation = floatval(0.15);
    $bumperAllocation = floatval(1 - $trueviewAllocation);

    // See listPlannableProducts on ReachPlanService to retrieve a list
    // of valid PlannableProductCode's for a given location:
    // https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
    $productMix = [
        new PlannedProduct([
            'plannable_product_code' => 'TRUEVIEW_IN_STREAM',
            'budget_micros' => self::BUDGET_MICROS * $trueviewAllocation
        ]),
        new PlannedProduct([
            'plannable_product_code' => 'BUMPER',
            'budget_micros' => self::BUDGET_MICROS * $bumperAllocation
        ])
    ];

    self::getReachCurve(
        $googleAdsClient,
        $customerId,
        $productMix,
        self::LOCATION_ID,
        self::CURRENCY_CODE
    );
}
      
```

### Python

```python
def forecast_manual_mix(
    client: GoogleAdsClient,
    customer_id: str,
    location_id: str,
    currency_code: str,
    budget: int,
):
    """Pulls a forecast for product mix created manually.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: The customer ID for the reach forecast.
        location_id: The location ID to plan for.
        currency_code: Three-character ISO 4217 currency code.
        budget: Budget to allocate to the plan.
    """
    product_mix: list[PlannedProduct] = []
    trueview_allocation = 0.15
    bumper_allocation = 1 - trueview_allocation
    product_splits = [
        ("TRUEVIEW_IN_STREAM", trueview_allocation),
        ("BUMPER", bumper_allocation),
    ]
    product: str
    split: float
    for product, split in product_splits:
        planned_product: PlannedProduct = client.get_type("PlannedProduct")
        planned_product.plannable_product_code = product
        planned_product.budget_micros = math.trunc(budget * ONE_MILLION * split)
        product_mix.append(planned_product)

    request_reach_curve(
        client, customer_id, product_mix, location_id, currency_code
    )
      
```

### Ruby

```ruby
def forecast_manual_mix(client, reach_plan_service, customer_id)
  # Set up a ratio to split the budget between two products.
  trueview_allocation = 0.15
  bumper_allocation = 1 - trueview_allocation

  # See listPlannableProducts on ReachPlanService to retrieve a list
  # of valid PlannableProductCode's for a given location:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
  product_mix = []

  product_mix << client.resource.planned_product do |p|
    p.plannable_product_code = 'TRUEVIEW_IN_STREAM'
    p.budget_micros = BUDGET_MICROS * trueview_allocation
  end

  product_mix << client.resource.planned_product do |p|
    p.plannable_product_code = 'BUMPER'
    p.budget_micros = BUDGET_MICROS * bumper_allocation
  end

  get_reach_curve(
    client,
    reach_plan_service,
    customer_id,
    product_mix,
    LOCATION_ID,
    CURRENCY_CODE,
  )
end
      
```

### Perl

```perl
sub forecast_mix {
  my (
    $reach_plan_service, $customer_id, $location_id,
    $currency_code,      $budget_micros
  ) = @_;

  my $product_mix = [];

  # Set up a ratio to split the budget between two products.
  my $trueview_allocation = 0.15;
  my $bumper_allocation   = 1 - $trueview_allocation;

  # See list_plannable_products on ReachPlanService to retrieve a list of valid
  # plannable product codes for a given location:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
  push @$product_mix,
    Google::Ads::GoogleAds::V26::Services::ReachPlanService::PlannedProduct->
    new({
      plannableProductCode => "TRUEVIEW_IN_STREAM",
      budgetMicros         => int($budget_micros * $trueview_allocation)});
  push @$product_mix,
    Google::Ads::GoogleAds::V26::Services::ReachPlanService::PlannedProduct->
    new({
      plannableProductCode => "BUMPER",
      budgetMicros         => int($budget_micros * $bumper_allocation)});

  my $reach_request =
    build_reach_request($customer_id, $product_mix, $location_id,
    $currency_code);

  pull_reach_curve($reach_plan_service, $reach_request);
}
      
```

### curl

> [!NOTE]
> **Note:** While a direct REST code sample for this step isn't provided here, you can achieve this using a manual REST request.   
>
> Refer to the Google Ads API REST interface documentation and the method-specific reference pages. You will need to construct the JSON payload based on the proto definitions.   
>
> Key Resources:
>
> - [Using REST](https://developers.google.com/google-ads/api/rest/overview.md.txt)
> - [REST Interface Structure](https://developers.google.com/google-ads/api/rest/design/overview.md.txt)
> - [JSON Field Mappings](https://developers.google.com/google-ads/api/rest/design/json-mappings.md.txt)
> - Consult the [REST API Reference](https://developers.google.com/google-ads/api/reference/rpc/latest/overview) for the specific service and method.

<br />

This is also when you can set
[`advanced_product_targeting`](https://developers.google.com/google-ads/api/reference/rpc/v26/PlannedProduct.md.txt#advanced_product_targeting) for YouTube Select
lineups.

## Reach forecast request fields

You must set the `customer_id`, `campaign_duration`, and `planned_products`
fields for each
[`GenerateReachForecastRequest`](https://developers.google.com/google-ads/api/reference/rpc/v26/GenerateReachForecastRequest.md.txt).

You can also set the following conditional or optional fields:

| Field | Requirement | Description |
|---|---|---|
| `cookie_frequency_cap_setting` | Optional | The maximum number of times an ad can be shown to the same user during a specified time interval. This cap can be defined as a limit per one day, one week, or one month. Replaces `cookie_frequency_cap`, which was deprecated in `v24` and removed in `v25`. |
| `currency_code` | Optional | Three-character ISO 4217 currency code. |
| `customer_reach_group` | **Conditionally required** | The name of the customer being planned for. This is a user-defined value. **Required** if `targeting.audience_targeting` is set. |
| `effective_frequency_limit` | Optional | Specifies the highest minimum effective frequency (`1`--`10`) for `Forecast.effective_frequency_breakdowns`, mutually exclusive with `min_effective_frequency`. |
| `forecast_metric_options` | Optional | Additional metric options (`ForecastMetricOptions`) such as `include_coview`. |
| `min_effective_frequency` | Optional | Minimum number of times a person was exposed to the ad for the reported reach metrics. |
| `targeting` | Optional | The targeting to be applied to all products selected in the product mix, including location (`plannable_location_ids`, which replaced `plannable_location_id` in `v25`), age range, genders, devices, network, and (in `v25` and later) parental statuses. |

## What's next

Learn how to [generate a reach curve](https://developers.google.com/google-ads/api/docs/reach-forecasting/generate-curve.md.txt).