The gap
ValueTrack is the mechanism Google Ads uses to stamp click context into your URLs: {campaignid}, {adgroupid}, {keyword}, {device}, and a few dozen more. What it has never offered is {campaignname}. The click arrives knowing exactly which campaign sent it, as a number nobody downstream can read.
That gap matters the moment names carry meaning your other systems need. A utm_campaign that says what the campaign is, rather than an ID you have to look up. Call tracking, where platforms attribute a phone call to the campaign name they find in the landing page URL (the architecture in the call-based conversion piece depends on exactly this). A hidden field on a lead form, so the CRM shows sales a campaign name instead of a number. A warehouse join that arrives pre-joined because the name landed with the click.
The usual answers are both bad. Hand-editing URLs per campaign rots the day someone launches without the ritual. Maintaining an ID-to-name lookup table somewhere rots the first time someone renames a campaign, which in a well-run account is routine hygiene, not an edge case.
The injection mechanism
Google Ads custom parameters are user-defined ValueTrack slots. Set campaign = MY_CAMPAIGN_NAME on a campaign and {_campaign} resolves to that value in any tracking template, final URL suffix, or final URL. They cascade: a parameter defined at a lower level overrides the same key defined above it. So the whole pattern is one tracking template, set once at account level, that references two parameters the entities themselves carry:
# self-maintaining UTMs {lpurl}?utm_source=google&utm_medium=cpc&utm_campaign={_campaign}&utm_content={_adgroup} # call-tracking payload (Infinity-style) {lpurl}?infinity=ict2~net~gaw~cmp~{_campaign}~ag~{_adgroup}~ar~{creative}
The template never changes again. What has to stay true is the parameters themselves: every campaign carrying its own name in {_campaign}, every ad group in {_adgroup}, through launches and renames alike. That is a synchronisation job, and synchronisation jobs belong to software, not to checklists.
A script keeps the names true
The tool is a single Google Ads Script, open source at google-ads-campaign-name-sync. On every run it walks each enabled campaign (Search, Display, Performance Max, Shopping, Video, Demand Gen where the runtime exposes it) and each ad group inside enabled campaigns, standard and Shopping alike. For each entity it compares the stored parameter against the entity's current URL-encoded name and rewrites it on any mismatch. That comparison is the point: it catches missing parameters, blanked values, and renames with the same check, so an account that renames campaigns weekly stays as clean as one that never does.
It merges rather than replaces. setCustomParameters() overwrites an entity's whole parameter set, so the script reads the existing set, updates its own two keys, and writes the merged result back; parameters owned by other tools survive. Google's limits are guarded rather than tripped over: entities hold at most 8 custom parameters and values cap at 200 characters, and entities that would breach either limit are skipped with a logged warning instead of failing the run.
Account: Example Account (123-456-7890) Mode: LIVE [Search/Display campaigns] scanned=3 updated=0 unchanged=3 skipped=0 errors=0 [Performance Max campaigns] scanned=4 updated=1 unchanged=3 skipped=0 errors=0 [Shopping campaigns] scanned=0 updated=0 unchanged=0 skipped=0 errors=0 [Ad groups] scanned=36 updated=0 unchanged=36 skipped=0 errors=0 ---------------------------------------- TOTAL scanned=43 updated=1 unchanged=42 skipped=0 errors=0
The logging is half the tool. One line per change, a summary per entity type, a grand total, and a thrown error if any entity failed, which flips a scheduled run to Failed and triggers Google's script-failure email. A sync job that cannot tell you what it did is a sync job you will eventually distrust for good reason.
The Performance Max trap
This script exists because the commonly shared version of the same idea, including the one at least one tracking vendor hands to its customers, has a silent failure mode. It updates campaigns through AdsApp.bulkUploads(), a CSV bulk upload, and bulk uploads do not apply campaign edits to Performance Max campaigns. The upload reports nothing back to the script, so the logs look healthy while every PMax campaign in the account carries no name at all.
I found this on a live account where every Search campaign dutifully carried its name and all four PMax campaigns carried nothing. Nothing in the account was misconfigured; the delivery mechanism simply skips the campaign type quietly. The tracker showed nameless PMax traffic for as long as the script had been "working".
Don't sync custom parameters through bulk uploads. The upload silently skips Performance Max campaigns and gives the script no per-row feedback, so the failure is invisible in the logs. Set parameters directly on each entity instead.
The fix is structural. Google Ads Scripts now exposes every campaign type behind its own selector, and AdsApp.performanceMaxCampaigns() supports urls().setCustomParameters() exactly like the others. Set the parameter on the entity, skip the upload entirely, and PMax behaves like everything else. The same structure is why the script covers Shopping, Video, and Demand Gen: each is one more selector in a list, not a special case.
Asset groups
PMax has asset groups instead of ad groups, so {_adgroup} is empty on PMax traffic by default. Until recently that was final: asset groups had no URL fields at all. Google has now quietly added asset group URL options (campaign, then Asset groups, then Edit assets, then Asset group URL options): a tracking template, a final URL suffix, and custom parameters, per asset group.
Because custom parameters resolve lowest-level-first, that enables a neat trick: add adgroup = ASSET_GROUP_NAME as a custom parameter on each asset group, and the existing account-level template's {_adgroup} resolves per asset group. No template changes, and the tracker receives asset group names in the same field ad group names arrive in.
The honest caveat: this is a manual step per asset group for now. Scripts cannot set it (the AssetGroupUrls object only exposes final URL and path methods), and at the time of writing the field is not even queryable in the Google Ads API. Put it in the asset group creation checklist; the script will absorb it as one more entity source the day Google exposes it. If numeric IDs are enough for your reporting, {assetgroupid} is now also a valid ValueTrack parameter in PMax tracking templates.
Set it up
Three minutes, four steps. In Google Ads, go to Tools, then Bulk actions, then Scripts, create a new script and paste in sync-custom-parameters.gs. Authorise it when prompted. Click Preview first: preview saves nothing, and the log shows exactly what a live run would change, which is also the fastest way to audit how many of your entities are currently nameless. Then run it live and schedule it hourly or daily; new and renamed entities are picked up on the next run.
Reference {_campaign} and {_adgroup} in your tracking template, and the names look after themselves from there. MIT licensed; the README carries the Infinity-specific notes and the asset group walkthrough.