One command turns a campaign into translated locale variants or persona-targeted copies. Every variant is a TypeScript file — reviewable, diffable, and applied like any other campaign.
$ ads expand --campaign campaigns/brand-search.ts \
--locales de,fr,es --translate
Expanding: brand-search → 3 locale variants
✓ campaigns/brand-search--de.ts (DE · translated)
✓ campaigns/brand-search--fr.ts (FR · translated)
✓ campaigns/brand-search--es.ts (ES · translated)
Each variant: localized headlines · geo targeting updated · budget preserved
Run `ads plan` to preview all changes before applying.Expanding to a new market means opening the platform UI and clicking through every campaign, ad group, and ad — duplicating and translating each one by hand.
5 markets means 5x the campaigns to maintain. A budget change in the original campaign has to be repeated in every locale variant.
Targeting a different audience angle — SaaS founders vs. ops managers — means yet another full duplication. The complexity compounds.
ads-as-code expands a campaign into locale translations or ICP variants in one command. Variants are TypeScript files — diffable, reviewable, and applied like any other campaign.
`ads expand --locales` creates translated copies of your campaign for each target locale. Headlines, descriptions, and body copy are translated by AI. Targeting and geo are updated automatically.
$ ads expand --campaign campaigns/brand-search.ts \
--locales de,fr,es,nl --translate
brand-search--de.ts
~ name → "Acme — Brand Search — DE"
~ geo ["US"] → ["DE", "AT", "CH"]
~ headlines 15 headlines translated to German
brand-search--fr.ts
~ name → "Acme — Brand Search — FR"
~ geo ["US"] → ["FR", "BE", "CH"]
~ headlines 15 headlines translated to French
✓ 4 files created. Run `ads plan` to review.Use `ads expand --icps` to generate variants targeting different buyer personas. Each variant gets rewritten copy for that persona while preserving budget and structure.
$ ads expand --campaign campaigns/brand-search.ts \
--icps "ops managers at B2B SaaS,founders at early-stage startups" \
--rewrite-copy
brand-search--ops-managers.ts
~ headlines "Automate the Work Your Ops Team Hates"
"Reclaim 10 Hours a Week for Your Ops Team"
brand-search--founders.ts
~ headlines "Ship Workflows Without Engineering Help"
"One Person Can Run What Took a Team"
✓ 2 files created. Run `ads plan` to review.Variants are plain TypeScript files. Edit them like any campaign — adjust bids, swap headlines, change targeting. Then run `ads plan` to diff all variants at once.
// campaigns/brand-search--de.ts
// Generated by `ads expand` — edit freely
import { google, daily, exact, phrase, headlines, descriptions, rsa, url } from '@upspawn/ads'
export default google.search('Acme — Brand Search — DE', {
budget: daily(30), // ← adjusted for DE market
bidding: 'maximize-clicks',
})
.group('Core KW', {
locale: { language: 'de', geo: ['DE', 'AT', 'CH'] },
keywords: [...exact('acme'), ...phrase('workflow automatisierung')],
ad: rsa(
headlines('Workflows mit KI automatisieren', 'Kein Code nötig', '500+ Integrationen'),
descriptions('Acme verbindet Ihre Tools. Sofort einsatzbereit.'),
url('https://acme.dev/de/'),
),
})`ads expand --locales` translates headlines, descriptions, and body copy into target languages and updates geo targeting to match.
`ads expand --icps` rewrites copy for different buyer personas while preserving structure, budgets, and bidding strategy.
One source campaign becomes N variant files. Every variant is a full TypeScript campaign definition — no templates, no magic.
Variants are plain TypeScript files. Edit bids, copy, and targeting before applying. No lock-in to generated content.
Expand multiple campaigns in one pass. All variants are created, reviewed with `ads plan`, and applied in one `ads apply`.
Budget tiers, ad group hierarchy, keyword match types, and bidding strategy carry over unchanged from the source campaign.