Hello All,
UPDATE - Resolved: Following up on this as turns out I was able to identify what the culprit was leading to the status messages saying "There are illegal characters in the string". The issue was caused by attempting to define the custom parameter as "ad_groupname" which was then being referenced as "_ad_groupname". Apparently, you cannot use underscores or anything other than strict alphanumeric characters in naming custom parameters even though I thought I already tried that before, must not have. I simply changed the script definition for Ad Group-level custom parameter from "ad_groupname" to remove the underscore, defining it as "adgrpname" instead and everything worked as expected. Simple fix of course, staring at me the entire time but thank you for your help and time.
For future reference, if anyone is looking to use custom scripts to set Campaign-level FinalURL Suffixes so that the UTM data dynamically populates dynamic ad group names and campaign names automatically, below are the scripts I'm using (there are three separate scripts as I'm not adept enough to combine them further). I hope this helps others who I think are probably looking for something similar on here.
Script #1 - Dynamic Custom Parameter for Campaign Name(s) | Applies on Campaign-level
// This script sets the custom parameter campaign (_campaign) for all campaigns in the account.
// This parameter contains the campaign name transformed into a URL-compatible format and can be used in the tracking template or final URL suffix.
// Before applying: Ensure that this parameter is not used for another purpose to avoid overwriting important information.
// Also, check that there isn’t a campaign parameter with an empty value, as this can cause errors.
// [Schedule] The script should be scheduled to run regularly, e.g., every hour, to add parameters to new campaigns.
// It is also good to manually run it after adding new campaigns.
// [Warning] As of October 27, 2024: The script does not work for all campaign types, including Demand Gen and Video. These need to be updated manually using Google Ads Editor.
// Remember: Changing a campaign name will leave the old names in historical data in Analytics. Therefore, avoid changing campaign names.
// © ADEQUATE www.adequate.digital 2024
// More info:
https://adequate.digital/en/utms-for-google-ads-campaigns/
function main() {
processAccount();
}
function processAccount() {
// Process Standard Campaigns (Search and Display)
try {
var campaignIterator = AdsApp.campaigns().get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
processCampaign(campaign);
}
}
catch (e) {
Logger.log('Search and Display Campaigns encountered an issue: ' + e.message);
}
// Optional: Process Shopping Campaigns - NOT APPLICABLE
// try {
// var shoppingCampaignIterator = AdsApp.shoppingCampaigns().get();
// while (shoppingCampaignIterator.hasNext()) {
// var campaign = shoppingCampaignIterator.next();
// processCampaign(campaign);
// }
//}
// catch (e) {
// Logger.log('Shopping Campaigns encountered an issue: ' + e.message);
// }
// Process Performance Max Campaigns
try {
var pmaxCampaignIterator = AdsApp.performanceMaxCampaigns().get();
while (pmaxCampaignIterator.hasNext()) {
var campaign = pmaxCampaignIterator.next();
processCampaign(campaign);
}
}
catch (e) {
Logger.log('PMAX Campaigns encountered an issue: ' + e.message);
}
// Optional: Process Demand Gen campaigns with error handling - NOT APPLICABLE
// try {
// var demandGenCampaignIterator = AdsApp.demandGenCampaigns().get();
// while (demandGenCampaignIterator.hasNext()) {
// var campaign = demandGenCampaignIterator.next();
// processCampaign(campaign);
// }
// } catch (e) {
// Logger.log('Demand Gen campaigns may not be supported in this account: ' + e.message);
// }
}
function processCampaign(campaign) {
var campaignName = campaign.getName(); // Get the original campaign name
// Standardization: Remove anything that is NOT a letter, number, underscore, or hyphen.
var cleanedCampaignName = campaignName.replace(/[^a-zA-Z0-9_\-]/g, '');
// Try Percent-encode the campaign name
try{
var modifiedCampaignName = encodeURIComponent(cleanedCampaignName);
// Truncate if necessary to 250 characters
if (modifiedCampaignName.length > 250) {
Logger.log('Warning: The encoded campaign name for "' + campaignName + '" exceeds 250 characters and will be truncated.');
modifiedCampaignName = modifiedCampaignName.substring(0, 250);
}
// Get existing custom parameters at the campaign level
var customParameters = campaign.urls().getCustomParameters() || {};
// Update or add the 'campaign' parameter
customParameters['campaign'] = modifiedCampaignName;
// Set the updated custom parameters back to the campaign
campaign.urls().setCustomParameters(customParameters);
Logger.log('Updated campaign "' + campaignName + '" (cleaned: "' + cleanedCampaignName + '") with custom parameter campaign=' + modifiedCampaignName);
}
catch (e) {
Logger.log('ERROR encoding campaign name "' + campaignName + '": ' + e.message);
// Optionally, you could skip this campaign or try a different cleaning strategy here.
}
}
Script #2 - Dynamic Custom Parameter for Ad Group Name(s) | Applies on Ad Group-level
// This script sets the custom parameter adgrpname (_adgrpname) for all ad groups in the account.
// This parameter contains the ad group name transformed into a URL-compatible format removing special characters and truncating for brevity
// and can be used in the tracking template or final URL suffix.
// Before applying: Ensure that this parameter is not used for another purpose to avoid
// overwriting important information. Also, check that there isn’t an ad group parameter
// with an empty value, as this can cause errors.
// [Schedule] The script should be scheduled to run regularly, e.g., every hour, to add
// parameters to new ad groups. It is also good to manually run it after adding new ad groups.
// [Warning] As of October 27, 2024: The script does not work for all campaign types,
// including Demand Gen and Video. These need to be updated manually using Google Ads Editor.
// Remember: Changing an ad group name will leave the old names in historical data in Analytics.
// Therefore, avoid changing ad group names.
function main() {
processAccount();
}
function processAccount() {
// Process all campaign types (including Search, Display, Shopping, Performance Max)
try {
var campaignIterator = AdsApp.campaigns().get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
processAdGroups(campaign);
}
} catch (e) {
Logger.log('Search Campaigns encountered an issue: ' + e.message);
}
// Optional: Not-Applicable Shopping Campaigns
// var shoppingCampaignIterator = AdsApp.shoppingCampaigns().get();
// while (shoppingCampaignIterator.hasNext()) {
// var campaign = shoppingCampaignIterator.next();
// processAdGroups(campaign);
// }
// Optional: Not-Applicable
// var pmaxCampaignIterator = AdsApp.performanceMaxCampaigns().get();
// while (pmaxCampaignIterator.hasNext()) {
// var campaign = pmaxCampaignIterator.next();
// processAssetGroups(campaign);
// }
// Optional: Attempt to process Demand Gen campaigns with error handling
// try {
// var demandGenCampaignIterator = AdsApp.demandGenCampaigns().get();
// while (demandGenCampaignIterator.hasNext()) {
// var campaign = demandGenCampaignIterator.next();
// processAdGroups(campaign);
// }
// } catch (e) {
// Logger.log('Demand Gen campaigns may not be supported in this account: ' + e.message);
// }
}
function processAdGroups(campaign) {
var adGroupIterator = campaign.adGroups().get();
while (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var adGroupName = adGroup.getName(); // Get the original name
// More aggressive cleaning: Remove anything that is NOT a letter, number, underscore, or hyphen and enforcing all lowercase.
var cleanedAdGroupName = adGroupName.replace(/[^a-zA-Z0-9_\-]/g, '').toLowerCase();
try {
var modifiedAdGroupName = encodeURIComponent(cleanedAdGroupName);
// Truncate if necessary (but 250 chars is usually enough)
if (modifiedAdGroupName.length > 250) {
Logger.log('Warning: Encoded name for "' + adGroupName + '" truncated.');
modifiedAdGroupName = modifiedAdGroupName.substring(0, 250);
}
var customParameters = adGroup.urls().getCustomParameters() || {};
customParameters['adgrpname'] = modifiedAdGroupName;
adGroup.urls().setCustomParameters(customParameters);
Logger.log('Updated ad group "' + adGroupName + '" (cleaned: "' + cleanedAdGroupName + '") with custom parameter adgrpname=' + modifiedAdGroupName);
} catch (e) {
Logger.log('ERROR encoding ad group name "' + adGroupName + '": ' + e.message);
// Optionally, you could skip this ad group or try a different cleaning strategy here.
}
}
}
Script #3 - Dynamic UTM Deployment to include and apply Campaign Name(s) and Ad Group Name(s) via FinalURL Suffix on all Search/Display Campaigns Only
// This script sets the following URL suffix for all campaigns in the account dynamically populating both campaign and adgroup names under UTM_Campaign:
// utm_source=google&utm_medium=cpc&utm_campaign={_campaign}_{_adgrpname}&utm_id={campaignid}&utm_term={keyword}_{matchtype}&utm_content={creative}&utm_source_platform=google+ads
// (If you want a different suffix, modify the script below.)
// Note: For this to work, custom campaign parameters (campaign) and custom ad group parameters (adgrpname) need to be set separately using a different script or manually.
// Warning: Before applying this, remove similar tracking templates if you are using them ({lpurl}?utm_source=google&utm_medium...) to avoid tag duplication.
// Check this for Account and Ad Group Settings as well!
// For account-level settings: Also set this suffix in the Google Ads panel (currently the only option).
// Scheduling: The script should be scheduled to run regularly, e.g., every hour, to add parameters to new campaigns.
// You can also manually run it after adding new campaigns.
// Warning (as of October 27, 2024): The script does not work for certain types of campaigns like Demand Gen and Video campaigns. These need to be updated manually using Google Ads Editor.
// Original Credit to © ADEQUATE www.adequate.digital 2024
// NOTE: This script is part of a batch of 3-Scripts necessary for successful deployment as-is.
// More info:
https://adequate.digital/en/utms-for-google-ads-campaigns/function main() {
// Process standard campaigns (Search and Display) with error handling
try{
var campaignIterator = AdsApp.campaigns().get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
processCampaign(campaign);
}
}
catch (e) {
Logger.log('Search Campaigns encountered an issue: ' + e.message);
}
//Optional: Process Shopping campaigns with error handling
// var shoppingCampaignIterator = AdsApp.shoppingCampaigns().get();
// while (shoppingCampaignIterator.hasNext()) {
// var campaign = shoppingCampaignIterator.next();
// processCampaign(campaign);
// }
//Optional: Attempt to process Performance Max campaigns with error handling
try {
var pmaxCampaignIterator = AdsApp.performanceMaxCampaigns().get();
while (pmaxCampaignIterator.hasNext()) {
var campaign = pmaxCampaignIterator.next();
processCampaign(campaign);
}
}
catch (e) {
Logger.log('PMAX Campaigns encountered an issue: ' + e.message);
}
// Optional: Attempt to process Demand Gen campaigns with error handling
// try {
// var demandGenCampaignIterator = AdsApp.demandGenCampaigns().get();
// while (demandGenCampaignIterator.hasNext()) {
// var campaign = demandGenCampaignIterator.next();
// processCampaign(campaign);
// }
// } catch (e) {
// Logger.log('Demand Gen campaigns may not be supported in this account: ' + e.message);
// }
}
function processCampaign(campaign) {
// Set the Final URL Suffix at the Campaign-level using Campaign Custom Parameter for Campaign Names and Ad Group Custom Parameter for Ad Group Names along with Ad Search Keyword & Matchtype
var campaignFinalUrlSuffix = "utm_source=google&utm_medium=cpc&utm_campaign={_campaign}_{_adgrpname}&utm_id={campaignid}&utm_term={keyword}_{matchtype}&utm_content={creative}&utm_source_platform=google+ads";
campaign.urls().setFinalUrlSuffix(campaignFinalUrlSuffix);
Logger.log('Updated campaign "' + campaign.getName() + '" with campaign-level Final URL Suffix.');
}
Regards,
On Friday, January 31, 2025 at 2:08:07 AM UTC-6 Search Engine Wings wrote: