
How to add UTMs in Google Ads for better attribution
Are you wrestling with Google Ads traffic appearing in GA4 as (organic) or (direct), wondering why the GCLID auto-tagging is failing? You’re not alone. If you’re looking for a robust, future-proof method to ensure every Google Ads click is accurately measured in GA4 with UTMs, you’re in the right place.
Originally published on Medium.
Intro
We know that applying UTM parameters is now essential to measuring Google Ads traffic in Google Analytics 4 (GA4) and the GCLID is becoming less reliable as a long-term solution. This is due to:
- Browsers increasingly blocking, stripping or obfuscating the GCLID parameter — UTMs are not affected (for now)
- Google Analytics cannot use the GCLID when ad_user_data is denied in Consent Mode, or when the user has opted out of personalised advertising in their Google Account
Just like Microsoft Ads, Meta Ads and every other marketing channel, UTMs are the standard for GA4 traffic tracking and attribution — and Google Ads is no longer the exception. And until Google Ads has an option to do this for us automatically (like Microsoft Ads does), we have to do this manually ourselves.
I’ve seen a number of ‘solutions’ to this — generally using Google Ads Scripts and customising the campaign-level tracking templates. However, tracking templates aren’t designed for UTM injection — they’re for redirect tracking i.e. the ones used by DV/SA360.
Instead, the Final URL suffix is designed exactly for applying query string parameters - perfect for adding UTMs!
So here’s my (better) UTM solution for Google Ads…
Step 1: Script
The reason we need a script is because the Campaign Name is not available as a ValueTrack parameter in Google Ads, so we need to create it as a custom parameter.
To do this, create a new Google Ads script and add the following code:
// -------------------------------
// By Daniel Perry-Reed
// https://kickflipanalytics.com/
//
// Run script daily and add the following to the account-level final URL suffix:
// utm_medium=cpc&utm_source=google&utm_campaign={_campaignname}&utm_id={campaignid}&utm_content=fallback
// -------------------------------
function main() {
//var campaignIdToTest = 123456789; // <-- Include for a single campaign 1/2
Logger.log("Updating campaigns in account: " + AdsApp.currentAccount().getCustomerId());
var campaigns = AdsApp.campaigns()
.withCondition("Status != REMOVED")
//.withCondition("CampaignId = " + campaignIdToTest) // <-- Include for a single campaign 2/2
.get();
while (campaigns.hasNext()) {
var campaign = campaigns.next();
try {
var customParams = campaign.urls().getCustomParameters();
customParams.campaignname = encodeURIComponent(campaign.getName());
campaign.urls().setCustomParameters(customParams);
Logger.log("Set {_campaignname} for: " + campaign.getName());
} catch (e) {
Logger.log("Error processing campaign '" + campaign.getName() + "': " + e.message);
}
}
Logger.log("All campaign updates completed.");
}
Give the script a name like "Campaign custom parameter: _campaignname" and click save.
You can see that there are two lines commented out - these are used to test the script on one campaign first. Don't skip this step!
- Remove the "//" to un-comment these two lines, and then chose a Campaign ID from your account to replace the "123456789".
- Authenticate your Google Account if not already done so, and then you can 'preview' the script to see what will happen without actually doing any changes.
- Check the results at the bottom of the screen where you'll see that the {_campaignname} should be added as a campaign-level custom parameter. Make sure nothing else is changed!
When happy, comment these two lines out again, and click run…
Once complete, enable the schedule for daily, I normally chose 23:00–00:00, but you can set it to what you like here - even run it hourly if you so wish.
Step 2: Final URL suffix
Now, let's make use of this new {_campaignname} parameter.
Head to Admin > Account settings and then expand Tracking.
Add the below to the Final URL Suffix:
utm_medium=cpc&utm_source=google&utm_campaign={_campaignname}&utm_id={campaignid}&utm_content=fallbackClick save, and you're done. That's it!
Customisations and adjustments
You may notice that I set the utm_term to "fallback".
This is so that I can identify and quantify this traffic where the GCLID was not able to be used, and GA4 used the 'fallback' method of the UTMs. You can change this to anything you like, or remove it completely if you so wish.
.
You may have also noticed that this script and UTM parameters are only applying and using the campaign name, and not the ad group, keyword, etc.
If you need/want these more granular fields, you can ChatGPT your way to an updated script setting the additional custom parameters at the level you require. The Final URL suffix will remain at the account-level however, just add/update the UTMs to use your custom fields as you so wish.
.
If you don't need the Campaign Name in your UTMs and happy with just the Campaign ID, you can skip the script altogether. You can then use the standard valueTrack {campaignid} for both the utm_id and utm_camapign, using something like the below in the account-level Final URL Suffix:
utm_medium=cpc&utm_source=google&utm_campaign={campaignid}&utm_id={campaignid}&utm_content=fallbackConclusion
Whatever approach you take here, it's essential for marketers to use UTM parameters across ALL marketing links to measure the performance as accurately as possible in GA4 - Google Ads is no exception.
Feel free to use my method detailed above, or adapt to fit your needs. Full disclosure, I used ChatGPT and a lot of refining and testing to get this code to where it is. This post however is all 100% me - whether that's good or not is up to you!
Find me on LinkedIn if you have any questions or suggestions.
This is a solution I developed at Data to Value while working with some great marketing teams , so a massive thank you to my colleagues and clients that I worked with to help me figure this out and develop this approach!