Repeat a resource for each item in a list
Some resources come in near-identical packs: the mobile + tablet exclusion pair your account mandates on every campaign, or eleven sitelink attachments that differ only in which asset they point at. Writing each one out by hand is pure boilerplate — and the copies drift.
for_each on a resource block fans one block out into one resource
per entry.
From a list
Each entry becomes each.value (and its own address key). The classic
device-exclusion pair:
resource "google_ads_campaign_criterion" "cookies_device_exclusions" { for_each = ["MOBILE", "TABLET"] campaign = google_ads_campaign.cookies.id bid_modifier = 0
device { type = each.value }}That is two live criteria — addressed as
cookies_device_exclusions["MOBILE"] and
cookies_device_exclusions["TABLET"] — from one block. The list can
also live in a shared local
(for_each = local.excluded_devices) so every campaign file reuses
the same one.
From a map
When each copy needs its own value, use a map: the key names the
instance, each.value carries its value — including references to
other resources:
resource "google_ads_campaign_asset" "cookies_sitelinks" { for_each = { neverconsent = google_ads_sitelink_asset.sl_neverconsent.id adblock = google_ads_sitelink_asset.sl_adblock.id help = google_ads_sitelink_asset.sl_help.id } campaign = google_ads_campaign.cookies.id asset = each.value field_type = "SITELINK"}each.key works too, anywhere in the block — including inside
${…} interpolation,
so a name like "Sitelink — ${each.key}" names each instance after
its map key.
When each copy needs more than one value
A map value can be a whole record, and each.value.<field> reaches
inside it. That is what turns N sitelinks — four fields each — into one
block:
resource "google_ads_sitelink_asset" "sl" { for_each = { howto = { text = "How it works", url = "https://example.com/how" } chrome = { text = "For Chrome", url = "https://example.com/chrome" } } link_text = each.value.text final_urls = [each.value.url]}Misspell a field and bidsmith says which names the entry actually has, rather than leaving you to compare two lists by eye.
Attaching what you just fanned out
A generated instance has a keyed address — sl["howto"] — and you can
point at it from another resource with the same key:
resource "google_ads_campaign_asset" "sl_link" { for_each = ["howto", "chrome"] campaign = google_ads_campaign.cookies.id asset = google_ads_sitelink_asset.sl[each.key].id field_type = "SITELINK"}So the assets and their attachments both collapse to one block, driven by the same set of keys.
Adopting it on a live account
Rewriting existing hand-written resources into a for_each block is
safe: criteria, assets, and keywords are matched by their content, so
bidsmith plan shows no changes. Campaigns, ad groups, and ads keep
their live state too — their identity label just moves to the new
keyed address, which plan shows as an ~ adopt (label only) row before
anything is written.
Where it stops
for_each fans out the resources under a campaign. To stamp out
whole campaign families — same structure, different city or product —
use a module with for_each
instead.
For callouts and structured snippets specifically, there is now a
shorter road still: declare them
straight on the campaign
and skip the asset and attachment resources altogether. Reach for
for_each when the assets are shared between campaigns, or when they
carry fields the inline form doesn’t cover.