Skip to content

Share campaign settings across every campaign

Open any two search campaign files in your account and compare: the same advertising_channel_type, the same languages and countries, the same manual_cpc and network_settings blocks, ~20 lines repeated per campaign. Only the name, budget, and everything under the campaign differ.

A defaults block declares that shell once, for a whole resource type:

defaults "google_ads_campaign" {
advertising_channel_type = "SEARCH"
languages = ["en"]
locations = ["US"]
contains_eu_political_advertising = "DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING"
manual_cpc {
enhanced_cpc_enabled = false
}
network_settings {
target_google_search = true
target_search_network = false
target_content_network = false
target_partner_search_network = false
}
}

Put it in a shared file (shared.bid works well) and every campaign in the folder tree inherits it. Each campaign file now says only what makes that campaign itself:

resource "google_ads_campaign" "cookies" {
name = "GH_Cookies — Search"
status = "ENABLED"
campaign_budget = google_ads_campaign_budget.cookies.id
}

Overriding per campaign

Set the attribute on the campaign and it wins — the default only fills gaps:

resource "google_ads_campaign" "fingerprint" {
name = "GH_Fingerprint — Search"
status = "ENABLED"
campaign_budget = google_ads_campaign_budget.fingerprint.id
locations = ["US", "CA", "GB"]
}

A nested block like manual_cpc overrides as a whole: declare it on the campaign and the default block is ignored entirely, so there’s never a half-merged surprise.

When one shell doesn’t fit the whole account

An unnamed defaults block applies to every resource of its type in the tree. That is what you want when the account is all one kind of campaign. It stops being what you want the moment it isn’t: an account that mixes search and video campaigns cannot factor the search shell this way, because the video campaigns — which say nothing about network_settings or manual_cpc — would silently inherit it.

Give the block a name, and only the campaigns that ask for it get it:

defaults "google_ads_campaign" "search_us" {
advertising_channel_type = "SEARCH"
languages = ["en"]
locations = ["US"]
network_settings {
target_google_search = true
target_search_network = false
target_content_network = false
target_partner_search_network = false
}
}
resource "google_ads_campaign" "cookies" {
defaults = defaults.search_us
name = "GH_Cookies — Search"
campaign_budget = google_ads_campaign_budget.cookies.id
}

A campaign that doesn’t name defaults.search_us is untouched by it — so the video campaigns in the same folder keep their own shape.

Three things to know:

  • A named block replaces the unnamed one for the campaign that opts in; it doesn’t stack on top. One opt-in line tells the whole story, so a reviewer reading the diff doesn’t have to go hunting for a second block that also applied.
  • You can declare as many named blocks per type as you like (search_us, search_de, video_shell) — only the name has to be unique. The one-per-type rule still holds for the unnamed block.
  • Naming a block that doesn’t exist is an error, and bidsmith lists the names that do. Inheriting the wrong shell silently is the failure this attribute exists to prevent, so it will not fall back to the unnamed block.

Campaigns built from a template

Campaigns instantiated through a module block get the same shell. The template names the block; the block itself stays in shared.bid at the root of the tree:

templates/preroll-campaign.bid
resource "google_ads_campaign" "search" {
name = var.campaign_name
status = "ENABLED"
defaults = defaults.search_shell
campaign_budget = google_ads_campaign_budget.budget.id
}

So a folder that mixes hand-written campaigns and templated ones keeps one copy of the shell, not two that can drift apart. A defaults block declared inside a template is private to it, and wins over an outer block of the same name — handy when one template genuinely needs its own shape.

Adopting it on a live account

The merge happens when bidsmith reads your files, before anything is compared with Google Ads — so rewriting existing campaigns onto a defaults block changes nothing live. bidsmith plan right after the refactor shows no changes; that’s the point.

What it can’t hold

  • One unnamed defaults block per resource type. Two files both declaring campaign defaults is an error — bidsmith tells you which files collide. Named blocks lift that limit: declare as many as you need, one per name.
  • Ad bodies. Every ad declares its own ad block or points at an ad_template.
  • Resources that come in packs — like the mobile/tablet exclusion pair every campaign carries — are separate resources, not campaign attributes. Fan those out with for_each.