Leap Labelizer – V1.4
Last update: 14/03/2025
Leap Labelizer is a free performance bucketing tool, that help ecommerce businesses allocate their higher portion of ads spent to high potential products that are generating higher percentage of revenue.
It’s more than just a labelizer; it features an insightful dashboard that displays daily performance data for all shopping products, both product-wise and bucket-wise.
What’s in V1.4
- Comparison table of current and previous buckets
Performance Labels
It creates 6 performance based buckets.
- Elites
- Regulars
- Climbers
- Strugglers
- Ghosts
- Sleepers
Elites
Products way above the target.
Regulars
Products are meeting the target.
Climbers
Products are behind the target.
Strugglers
Products are way behind the target, aka budget busters.
Ghosts
Products that show up sometimes and have fewer clicks.
Sleepers
Products with zero clicks.
You don’t need to create a separate campaign for each label. Since elite and regular products will be fewer, combine them into a single campaign.
Combine Ghosts and climbers in a single campaign.
Rest, you know the drill.
The purpose is to analyze the spending and act accordingly without putting ourselves into panic mode.
You can use the script just for data analysis, without putting labels into campaigns.
How it works?
It has 2 sheets.
- Supplementary Feed
- Dashboard
Please create supplementary feed for yourself or use Google merchant Next template URL, if you don’t have any sup feed before Or Use your current supplementary feed URL.
Paste that supplementary feed URL in the configuration sheet B12 cell.
Duplicate this dashboard sheet. Link
Paste the Dashboard link to the script.
Schedule your script for daily,
Note: Schedule your script time before your sup feed in the merchant centre.
Once it’s scheduled, just monitor the dashboard only. It has everything there.
Before you run the script, set up your configuration sheet. Especially Feed Label.
How to use the script?
- Go to
- Google Ads
- Settings
- Bulk Actions
- Scripts
- New Script
- Paste the Leap Labelizer script
- Duplicate the sheet
- Paste the URL in the script
- Update your configurations sheet
- Authorize the script
- Run the script
Wait for data to show up in the dashboard.
If you like the script, please share in your network, so every PPCer could take a benefit from it.
If you want to see inside view dashboard template? Watch here
Script Code:
// Leap Labelizer V1.4 - 14 March 2025
// copy this template https://docs.google.com/spreadsheets/d/1XhMVqkRF4KC_PJYo94TDqs2g816hDnWJRffQ2DO0d2M/copy
var SPREADSHEET_URL = "PASTE_YOUR_SHEET_LINK_HERE"; // Paste the url here
// No updates area --------------------------------------------------------------------------------------
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
const configSheet = spreadsheet.getSheetByName('Configurations');
var feedLabel = configSheet.getRange('B7').getValue();
var productIDCase = configSheet.getRange('B11').getValue();
var SUPFEED_URL = configSheet.getRange('B12').getValue();
var breakevenRoas = configSheet.getRange('B2').getValue();
var ghostThreshold = configSheet.getRange('B4').getValue();
var daysAgo = configSheet.getRange('B5').getValue();
var numDaysToSkip = configSheet.getRange('B6').getValue();
var productTypeLevel = configSheet.getRange('B8').getValue();
var labelFirst = configSheet.getRange('B9').getValue();
var labelSecond = configSheet.getRange('B10').getValue();
const dateRange = getDateRange(numDaysToSkip, daysAgo);
let segFeedLabel = ' segments.product_feed_label ';
let segItemId = ' segments.product_item_id ';
let metImpressions = ' metrics.impressions ';
let metClicks = ' metrics.clicks ';
let metCtr = ' metrics.ctr ';
let metCostMicros = ' metrics.cost_micros ';
let metConversions = ' metrics.conversions ';
let metConversionsValue = ' metrics.conversions_value ';
let segProductTitle = ' segments.product_title ';
let segProductType = ` segments.product_type_l${productTypeLevel} `;
let segLabelFirst = ` segments.product_custom_attribute${labelFirst} `;
let segLabelSecond = ` segments.product_custom_attribute${labelSecond} `;
let segDate = ' segments.date ';
let campName = ' campaign.name ';
let whereFeedLabel = ` segments.product_feed_label = "${feedLabel}" `;
let whereDate = ` segments.date BETWEEN ${dateRange} `;
// New configuration variables
var excludeBrand = configSheet.getRange('B3').getValue(); // true/false
var excludeSale = configSheet.getRange('B13').getValue(); // true/false
var saleStartDate = formatYYYYMMDD(configSheet.getRange('C14').getValue()); //"20241224"; Format: YYYYMMDD
var saleEndDate = formatYYYYMMDD(configSheet.getRange('E14').getValue()); //"20241228"; Format: YYYYMMDD
function main(){
setDetails();
var products = getShoppingProducts(daysAgo);
pushToDashboard(products);
SUPFEED_URL ? pushToSupFeed(products) : Logger.log('Please provide your sup feed URL');
exportRawData(products);
}
// -----------------------------------------------------------------------------------------------------------------
function setDetails() {
var account = AdsApp.currentAccount();
var accountName = account.getName();
var dateText = formatDateRange(dateRange);
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getSheetByName('Dashboard');
sheet.getRange('C2').setValue(`${accountName} ${feedLabel}`);
sheet.getRange('C3').setValue(dateText);
}
// -----------------------------------------------------------------------------------------------------------------
function isBrandCampaign(campaignName) {
return campaignName.toLowerCase().includes('brand');
}
// -----------------------------------------------------------------------------------------------------------------
function isWithinSalePeriod(date) {
//let dateClean = date.replace(/-/g, '');
//return date >= saleStartDate && date <= saleEndDate;
// Ensure the date is stripped of dashes and converted to an integer
let cleanDate = parseInt(date.replace(/-/g, ''), 10);
let saleStart = parseInt(saleStartDate, 10); // Ensure numeric comparison
let saleEnd = parseInt(saleEndDate, 10); // Ensure numeric comparison
// Perform numeric comparison for accuracy
return cleanDate >= saleStart && cleanDate <= saleEnd;
}
// -----------------------------------------------------------------------------------------------------------------
function exportRawData(products) {
Logger.log("Exporting overtime data now");
let dataMetrics = [
segFeedLabel, segDate, segItemId, metImpressions, metClicks, metCtr,
metCostMicros, metConversions, metConversionsValue, segProductTitle,
segProductType, campName
];
let query = 'SELECT ' + dataMetrics.join(',') +
' FROM shopping_performance_view ' +
' WHERE ' + whereFeedLabel +
' AND ' + whereDate;
const report = AdsApp.report(query);
const rows = report.rows();
let processedData = []; // Array to store processed rows
while (rows.hasNext()) {
let row = rows.next();
let itemId = row['segments.product_item_id'];
let date = row['segments.date'];
let campaignName = row['campaign.name'];
// Skip if conditions match exclusion criteria
if (excludeBrand && isBrandCampaign(campaignName)) continue;
if (excludeSale && isWithinSalePeriod(date)) continue;
// Get leapLabel from the product data
let product = products.find(p => p[0] === itemId); // Find the product by itemId
let leapLabel = product ? product[7] : 'N/A';
// Process and store filtered data in array
let rowData = [
row['segments.product_feed_label'],
row['segments.date'],
row['segments.product_item_id'],
row['metrics.impressions'],
row['metrics.clicks'],
row['metrics.ctr'],
row['metrics.cost_micros'],
row['metrics.conversions'],
row['metrics.conversions_value'],
row['segments.product_title'],
row[`segments.product_type_l${productTypeLevel}`],
row['campaign.name'],
leapLabel // Include leapLabel
];
processedData.push(rowData);
}
// Add header row
const headerRow = [
'segments.product_feed_label', 'segments.date', 'segments.product_item_id',
'metrics.impressions', 'metrics.clicks', 'metrics.ctr', 'metrics.cost_micros',
'metrics.conversions', 'metrics.conversions_value', 'segments.product_title',
`segments.product_type_l${productTypeLevel}`, 'campaign.name', 'leapLabel'
];
processedData.unshift(headerRow); // Add header at the start of the array
Logger.log(`Total rows to be exported: ${processedData.length}`);
// Export to spreadsheet
const sheet = spreadsheet.getSheetByName('r_data');
sheet.clearContents(); // Clear existing data
sheet.getRange(1, 1, processedData.length, processedData[0].length).setValues(processedData);
Logger.log("All rows exported.");
}
// -----------------------------------------------------------------------------------------------------------------
function aggregateMetrics(rows) {
let productMetrics = {};
while (rows.hasNext()) {
let row = rows.next();
let itemId = row['segments.product_item_id'];
let date = row['segments.date'];
let campaignName = row['campaign.name'];
// Skip if conditions match exclusion criteria
if (excludeBrand && isBrandCampaign(campaignName)) continue;
if (excludeSale && isWithinSalePeriod(date)) continue;
if (!productMetrics[itemId]) {
productMetrics[itemId] = {
itemId: itemId,
productType: row[`segments.product_type_l${productTypeLevel}`],
title: row['segments.product_title'],
labelFirst: row[`segments.product_custom_attribute${labelFirst}`],
labelSecond: row[`segments.product_custom_attribute${labelSecond}`],
impressions: 0,
clicks: 0,
cost: 0,
conversions: 0,
conversionValue: 0
};
}
// Aggregate metrics
productMetrics[itemId].impressions += Number(row['metrics.impressions']);
productMetrics[itemId].clicks += Number(row['metrics.clicks']);
productMetrics[itemId].cost += Number(row['metrics.cost_micros'])/1000000;
productMetrics[itemId].conversions += Number(row['metrics.conversions']);
productMetrics[itemId].conversionValue += Number(row['metrics.conversions_value']);
}
return Object.values(productMetrics);
}
// -----------------------------------------------------------------------------------------------------------------
function getShoppingProducts(daysAgo){
// Fetching Data for processing
let dataMetrics = [segFeedLabel, segItemId, metImpressions, metClicks, metCtr, metCostMicros,
metConversions, metConversionsValue, segProductTitle, segProductType, segDate, campName, segLabelFirst, segLabelSecond];
var query = 'SELECT ' + dataMetrics.join(',') +
' FROM shopping_performance_view ' +
' WHERE ' + whereFeedLabel + ' AND ' + whereDate;
var products = [];
var report = AdWordsApp.report(query);
var rows = report.rows();
// Aggregate metrics after filtering
var aggregatedProducts = aggregateMetrics(rows);
// Process aggregated products and apply labels
for (let product of aggregatedProducts) {
var roas = product.conversionValue/product.cost;
if (isNaN(roas)) {
roas = 0;
}
var leapLabel = '';
if (product.clicks == 0) {
leapLabel = "Sleepers";
} else if (product.clicks <= ghostThreshold) {
leapLabel = "Ghosts";
} else if (roas >= breakevenRoas + 1) {
leapLabel = 'Elites';
} else if (roas >= breakevenRoas) {
leapLabel = 'Regulars';
} else if (roas >= breakevenRoas - 1) {
leapLabel = 'Climbers';
} else {
leapLabel = 'Strugglers';
}
var productTypeLeapLabel = product.productType + " " + leapLabel;
products.push([
product.itemId,
product.impressions.toString(),
product.clicks.toString(),
product.cost,
product.conversions.toString(),
product.conversionValue,
roas,
leapLabel,
product.labelSecond,
productTypeLeapLabel,
product.labelFirst,
product.productType,
product.title,
]);
}
return products;
}
// -----------------------------------------------------------------------------------------------------------------
// Pushing Products data to Dashboard sheet
function pushToDashboard(data){
var sheet = spreadsheet.getSheetByName('Leap Data');
sheet.clear();
let header = ['Item Id','Impressions','Clicks','Cost','Conversions','Conv. Value','ROAS',`Custom Label ${labelSecond}`, `Prev Custom Label ${labelSecond}` ,`Custom Label ${labelFirst}`, `Prev Custom Label ${labelFirst}` ,`Product Type ${productTypeLevel}`,'Title'];
data.unshift(header);
sheet.getRange(1, 1, data.length, header.length).setValues(data);
sheet.sort(4, false);
}
// -----------------------------------------------------------------------------------------------------------------
// Pushing sup feed data
function pushToSupFeed(data){
var data = data.slice(1);
var spreadsheet = SpreadsheetApp.openByUrl(SUPFEED_URL);
var sheet = spreadsheet.getActiveSheet();
let supFeedValues = data.map(item => [
formatItemId(item[0]), // Index 0 contains itemId
item[7], // Index 7 contains label
item[9] // Index 9 contains label2
]);
var supFeedHeader = [`id`, `custom label ${labelSecond}`, `custom label ${labelFirst}`];
supFeedValues.unshift(supFeedHeader);
sheet.clear();
sheet.getRange(1, 1, supFeedValues.length, supFeedHeader.length).setValues(supFeedValues);
}
// -----------------------------------------------------------------------------------------------------------------
function formatItemId(itemID) {
if (itemID) { // Short check for not null/undefined
if (productIDCase === 'Upper') {
return itemID.toUpperCase(); // Return itemID in uppercase
} else if (productIDCase === 'Small') {
return itemID.toLowerCase(); // Return itemID in lowercase
} else {
var parts = itemID.split('_');
if (parts.length >= 2) {
var countryCode = parts[1];
var countryCodeInUpperCase = countryCode.toUpperCase();
parts[1] = countryCodeInUpperCase;
return parts.join('_');
} else {
// If the itemID doesn't have the expected structure, return the original itemID
return itemID;
}
}
} else {
return ''; // Return an empty string if itemID is null or undefined
}
}
// -----------------------------------------------------------------------------------------------------------------
// function to get date range
function getDateRange(numDaysToSkip, numDaysToLookBack) {
const endDate = new Date();
endDate.setDate(endDate.getDate() - numDaysToSkip);
const startDate = new Date();
startDate.setDate(startDate.getDate() - numDaysToLookBack - numDaysToSkip + 1);
const format = date => Utilities.formatDate(date, AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd');
return `${format(startDate)} AND ${format(endDate)}`;
}
// -----------------------------------------------------------------------------------------------------------------
function formatDateRange(dateString) {
var dates = dateString.split(" AND ");
return dates.map(date => date.replace(/(\d{4})(\d{2})(\d{2})/, "$3-$2-$1")).join(" to ");
}
// -----------------------------------------------------------------------------------------------------------------
function formatYYYYMMDD(date) {
if (!date) return null;
// If date is already a string in YYYYMMDD format, return as is
if (typeof date === 'string' && date.match(/^\d{8}$/)) {
return date;
}
// Convert to Date object if it's not already
const dateObj = (date instanceof Date) ? date : new Date(date);
// Use Utilities.formatDate with our spreadsheet's timezone
const formattedDate = Utilities.formatDate(dateObj, spreadsheet.getSpreadsheetTimeZone(), 'yyyyMMdd');
return formattedDate;
}
// -----------------------------------------------------------------------------------------------------------------
// function to get date range
function getDateRange(numDaysToSkip, numDaysToLookBack) {
const endDate = new Date();
endDate.setDate(endDate.getDate() - numDaysToSkip);
const startDate = new Date();
startDate.setDate(startDate.getDate() - numDaysToLookBack - numDaysToSkip + 1);
const format = date => Utilities.formatDate(date, AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd');
return `${format(startDate)} AND ${format(endDate)}`;
}