I’m constantly updating our client’s Google Ad PPC campaigns with fresh content (mostly headlines and descriptions) to fuel Google’s AI with more variations in order to improve overall campaign performance.
So, I set out on a quick experiment to see how fresh content can be generated with the help of ChatGPT’s infinite wisdom ; ) .. It turned out that it generated super relevant content. By relevant I mean it was pretty much what I would of added after doing keyword volume and competition analysis and a few other steps involved in creating high performing content.
Below are the steps that I took in order to develop a reusable Google Ad Script that will generate fresh headlines and descriptions for an existing campaign. This same script can be reused with any Google Ad account. Here are the exact steps I took:
1. Find a script (javascipt in this case) that would communicate though OpenAI’s API.
After a little searching I found a nice script (view at end) that would connect and communicate with OpenAI(ChatGPT). I then saved this script to my handy text editor. This tiny 200~ line script does a bunch of awesome tasks each time it is run within Google Ads. The script’s tweak-able variables looked like the following:
// If Blank script will create a new Google sheet everytime it runs.
var SS_URL = '';
// Name of the tab in the Google sheet.
var TAB_NAME = 'RSA';
// Flag to decide if the script checks only ads in active campaigns and active ad groups
var INCLUDE_PAUSED = false;
// only include ads with this many or fewer headlines on the output spreadsheet (defaults to 15)
var MAX_HEADLINES = 15;
// only include ads with this many or fewer descriptions on the output spreadsheet (defaults to 4)
var MAX_DESCRIPTIONS = 4;
// Multiple emails can be added sepearated by comma (,)
// Used for access to spreadsheet and for sending email
var EMAIL = '';
// Set to true if you want to recieve the report on Email.
var SEND_EMAIL = false;
var OPEN_AI_API_KEY = ''; // get your own API key at https://platform.openai.com/account/api-keys
var GPT_MODEL = 'gpt-3.5-turbo';
2. Then, I created an API key over at OpenAI within my $20/mo OpenAI account.
You will need an OpenAI account in order to create the API key. Within minutes, I had my API key from my business account. The API key allows the script to use ChatGPT (3.5 in this case) to research and provide fresh headlines and descriptions relative to where the script is uploaded. This keeps the content relative to existing settings and ads within the Google Ads account. Here is a screenshot of the API Key that was generated:
3. Add the Open AI API Key into the script within Google Ad Scripts in Tools/Settings.
After adding the script with OpenAI’s API key I set the script to run right away within the account. You can also set it to run at any interval(hourly, daily and etc) that works best for your needs. I’m impatient and wanted to see if this baby would work from the getgo. So, I let ‘er rip(run).
I hit Run and within 40 seconds, the script added all content to a Google Sheet as seen below in green box:
There is a way for it to automatically insert the new headlines and descriptions to the campaign. Since this was a live campaign with a paying client I was not willing to risk screwing up the whole account because of my curiosity. Although, you can always undo any actions with Google Ads just in case you want to get a little crazy…
4. The script ran and I was provided with a link to the Google Sheet with the “goods”.
This pulled my whole campaign and relative assets down to a nice logical breakdown of all campaigns, ad groups and headlines. The fresh content was highlighted in green so that I could quickly copy/paste into my live campaign. Here is how it looked in the Google Sheet:
5. New Headlines and Descriptions were entered in Google Ads.
After adding everything and making sure there was no weirdness going on, I simply submitted campaigns for approval. Within a minute, my ads were approved and I was off and running with my fresh content for my client’s campaign.
In conclusion, Google Ad Scripts combined with Generative AI can be a powerful tool to create fresh content for PPC ads. It allows for an efficient way to add engaging content to your campaigns while keeping up with the ever-changing landscape of keyword trends/research and all of that other fun stuff. Contact Xblu today to learn more about how you can utilize the best of both human and generative ai tools to boost your Google Ad campaigns.
By Charlie B.
Here is complete script that you can use. ◔̯◔
/******************************************
* RSA Report
* @version: 3.0
* @authors: Naman Jindal (Optmyzr), Frederick Vallaeys (Optmyzr)
* -------------------------------
* Install this script in your Google Ads account (not an MCC account)
* to generate a Google Sheet with a list of all your responsive search ads
* and their headlines and descriptions.
* For RSAs that are not using the maximum number of allowed variations,
* this script will suggest new variations for headlines and descriptions
* using the OpenAI GPT API.
* The resulting sheet can be bulk uploaded back into Google Ads.
* --------------------------------
* For more PPC tools, visit www.optmyzr.com.
******************************************/
// If Blank script will create a new Google sheet everytime it runs.
var SS_URL = '';
// Name of the tab in the Google sheet.
var TAB_NAME = 'RSA';
// Flag to decide if the script checks only ads in active campaigns and active ad groups
var INCLUDE_PAUSED = false;
// only include ads with this many or fewer headlines on the output spreadsheet (defaults to 15)
var MAX_HEADLINES = 15;
// only include ads with this many or fewer descriptions on the output spreadsheet (defaults to 4)
var MAX_DESCRIPTIONS = 4;
// Multiple emails can be added sepearated by comma (,)
// Used for access to spreadsheet and for sending email
var EMAIL = '';
// Set to true if you want to recieve the report on Email.
var SEND_EMAIL = false;
var OPEN_AI_API_KEY = ''; // get your own API key at https://platform.openai.com/account/api-keys
var GPT_MODEL = 'gpt-3.5-turbo';
// Do not edit anything below this line
function main() {
var output = [[
'Account ID', 'Account Name', 'Campaign', 'Ad Group', 'Ad ID', '# Headlines', '# Descriptions', 'Ad Strength',
'Headline 1', 'Headline 2', 'Headline 3', 'Headline 4', 'Headline 5', 'Headline 6', 'Headline 7', 'Headline 8', 'Headline 9',
'Headline 10', 'Headline 11', 'Headline 12', 'Headline 13', 'Headline 14', 'Headline 15',
'Description Line 1', 'Description Line 2', 'Description Line 3', 'Description Line 4'
]];
var columCount = output[0].length;
var backgroupHeader = [];
while(backgroupHeader.length < columCount) {
backgroupHeader.push('#ffffff');
}
var backgrounds = [backgroupHeader];
var accId = AdsApp.currentAccount().getCustomerId(),
accName = AdsApp.currentAccount().getName();
var query = [
'SELECT campaign.name, ad_group.name, ad_group_ad.ad.id, ad_group_ad.ad_strength,',
'ad_group_ad.ad.responsive_search_ad.headlines, ad_group_ad.ad.responsive_search_ad.descriptions',
'FROM ad_group_ad WHERE ad_group_ad.ad.type = RESPONSIVE_SEARCH_AD AND metrics.impressions >= 0',
INCLUDE_PAUSED ? '' : 'AND ad_group_ad.status = ENABLED AND campaign.status = ENABLED and ad_group.status = ENABLED',
'AND segments.date DURING LAST_7_DAYS'
].join(' ');
var rows = AdsApp.report(query).rows();
while(rows.hasNext()) {
var row = rows.next();
var headlines = row['ad_group_ad.ad.responsive_search_ad.headlines'];
var headlineCount = headlines.length;
var descriptions = row['ad_group_ad.ad.responsive_search_ad.descriptions'];
var descriptionCount = descriptions.length;
if(headlineCount > MAX_HEADLINES || descriptionCount > MAX_DESCRIPTIONS) { continue; }
var out = [
accId, accName, row['campaign.name'], row['ad_group.name'], row['ad_group_ad.ad.id'],
headlineCount, descriptionCount, row['ad_group_ad.ad_strength']
];
var bgRow = ['#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff'];
var headlinesText = [];
for(var z in headlines) {
headlinesText.push(headlines[z].text);
bgRow.push('#ffffff');
}
var diff = 15 - headlinesText.length;
var autoHeadlines = [];
if(diff > 0) {
autoHeadlines = generateTextOpenAI('Find '+diff+' more ad headlines under 30 characters that are similar to these:n', headlinesText);
}
if(autoHeadlines.length) {
headlinesText = headlinesText.concat(autoHeadlines);
for(var i = 0; i < autoHeadlines.length; i++) {
bgRow.push('#d9ead3');
}
}
while(headlinesText.length < 15) {
headlinesText.push('');
bgRow.push('#fffff')
}
while(headlinesText.length > 15) {
headlinesText.pop();
bgRow.pop();
}
var descriptionsText = [];
for(var z in descriptions) {
descriptionsText.push(descriptions[z].text);
bgRow.push('#ffffff');
}
var diff = 4 - descriptions.length;
var autoDescriptions = [];
if(diff > 0) {
autoDescriptions = generateTextOpenAI('Find '+diff+' more ad descriptions under 90 characters that are similar to these:n', descriptionsText);
}
if(autoDescriptions.length) {
descriptionsText = descriptionsText.concat(autoDescriptions);
for(var i = 0; i < autoDescriptions.length; i++) {
bgRow.push('#d9ead3');
}
}
while(descriptionsText.length < 4) {
descriptionsText.push('');
bgRow.push('#ffffff');
}
while(descriptionsText.length > 4) {
descriptionsText.pop();
bgRow.pop();
}
out = out.concat(headlinesText).concat(descriptionsText);
backgrounds.push(bgRow);
output.push(out);
}
if(!SS_URL) {
var ss = SpreadsheetApp.create(accName + ': RSA Report');
SS_URL = ss.getUrl();
if(EMAIL) {
ss.addEditors(EMAIL.split(','));
}
}
Logger.log('Report URL: ' + SS_URL);
var ss = SpreadsheetApp.openByUrl(SS_URL);
var tab = ss.getSheetByName(TAB_NAME);
if(!tab) {
tab = ss.getSheetByName('Sheet1');
if(!tab) {
tab = ss.insertSheet(TAB_NAME);
} else {
tab.setName(TAB_NAME)
}
}
tab.clearContents();
tab.setFrozenRows(1);
tab.getRange(1,1,output.length,output[0].length).setValues(output).setBackgrounds(backgrounds).setFontFamily('Calibri');
if(EMAIL && SEND_EMAIL) {
MailApp.sendEmail(EMAIL, accName + ' RSA Report is ready', 'Report is available at below link:n'+SS_URL);
}
}
function getGoogleAdsFormattedDate(d, format){
var date = new Date();
date.setDate(date.getDate() - d);
return Utilities.formatDate(date,AdsApp.currentAccount().getTimeZone(),format);
}
function generateTextOpenAI(question, texts) {
//texts.pop();
var prompt = question + texts.join('n');
var messages= [
{"role": "user", "content": prompt}
];
var payload = {
"model": GPT_MODEL,
"messages": messages
};
var httpOptions = {
"method" : "POST",
"muteHttpExceptions": true,
"contentType": "application/json",
"headers" : {
"Authorization" : 'Bearer ' + OPEN_AI_API_KEY
},
'payload': JSON.stringify(payload)
};
//Logger.log(JSON.stringify(payload));
var response = JSON.parse(UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', httpOptions));
var choices = response['choices'];
var texts = [];
if(choices[0] && choices[0]['message']) {
var output = choices[0]['message']['content'].split('n');
for(var z in output) {
if(!output[z].trim()) { continue; }
var parts = output[z].split('. ');
if(parts.length > 1) {
parts.shift();
}
texts.push(parts.join('. '));
}
}
return texts;
}