Updating Price Extensions

Automatically update price extensions for products, brands or categories when product prices are adjusted.

Start Now!
Updating Price Extensions
Updating Get started!

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 2019. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
//
// Created 15-08-2019
// Last update: 14-02-2019
//
// 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 : "productType",  
  
  // 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!
The Experts
Tibbe van Asten Head of PPC @ Increase
Nils Rooijmans Water Cooler Topics
Martijn Kraan Freelance PPC Specialist
Bas Baudoin Teamlead SEA @ Happy Leads
How about you? JOIN US!
Sharing Knowledge
Caring

Sharing Knowledge

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.

Contact us

Training &
Workshop
Contact us!
Adsscripts Training & Workshop