Serve only the extensions you declared
Your file says a campaign has four sitelinks. The live ad shows eight. The other four came from somewhere else — added in the Google Ads UI, inherited from an account-level extension that attaches to every campaign, or generated by Google.
By default bidsmith leaves them alone: it only reconciles what it can prove it manages. This page turns that around, so that within a scope you name, the file is the complete list and anything else is removed.
A campaign owns the kinds it declares
Ownership is not a flag you pass — it’s what declaring something means. The moment a campaign declares one sitelink, bidsmith treats that campaign’s sitelinks as yours:
resource "google_ads_campaign_asset" "checkout_sitelinks" { campaign = google_ads_campaign.checkout.id assets = [ google_ads_sitelink_asset.free_delivery.id, google_ads_sitelink_asset.size_guide.id, ]}bidsmith plan .google_ads_campaign.checkout (removed sitelink "Gift cards") - destroygoogle_ads_campaign.checkout (removed sitelink "Our story") - destroy
Plan: 0 to create, 0 to update, 2 to destroy, 1 to adopt, 12 unchanged.The scope is one kind of extension on one campaign. Declaring sitelinks says nothing about that campaign’s callouts, and nothing at all about any other campaign. A campaign that declares no sitelinks keeps every sitelink it has.
The same holds for an ad group with
google_ads_ad_group_asset,
and for the callouts and
structured_snippet shorthands on a campaign.
The account level has to be asked for
Extensions attached to the account rather than to a campaign show up
under every campaign you run — a generic “Shop now” sitelink appearing
beneath an ad about something else entirely. Removing one changes every
campaign at once, so bidsmith never does it because a block happens to
exist. Say so in the provider block:
provider "google_ads" { customer_id = "1234567890" owns = ["sitelinks", "callouts"]}account (removed account-level sitelink "Shop now") - destroyaccount (removed account-level callout "Free returns") - destroyNow the account-level sitelinks and callouts are exactly the
google_ads_customer_asset
blocks in your files — including none at all, which is the usual answer
once each campaign carries its own.
owns accepts sitelinks, callouts, structured_snippets, and
calls. Anything you leave out is untouched.
Getting there without breaking anything
-
Adopt first. Anything you want to keep has to be declared before ownership is on, or the first apply removes it — see Adopt sitelinks and callouts you already have.
-
Plan and read the destroys.
bidsmith plan .lists every removal by what it puts on the page (removed sitelink "Gift cards"), not by an id. Nothing has been sent to Google Ads. -
Delete the blocks you don’t want, keep the ones you do, and plan again until the destroy list is exactly what you meant.
-
Apply.
bidsmith apply .shows the same list and asks for confirmation before anything is removed.
What Google attached is paused, not removed
An extension Google’s asset automation attached is not yours to delete: bidsmith cannot recreate one, so Google simply attaches it again and the same destroy would come back on every plan forever. Those are paused instead. A paused extension stops serving and stays paused, which is the end state you were after:
~ pause google_ads_campaign.checkout (paused sitelink "Firefox too") (status: "ENABLED" -> "PAUSED"; attached by Google's asset automation)
Plan: 0 to create, 0 to update, 2 to destroy, 1 to pause, 12 unchanged.This happens automatically inside a kind of extension you already declare — declare your sitelinks and an invented sitelink is paused with it. For the ones no block can declare, because there is no such thing as a business-name resource, claim them on the campaign:
resource "google_ads_campaign" "checkout" { # ... owns = ["automatically_created_assets"]}Account-level ones take the same token in the provider block. Until
something claims them, plan counts them in a warning on every run:
plan: warning: account: Google's asset automation is serving 6 asset(s) nothingdeclares (2 BUSINESS_NAME, 2 LOGO, 2 SITELINK). Add`owns = ["automatically_created_assets"]` to the campaign — or to the `provider`block for the account-level ones — and bidsmith will pause them so they stopserving.Pausing stops the assets already there. To stop new ones being made, see
Stop Google writing your ad copy
— which half of that a .bid can declare, and which half is a switch in
the Google Ads UI.
One thing prune will not touch
Anything on a Video campaign. The Google Ads API cannot mutate YouTube campaigns at all, extensions included. The API rejects the operation outright, and one rejected operation fails the whole batch with it — so bidsmith checks before sending, skips it, and says so, while the rest of your changes still apply:
Plan: 0 to create, 0 to update, 2 to destroy (1 skipped), 12 unchanged.The same skip-and-warn applies to a device criterion nobody declared: Google refuses to remove those, so bidsmith reports the drift rather than sending an operation that would sink the batch.
See also
- Adopt sitelinks and callouts you already have — the step before this one.
- Stop Google writing your ad copy — stopping the automation from making more of them.
- Make a search ad render taller with extensions — what to declare in the first place.
bidsmith plan— reading a destroy row.