Engineering

There Wasn’t a Terraform Provider for Apple Ads

3 min read

Three teddy bears at sunset planning Apple Ads as Terraform — a whiteboard maps Terraform to Apple with campaign HCL and reports.

At Pi Cubed, we use Terraform heavily to manage the infrastructure behind our apps. DNS, hosting, secrets, and most of the other boring glue already lives in code, which makes it easy to see how an app is configured and review changes before they happen.

Apple Search Ads was one of the exceptions. As we started advertising more of our apps, we wanted campaigns to work the same way as everything else: define them in code, review the change, run terraform plan, and apply it.

We went looking for a Terraform provider for Apple Ads and couldn’t find one, so we built and open-sourced picubedllc/appleads.

Apple Ads, but in Terraform

The provider manages the core pieces of an Apple Search Ads setup: campaigns, ad groups, keywords, negative keywords, and targeting. It also includes data sources for things like apps, geolocations, product pages, bid recommendations, and performance reports.

The end result is that a Search Ads campaign can live alongside the rest of an app’s infrastructure:

resource "appleads_campaign" "us" {
  name                 = "My App - US"
  adam_id              = var.adam_id
  countries_or_regions = ["US"]
  daily_budget_amount  = "25.00"
  status               = "ENABLED"
}

resource "appleads_ad_group" "default" {
  campaign_id        = appleads_campaign.us.id
  name               = "Default"
  default_bid_amount = "1.50"
  status             = "ENABLED"
}

resource "appleads_keyword" "keywords" {
  for_each = {
    "nyc parking" = "2.00"
    "parking app" = "1.50"
  }

  campaign_id = appleads_campaign.us.id
  ad_group_id = appleads_ad_group.default.id
  text        = each.key
  match_type  = "EXACT"
  bid_amount  = each.value
  status      = "ACTIVE"
}

Now changing a budget, adding a keyword, or creating another campaign is just a Terraform change. More importantly for us, the configuration is visible. We don’t have to open Apple Ads to remember how another app was configured or manually recreate the same structure for the next one.

It also means we can use normal Terraform patterns as the studio grows. Common campaign structures can become modules, app-specific settings can remain variables, and changes can go through the same pull request workflow as everything else.

Making destructive changes harder

There was one place where we intentionally made the provider behave a little differently from what you might expect from Terraform: deleting campaigns.

Removing infrastructure with Terraform is normally unremarkable. Removing an Apple Ads campaign isn’t. Apple permanently archives it, and the historical identity of a campaign is valuable when you care about its performance over time.

We didn’t want an accidental resource removal or terraform destroy to quietly archive a campaign, so deletion is disabled by default:

provider "appleads" {
  allow_campaign_deletion = false
}

If you actually want Terraform to archive campaigns, you can opt into it:

provider "appleads" {
  allow_campaign_deletion = true
}

We also recommend Terraform’s prevent_destroy lifecycle rule for important campaigns. It’s a small choice, but one we think makes sense for this particular kind of infrastructure: destructive behavior should require intention.

Open source

We originally built the provider because we wanted Apple Ads to fit the way we already run Pi Cubed. Once we had it working, there wasn’t much reason to keep it to ourselves.

The provider is available as picubedllc/appleads on the Terraform Registry. It’s still early and there are parts of the Apple Ads API we haven’t covered yet, but we’re using it ourselves and will keep expanding it as we need more of the API.

If you already use Terraform and run Apple Search Ads, hopefully it saves you from having to build the same thing. The GitHub repo is where the work happens.

← All posts