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:
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
defaultsblock 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
adblock 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.