Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save siliconvallaeys/a579409e0f94d8269744d6460b9cb466 to your computer and use it in GitHub Desktop.
Save siliconvallaeys/a579409e0f94d8269744d6460b9cb466 to your computer and use it in GitHub Desktop.
Add search terms and category labels from Performance Max campaigns on Google Ads to a Google spreadsheet automatically
function main() {
/******************************************
* PMax Search Terms Report
* @version: 1.0
* @authors: Frederick Vallaeys (Optmyzr)
* -------------------------------
* Install this script in your Google Ads account (not an MCC account)
* to generate a spreadsheet containing the search terms in your Performance Max campaigns.
* The spreadsheet also includes data about category labels (groupings of search terms).
* Metrics include conversion value, conversions, clicks, and impressions
* --------------------------------
* v2 (Mar 14, 2024): adds a new column that says if the search terms exists as a keyword in a search campaign. adds better error handling.
* --------------------------------
* For more PPC tools and scripts, visit www.optmyzr.com.
******************************************/
var minImp = 10; // Limit the output to only items with at least this many impressions
var spreadsheetUrl = ""; // leave blank to generate a new spreadsheet or add your own URL to overwrite the data in an existing spreadsheet
var reportLastNDays = 30; // The number of days to include in the report
var EMAILADDRESS = ""; // enter your email address
// Don't edit below this line unless you know how to write scripts
//-----------------------------------------------------------------
let pmaxOnly = 1;
let allCategoryLabels = [
[
"Campaign Name",
"Category Label",
"Conv Val",
"Conv",
"Clicks",
"Imp",
],
];
let allSearchTerms = [
[
"Campaign Name",
"Category Label",
"Subcat",
"Search Term",
"Conv Val",
"Conv",
"Clicks",
"Imp",
"Exists as Keyword?",
],
];
let keywordsList = getAllSearchCampaignKeywords();
var dateRange = getDateRange(reportLastNDays);
try {
let baseQuery = `
SELECT
campaign.id,
campaign.name,
metrics.clicks,
metrics.impressions,
metrics.conversions,
metrics.conversions_value
FROM campaign
WHERE campaign.status != 'REMOVED'
AND metrics.impressions > 0
AND segments.date BETWEEN ${dateRange}
AND metrics.impressions >= ${minImp}
`;
// Modify the SQL query if pmaxOnly is true
if (pmaxOnly) {
baseQuery +=
" AND campaign.advertising_channel_type = 'PERFORMANCE_MAX' ";
}
baseQuery += "ORDER BY metrics.conversions DESC";
let campaignIdsQuery = AdsApp.report(baseQuery);
let rows = campaignIdsQuery.rows();
while (rows.hasNext()) {
let campaignRow = rows.next();
let campaignId = campaignRow["campaign.id"];
Logger.log(
campaignRow["campaign.id"] + " " + campaignRow["campaign.name"]
);
// Search Labels Report
let categoryLabelsQuery = `
SELECT
campaign_search_term_insight.category_label,
campaign_search_term_insight.id,
metrics.clicks,
metrics.impressions,
metrics.conversions,
metrics.conversions_value
FROM
campaign_search_term_insight
WHERE
segments.date BETWEEN ${dateRange}
AND campaign_search_term_insight.campaign_id = '${campaignId}'
AND metrics.impressions >= ${minImp}
ORDER BY
metrics.conversions DESC
`;
let categoryLabelsQueryResult = AdsApp.report(categoryLabelsQuery);
let categoryLabelsResults = categoryLabelsQueryResult.rows();
while (categoryLabelsResults.hasNext()) {
let categoryLabelsRow = categoryLabelsResults.next();
let categoryLabelId =
categoryLabelsRow["campaign_search_term_insight.id"];
//Logger.log(categoryLabelId + ". " + categoryLabelsRow['campaign_search_term_insight.category_label'] + " " + categoryLabelsRow['metrics.impressions']);
allCategoryLabels.push([
campaignRow["campaign.name"],
categoryLabelsRow[
"campaign_search_term_insight.category_label"
],
categoryLabelsRow["metrics.conversions_value"].toFixed(2),
categoryLabelsRow["metrics.conversions"].toFixed(1),
categoryLabelsRow["metrics.clicks"],
categoryLabelsRow["metrics.impressions"],
]);
// Search Terms Report
let searchTermsQuery = `
SELECT
metrics.clicks,
metrics.impressions,
metrics.conversions,
metrics.conversions_value,
segments.search_term,
segments.search_subcategory
FROM
campaign_search_term_insight
WHERE
segments.date BETWEEN ${dateRange}
AND campaign_search_term_insight.campaign_id = '${campaignId}'
AND campaign_search_term_insight.id = '${categoryLabelId}'
`;
let searchTermsQueryResult = AdsApp.report(searchTermsQuery);
let searchTermsResults = searchTermsQueryResult.rows();
// Inside the loop where search terms are being processed
while (searchTermsResults.hasNext()) {
let searchTermsRow = searchTermsResults.next();
if (searchTermsRow["metrics.impressions"] >= minImp) {
let searchTermText = searchTermsRow[
"segments.search_term"
].toLowerCase(); // Normalize search term text for comparison
let existsAsKeyword = keywordsList[searchTermText]
? "Yes"
: "No";
allSearchTerms.push([
campaignRow["campaign.name"],
categoryLabelsRow[
"campaign_search_term_insight.category_label"
],
searchTermsRow["segments.search_subcategory"],
searchTermsRow["segments.search_term"],
searchTermsRow["metrics.conversions_value"].toFixed(
2
),
searchTermsRow["metrics.conversions"].toFixed(1),
searchTermsRow["metrics.clicks"],
searchTermsRow["metrics.impressions"],
existsAsKeyword, // New column indicating if the search term exists as a keyword
]);
}
}
}
}
if (!spreadsheetUrl) {
var ss = SpreadsheetApp.create("PMax Search Terms", 10000, 20);
var spreadsheetUrl = ss.getUrl();
} else {
var ss = SpreadsheetApp.openByUrl(spreadsheetUrl);
}
let categoriesSheet = ss.getSheetByName("categories")
? ss.getSheetByName("categories").clear()
: ss.insertSheet("categories");
if (allCategoryLabels.length > 1) {
// Check if there's more than just the header row
categoriesSheet
.getRange(
1,
1,
allCategoryLabels.length,
allCategoryLabels[0].length
)
.setValues(allCategoryLabels);
}
let termsSheet = ss.getSheetByName("terms")
? ss.getSheetByName("terms").clear()
: ss.insertSheet("terms");
if (allSearchTerms.length > 1) {
// Check if there's more than just the header row
termsSheet
.getRange(1, 1, allSearchTerms.length, allSearchTerms[0].length)
.setValues(allSearchTerms);
}
var subject = "PMax Search Terms Report Ready";
var body =
"The PMax Search Terms Report has been generated and is available at: " +
spreadsheetUrl +
"\n\nReport covers the last " +
reportLastNDays +
" days." +
"\n\nThis is an automated email sent by Google Ads Script.";
} catch (e) {
Logger.log("Error: " + e.message);
var subject = "PMax Search Terms Report Failed";
var body =
"The PMax Search Terms Report encountered an error: " +
e +
"\n\nThis is an automated email sent by Google Ads Script.";
}
Logger.log("spreadsheet: " + spreadsheetUrl);
// Send the email
var recipientEmail = EMAILADDRESS;
MailApp.sendEmail(recipientEmail, subject, body);
}
// function to get date range
function getDateRange(numDays) {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(endDate.getDate() - numDays);
const format = (date) =>
Utilities.formatDate(
date,
AdsApp.currentAccount().getTimeZone(),
"yyyyMMdd"
);
return `${format(startDate)} AND ${format(endDate)}`;
}
function getAllSearchCampaignKeywords() {
let keywordsList = {};
let keywordIterator = AdsApp.keywords()
.withCondition("Status = ENABLED")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.get();
while (keywordIterator.hasNext()) {
let keyword = keywordIterator.next();
let keywordText = keyword.getText().toLowerCase(); // Normalize keyword text for comparison
keywordsList[keywordText] = true; // Use a map for efficient lookup
}
return keywordsList;
}
@mcarter024
Copy link

I'm getting the same error: "Error: Could not read from Google Ads"

@siliconvallaeys
Copy link
Author

@aavolo it seems you may have attached an image of the error but that didn't come through. Can you email it to me or Optmyzr as it may help us track down the problem?

The error appears not to be tied to a particular line in the script and it sounds like some people are getting it after a campaign name is printed to the logs. This would indicate a problem on the Google side rather than with the script itself. I'll try to find a solution...

@YNS-Solutions
Copy link

I am also getting the "Error: Could not read from Google Ads" error

@RozRoyal
Copy link

RozRoyal commented Apr 29, 2024

I'll try to find a solution...

Any update on a solution?

@jjod92
Copy link

jjod92 commented May 22, 2024

I am also getting the "Error: Could not read from Google Ads" error

@clivedavy
Copy link

Hi, thanks for the code, should prove very useful. Hate not being able to see what search terms are triggering ads. I've added the code an it seems to run fine with out errors, but I'm not seeing any data in the Google Sheet. My PMax has been running for 2 days and received 72 clicks. Should I expect to see data from this?

Thanks for your help.

@MusaFormazione
Copy link

Hi, I set up the script a few weeks ago and it worked until this latest update. I received this notification "PMax search terms report failed". Do you know what's happening? Could you help me?
Thanks for your help.

@ppcsolutionsca
Copy link

I love this script. Thanks so much Frederick Vallaeys.

I've tried it on three accounts. On the first account, it works great. On the other two accounts I get the same errors the others have noted above.

I do not believe this is the script's fault, because when I go to download (export) the report manually using the insights tab in Google Ads, Google refuses to process that as well. I'm going to open a case with support about the account failing but am not optimistic that they will be helpful.

@OlenaTimchenko
Copy link

Thank you so much for that script! I will share it at my channel with direct link to Github and author mention.

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