Skip to content

Target specific countries and languages

You want a campaign to show only in the United States and the United Kingdom, to English speakers. In the Google Ads UI that’s a few dropdowns. In bidsmith it’s two lines on the campaign — and a reviewer can read exactly what changed in the pull request.

The change

Add locations and languages to the google_ads_campaign block:

resource "google_ads_campaign" "search_brand" {
name = "Search_Brand"
advertising_channel_type = "SEARCH"
campaign_budget = google_ads_campaign_budget.brand.id
languages = ["en"]
locations = ["US", "GB"]
}
  • locations uses two-letter ISO country codes"US", "GB", "CA", "DE", "SG", and so on.
  • languages uses short language codes — "en", "de", "fr", "pl", "zh-CN".

Widening the campaign to Canada later is the kind of one-word diff that reviews itself:

locations = ["US", "GB", "CA"]

Only people actually there

By default, locations = ["US"] does not mean “people in the United States”. Google reads it as people in the US plus people anywhere in the world showing interest in the US — searching for US destinations, watching US content. For a brand campaign that extra reach is often welcome. For anything where the market is the experiment, it’s a confound: run a US, a German, and a French version of the same test and all three can quietly serve to the same third-country audience, so the per-market numbers stop being comparable.

Say which you mean:

resource "google_ads_campaign" "search_brand" {
# ...
locations = ["US", "GB"]
geo_target_type_setting {
positive_geo_target_type = "PRESENCE"
negative_geo_target_type = "PRESENCE"
}
}
  • PRESENCE — people in (or regularly in) those countries.
  • PRESENCE_OR_INTEREST — those people plus anyone interested in them. Google’s default.

The negative side answers the same question for locations you exclude. Leave the block out and bidsmith leaves the setting to the account; write it and a flip in the Google Ads UI shows up in the next plan as drift. This is the same setting the UI calls advanced location options, buried a level down in the locations panel.

Plan and apply

Terminal window
bidsmith plan .

Expected: one + create row per new country/language criterion, a ~ update on the campaign if you changed how those locations are read, and nothing else churns. Commit, PR, merge, apply.

Cities, regions, and uncommon languages

The shipped tables cover every country and the major languages. For a city or region, drop the raw geoTargetConstants/NNNN id into the same list — look it up in Google’s geo target table:

# United States, plus the New York City metro area
locations = ["US", "geoTargetConstants/1023191"]

The same goes for a language that isn’t in the shipped set — use its languageConstants/NNNN id.

Converting an existing campaign

If your campaign already targets locations and languages through explicit google_ads_campaign_criterion resources, you can collapse them onto the campaign and delete the standalone blocks:

# Before — one resource per target
resource "google_ads_campaign_criterion" "geo_us" {
campaign = google_ads_campaign.search_brand.id
location {
geo_target_constant = "geoTargetConstants/2840"
}
}
# After — inline on the campaign
resource "google_ads_campaign" "search_brand" {
# ...
locations = ["US"]
}

bidsmith identifies live criteria by their resolved target, not by an address, so this conversion plans as no change at all — no campaign recreation, no lost history. Running bidsmith refresh on an existing account writes the inline form for you automatically.

Demand Gen campaigns pick a level first

Demand Gen is the one channel where Google asks where the targeting lives before it accepts any: on the campaign, or on its ad groups — never both. Every new Demand Gen campaign starts in ad-group mode unless it opts out at creation, and the choice can’t be changed afterwards.

To keep the two-line form above, add the opt-out:

resource "google_ads_campaign" "demand_gen_fr" {
name = "DemandGen_FR"
advertising_channel_type = "DEMAND_GEN"
campaign_budget = google_ads_campaign_budget.dg.id
target_spend {}
languages = ["fr"]
locations = ["FR"]
demand_gen_campaign_settings {
upgraded_targeting = false
}
}

Or leave the campaign in ad-group mode and declare the targeting per ad group instead — useful when one campaign runs a French and a German ad group side by side:

resource "google_ads_ad_group_criterion" "dg_fr_language" {
ad_group = google_ads_ad_group.dg_fr.id
language {
language_constant = "languageConstants/1002"
}
}
resource "google_ads_ad_group_criterion" "dg_fr_location" {
ad_group = google_ads_ad_group.dg_fr.id
location {
geo_target_constant = "geoTargetConstants/2250"
}
}

Mixing the two levels — languages on a Demand Gen campaign without the opt-out, or ad-group criteria under a campaign that declared it — is a validate error, because Google would reject the apply with an error message that names no cause. For a campaign that already exists, match the level it was created with: bidsmith refresh writes the opt-out block for campaigns that have it, and plan warns when a file declares the other level.

See also