Skip to content

Instantly share code, notes, and snippets.

@caseypage
Last active May 6, 2025 20:58
Show Gist options
  • Save caseypage/5fc3b5bd4ae2fbcdff1286c7bce94a05 to your computer and use it in GitHub Desktop.
Save caseypage/5fc3b5bd4ae2fbcdff1286c7bce94a05 to your computer and use it in GitHub Desktop.
This will automate setting custom parameters for your adwords tracking template at the campaign and adgroup level.
// Sample Tracking Template
// {lpurl}?utm_medium=adwords&utm_campaign={_campaign}&utm_source={_adgroup}&utm_term={keyword}
// This script will set custom parameters {_campaign} and {_adgroup} at the campaign and adgroup level respectively.
// Configuration
// Set the Parameter Format Mode to either "safe" or "strict"
const PARAMETER_FORMAT_MODE = 'safe';
function main() {
updateCampaigns();
updateAdGroups();
}
function formatParameterValue(name, mode = 'safe') {
// adjust accordingly below
if (mode === 'strict') {
return name
.replace(/[^a-zA-Z0-9 ]+/g, '') // remove non-alphanumerics
.toLowerCase() // make lower case
.trim() // remove beginning or trailing spaces
.replace(/[^\w\-\.~ ]+/g, '') // remove anything not URL-safe
.replace(/\s+/g, '_') // replace spaces with underscores
.substr(0, 100); // limit to 100 characters
} else {
return name
.trim() // remove beginning or trailing spaces
.replace(/[^\w\-\.~ ]+/g, '') // remove anything not URL-safe
.replace(/\s+/g, '_') // replace spaces with underscores
.substr(0, 100); // limit to 100 characters
}
}
function getNewCustomParameters(adwordsObject, parameterName, parameterValue) {
let customParameters = adwordsObject.urls().getCustomParameters();
let newCustomParameters = {};
if (customParameters == null) {
newCustomParameters[parameterName] = parameterValue;
} else {
if (customParameters.hasOwnProperty(parameterName)) {
customParameters[parameterName] = parameterValue;
} else if (Object.keys(customParameters).length < 3) {
customParameters[parameterName] = parameterValue;
}
newCustomParameters = customParameters;
}
return newCustomParameters;
}
function updateCampaigns() {
const query = `
SELECT
campaign.id,
campaign.name,
campaign.status
FROM campaign
WHERE
campaign.status IN ('ENABLED', 'PAUSED')
`;
const iterator = AdsApp.search(query);
while (iterator.hasNext()) {
const row = iterator.next();
Logger.log(JSON.stringify(row));
const campaign = row.campaign;
const campaignName = campaign.name;
const paramValue = formatParameterValue(campaignName, PARAMETER_FORMAT_MODE);
try {
const campaignIterator = AdsApp.campaigns()
.withIds([campaign.id])
.get();
if (campaignIterator.hasNext()) {
const campaignEntity = campaignIterator.next();
const newCustomParameters = getNewCustomParameters(campaignEntity, 'campaign', paramValue);
Logger.log(`Campaign: ${campaignName} | ${paramValue}`);
campaignEntity.urls().setCustomParameters(newCustomParameters);
} else {
Logger.log(`Campaign ID ${campaign.id} not found (possibly deleted)`);
}
} catch (error) {
Logger.log(`Error updating Campaign ID ${campaign.id}: ${error.message}`);
}
}
}
function updateAdGroups() {
const query = `
SELECT
ad_group.id,
ad_group.name,
ad_group.status,
ad_group.campaign
FROM ad_group
WHERE
ad_group.status IN ('ENABLED', 'PAUSED') AND
campaign.status IN ('ENABLED', 'PAUSED')
`;
const iterator = AdsApp.search(query);
while (iterator.hasNext()) {
const row = iterator.next();
const adGroup = row.adGroup;
const adGroupName = adGroup.name;
const paramValue = formatParameterValue(adGroupName, PARAMETER_FORMAT_MODE);
try {
const adGroupIterator = AdsApp.adGroups()
.withIds([adGroup.id])
.get();
if (adGroupIterator.hasNext()) {
const adGroupEntity = adGroupIterator.next();
const newCustomParameters = getNewCustomParameters(adGroupEntity, 'adgroup', paramValue);
Logger.log(`AdGroup: ${adGroupName} | ${paramValue}`);
adGroupEntity.urls().setCustomParameters(newCustomParameters);
} else {
Logger.log(`AdGroup ID ${adGroup.id} not found (likely orphaned)`);
}
} catch (error) {
Logger.log(`Error updating AdGroup ID ${adGroup.id}: ${error.message}`);
}
}
}
@igsm
Copy link

igsm commented May 26, 2020

Hey Casey,
Thanks for the script and a tutorial on YouTube. It is very helpful.
One thing, though. Do we need var new_custom_parameters = {}; in get_new_custom_parameters function? Otherwise, it throws an error.
Cheers,
Igor

@caseypage
Copy link
Author

Ah yes - thanks, I added that.

@princess-carolyn
Copy link

Hey Casey,
thanks for the script!
I've two question, do I have to put a tracking template in the GAds panel using this script or just running script is enough?
And next - how can I set up custom parameters to {_keywords}?

Cheers!

@caseypage
Copy link
Author

caseypage commented Jul 10, 2020

Yes - you have to create a tracking template inside google adwords first. You do this under the Account Settings. This is a video on how to do that.

Inside your tracking template, you'll have something like: "utm_term={keyword}" - this will capture the keyword using a value track parameter. Video about that here.

This video here is a walk through on how to use the above script

The script above only sets the "_campaign" and "_adgroup" custom parameter. If you need to set other custom parameters, then you'll need to modify the script to do what you need it to do.

Hope that helps!

@princess-carolyn
Copy link

Thank you Casey! Now is all clear! :)

@augustorebagliati
Copy link

Thanks, this is awesome! Is there by chance a MCC version of this script?

@Shani-b21
Copy link

Hi Casey,

Thank you very much for this script!
I would like to add a custom parameter at the ad level, how do I do that?

Thank you

@caseypage
Copy link
Author

I don't think this is possible. Based on the ads script documentation: https://developers.google.com/google-ads/scripts/docs/reference/adsapp/adsapp_adurls - there is not a "setCustomParameters" method at the ad level. So I do not know of a way to do that.

@AlamgirAnwar
Copy link

Hey Casey,
Thanks for the script and a tutorial on YouTube. It is very helpful.
Can you guide me on how I can place this tag {{utm_keyword}} on a website?
If you have a guided video, it will be useful.

Best Wishes,

Alamgir

@princess-carolyn
Copy link

Hey,
is it possible to modify this script to run it only on campaigns with some specific campaign name? For example only on campaigns which have TEST in name?

@st1digital
Copy link

Hi,
I was running this script successfully for the last years - thanks for that!

Is it possible, that the script is not working for Demand Gen campaigns?

Thank in advance!

Best,
Daniel

@caseypage
Copy link
Author

caseypage commented May 6, 2025

Hi, I was running this script successfully for the last years - thanks for that!

Is it possible, that the script is not working for Demand Gen campaigns?

Thank in advance!

Best, Daniel

Just got around to looking at this. For updating custom parameters on a campaign, you use CampaignUrls to do that. The issue is that Demand Gen and Video campaigns do not support this.

The previous version of my script used a method that did not return Demand Gen or Video campaigns.. so I updated the script to use GAQL (Google Ads Query Language) .. this does in fact return the campaigns now, but they do not support the CampaignUrls() method I need to update the parameters. Maybe at some point in the future they'll add support for Demand Gen & Video campaigns via an AdWords App script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment