Applying price extensions in accounts of larger ecommerce parties is a time-consuming task. When prices are adjusted in the shop, this must be entered manually in a price extension. Or you choose to partially automate this through a CSV upload, but that still means that you have to upload a new sheet every day. Or multiple times a day.
With the script below you can easily check existing price extensions. When prices change in the shop, the script also applies the new price in the price extension. You can use the script for price extensions with product prices, categories and brands. Through a link with Google Merchant Center (GMC), the script always has the most current prices to be able to apply in the price extensions.
Products, categories and brands
The application of the script to product prices is simple; the last price is searched in GMC based on the product URL. When a product has a different price, it is adjusted in the price extension. When a product is no longer in GMC, the product is removed from the price extension.
When you put prices of brands from the shop in a price extension, the script searches for all products with this brand and then takes the lowest price from GMC. This means you always have a current 'from price'. Make sure that the brand name in the title of the price item of the extension matches the brand name in GMC. The same applies to categories as to brands. When a category in the title of a price item matches a field from GMC (you can determine this field in the script itself), the script looks for the lowest price to include in the price extension.
When an item must be removed from a price extension, but the price extension has only three items, an end date is set on the price extension. You will also receive an e-mail stating that a price extension has been paused, so that you know that you have to create a new one or add a new item to the existing price extension.
Make sure you run this script extensively in preview mode before use, to see what changes would be made. Check if the prices match with the Google Merchant Center.
Settings
With the following settings you can apply the script as desired:
LOG: Specify whether the script should report the intermediate steps, by setting the value to 'true'.
MERCHANT_ID: Enable the advanced API 'Shopping Content' and enter the Merchant Center ID here. Ensure that the user running the script also has access to GMC.
INCLUDE_PRODUCT_TIERS: Set to 'true' when you want to check price extensions with product prices, otherwise set to 'false'.
INCLUDE_BRANDS: Set to 'true' when you want to check price extensions with brand prices, otherwise set them to 'false'.
INCLUDE_CATEGORIES: Set to 'true' if you want to check price extensions with category prices, otherwise set them to 'false'.
CATEGORY_FEEDFIELD: If you want to check price extensions with category prices, you can indicate here which field in GMC contains the category.
EMAIL_RECIPIENTS: When price extensions are paused, an email is sent to these recipient(s).
EMAIL_SUBJECT: State the subject of the email.
EMAIL_CONTENT: Content of the email, which is supplemented with notifications from the account.
The script
// Copyright 2021
//
// Created By: Tibbe van Asten
// for Increase B.V.
//
// Created 15-08-2019
// Last update: 08-10-2021 - Changes productType to productTypes
//
// ABOUT THE SCRIPT
// This script will check all price-extensions for up-to-date prices
// When prices have changed, the script will update the extension.
//
////////////////////////////////////////////////////////////////////
var config = {
LOG : true,
// Connect Merchant Center. Add user to MC that runs this script.
// Also enable Advanced API 'Shopping content'.
MERCHANT_ID : "123456789",
INCLUDE_PRODUCT_TIERS : true,
INCLUDE_BRANDS : true,
INCLUDE_CATEGORIES : true,
// Specify the field in Merchant Center that contains your product-category
CATEGORY_FEEDFIELD : "productTypes",
// An email will be send when price extensions are paused
// Add multiple emailaddresses separated by a comma.
EMAIL_RECIPIENTS : "example@example.com",
EMAIL_SUBJECT : "Update Price Extensions in " + AdsApp.currentAccount().getName(),
EMAIL_CONTENT : "The following changes are made in price extensions:<br /><br />"
}
////////////////////////////////////////////////////////////////////
function main(){
// Connect Merchant Center and collect active brands and products
var products = connectMerchant();
var newPriceItems = [];
// Set builders
var priceItemBuilder = AdsApp.extensions().newPriceItemBuilder();
var priceBuilder = AdsApp.extensions().newPriceBuilder();
var priceIterator = AdsApp
.extensions()
.prices()
.withCondition("Status = ENABLED")
.withCondition("PolicyApprovalStatus IN ['APPROVED','APPROVED_LIMITED']")
.get();
while(priceIterator.hasNext()){
var price = priceIterator.next();
var priceItems = price.getPriceItems();
var numItems = priceItems.length;
// Productprices are checked, when the price extension includes products
// and config is set to include product tiers
if(config.INCLUDE_PRODUCT_TIERS === true && !price.getEndDate() && price.getPriceType() == "PRODUCT_TIERS"){
for(var i = 0;i < priceItems.length;i++){
var priceItem = priceItems[i];
var currentPrice = checkPrice(products, priceItem.getFinalUrl());
numItems = checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder);
} // priceItemIterator
} // PRODUCT_TIERS
// Brandprices are checked, when the price extension includes brands
// and config is set to include brand prices
if(config.INCLUDE_BRANDS === true && !price.getEndDate() && price.getPriceType() == "BRANDS"){
for(var i = 0;i < priceItems.length;i++){
var priceItem = priceItems[i];
var currentPrice = checkBrandPrice(products, priceItem.getHeader());
numItems = checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder);
} // priceItemIterator
} // BRANDS
// ProductCategories are checked, when the price extension includes categories
// and config is set to include product categories
if(config.INCLUDE_CATEGORIES === true && !price.getEndDate() && price.getPriceType() == "PRODUCT_CATEGORIES"){
for(var i = 0;i < priceItems.length;i++){
var priceItem = priceItems[i];
var currentPrice = checkCategoryPrice(products, priceItem.getHeader());
numItems = checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder);
} // priceItemIterator
} // PRODUCT_CATEGORIES
} // priceIterator
sendEmail();
Logger.log("Thanks for using this custom script by Tibbe van Asten. Winning!");
} // function main
////////////////////////////////////////////////////////////////////
function connectMerchant(){
if(config.MERCHANT == "123456789"){
throw Error("Change the Merchant ID in the settings");
}
var pageToken;
var pageNum = 1;
var maxResults = 250;
var products = [];
do {
var productList = ShoppingContent.Products.list(config.MERCHANT_ID, {
pageToken: pageToken,
maxResults: maxResults
});
if (productList.resources) {
for (var i = 0; i < productList.resources.length; i++) {
// We'll only check for products that are in stock
if(productList.resources[i]["availability"] == "in stock"){
products.push(productList.resources[i]);
}
}
}
pageToken = productList.nextPageToken;
pageNum++;
} while (pageToken);
return products;
} // function connectMerchant()
////////////////////////////////////////////////////////////////////
function checkPrice(products, url){
var productPrice = 1000;
for (var i = 1; i < products.length; i++) {
// When the link in the feed is the same as the price-item url, the price is retrieved
if(products[i]["link"] == url) {
// When the saleprice is in the feed, this will be used. If not, the regular price is used.
if(products[i]["salePrice"]) {
var price = products[i]["salePrice"]["value"];
} else {
var price = products[i]["price"]["value"];
}
if(price < productPrice){
productPrice = price;
}
} // if statement url
} // for statement
if(productPrice != 1000){
return productPrice;
}
} // function checkPrice()
////////////////////////////////////////////////////////////////////
function checkBrandPrice(products, brand){
var brandPrice = 1000;
for (var i = 1; i < products.length; i++) {
// When the brand in the feed is the same as the price-item brand, the price is retrieved
if(products[i]["brand"] == brand) {
// When the saleprice is in the feed, this will be used. If not, the regular price is used.
if(products[i]["salePrice"]) {
var price = parseFloat(products[i]["salePrice"]["value"]);
} else {
var price = parseFloat(products[i]["price"]["value"]);
}
if(price < brandPrice){
brandPrice = price;
}
} // if statement brand
} // for statement
if(brandPrice != 1000){
return brandPrice;
}
} // function checkBrandPrice()
////////////////////////////////////////////////////////////////////
function checkCategoryPrice(products, category){
var categoryPrice = 1000;
for (var i = 1; i < products.length; i++) {
// When the brand in the feed is the same as the price-item productType, the price is retrieved
if(products[i][config.CATEGORY_FEEDFIELD] == category) {
// When the saleprice is in the feed, this will be used. If not, the regular price is used.
if(products[i]["salePrice"]) {
var price = parseFloat(products[i]["salePrice"]["value"]);
} else {
var price = parseFloat(products[i]["price"]["value"]);
}
if(price < categoryPrice){
categoryPrice = price;
}
} // if statement brand
} // for statement
if(categoryPrice != 1000){
return categoryPrice;
}
} // function checkCategoryPrice()
////////////////////////////////////////////////////////////////////
function checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder){
// If product is not found, the priceItem will be removed.
// When price only exists of 3 items, the price will be removed
if(currentPrice == null && numItems > 3){
priceItem.remove();
numItems--;
// Summary changes in mail
config.EMAIL_CONTENT += "Removed " + priceItem.getHeader() + " from price #" + price.getId() + "<br />";
if(config.LOG === true){
Logger.log("Removed " + priceItem.getHeader() + " from price #" + price.getId());
Logger.log(" ");
}
} // remove priceItem, not in feed
else if(currentPrice == null && numItems == 3){
var date = new Date(Utilities.formatDate(new Date(), 'Europe/Amsterdam', 'yyyy/MM/dd HH:mm').toString());
if(price.getEndDate() == null){
price.setEndDate({year: date.getYear(), month: date.getMonth()+1, day: date.getDate()})
price.setSchedules([{dayOfWeek: "MONDAY", startHour: 0, startMinute: 0, endHour: date.getHours(), endMinute: 0}]);
if(config.LOG === true){
Logger.log("Ended " + priceItem.getHeader() + " in #" + price.getId());
Logger.log(" ");
}
// Summary changes in mail
config.EMAIL_CONTENT += "Ended " + priceItem.getHeader() + " in #" + price.getId() + "<br />";
}
} // remove price, not in feed anymore
// If price from priceItem is not the same as the one in the feed
// we'll remove it.
if(priceItem.getAmount() != currentPrice && currentPrice != null){
Logger.log(priceItem.getHeader() + ". Current: " + priceItem.getAmount() + ", New: " + currentPrice);
// Add dummy item
var dummyPriceItemOperation = priceItemBuilder
.withHeader("Dummy")
.withDescription("Dummy")
.withAmount(10)
.withCurrencyCode(priceItem.getCurrencyCode())
.withUnitType("NONE")
.withFinalUrl("http://www.example.com/")
.build();
// Setting up new priceItem
var newPriceItemOperation = priceItemBuilder
.withHeader(priceItem.getHeader())
.withDescription(priceItem.getDescription())
.withAmount(parseFloat(currentPrice))
.withUnitType(priceItem.getUnitType())
.withCurrencyCode(priceItem.getCurrencyCode())
.withFinalUrl(priceItem.getFinalUrl())
.build();
var dummyPriceItem = dummyPriceItemOperation.getResult();
var newPriceItem = newPriceItemOperation.getResult();
// Adding dummyPriceitem and removing original
price.addPriceItem(dummyPriceItem);
priceItem.remove();
price.addPriceItem(newPriceItem);
if(config.LOG === true){
Logger.log("Renewed " + priceItem.getHeader());
Logger.log(" ");
}
// Summary changes in mail
config.EMAIL_CONTENT += "Renewed " + priceItem.getHeader() + "<br />";
} // Replace item with wrong price
// Remove dummy priceItem
var priceItems = price.getPriceItems()
for(var y = 0;y < priceItems.length;y++){
var priceItem = priceItems[y];
if(priceItem.getHeader() == "Dummy"){
priceItem.remove();
}
} // Remove dummy
return numItems;
} // function checkPriceItem()
////////////////////////////////////////////////////////////////////
function sendEmail() {
if(config.EMAIL_CONTENT != "The following changes are made in price extensions:<br /><br />"){
MailApp.sendEmail({
to: config.EMAIL_RECIPIENTS,
subject: config.EMAIL_SUBJECT,
htmlBody: config.EMAIL_CONTENT});
Logger.log(config.EMAIL_CONTENT);
}
} // function sendEmail()
Show whole script!
Loading Comments
The Experts
Tibbe van AstenTeam Lead Performance Marketing
Nils RooijmansWater Cooler Topics
Martijn KraanFreelance PPC Specialist
Bas BaudoinTeamlead SEA @ Happy Leads
Jermaya LeijenDigital Marketing Strategist
Krzysztof BycinaPPC Specialist from Poland
How about you?JOIN US!
Caring
Adsscripts.com is all about sharing knowledge. In the current market, PPC specialists like to keep their knowledge and experience to themselves. We're convinced that sharing knowledge can ensure that everyone gets better at their work. We want to change this by sharing our knowledge about scripts with everyone.
Do you also want to contribute? We are open to new ideas and feedback on everything you find on Adsscripts.com.