Skip to content

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") - destroy
google_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") - destroy
account (removed account-level callout "Free returns") - destroy

Now 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

  1. 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.

  2. 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.

  3. Delete the blocks you don’t want, keep the ones you do, and plan again until the destroy list is exactly what you meant.

  4. 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) nothing
declares (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 stop
serving.

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