Build and maintain Google Shopping campaigns

Use this script to build and maintain Google Shopping campaigns in your account.

Start Now!
Build and maintain Google Shopping campaigns
Build and Get started!

With large webshops it is impossible to manually keep all Google Shopping campaigns up-to-date. Often new products are added or products disappear from the inventory. In order to keep track of your Google Shopping campaigns, these products will have to be added or removed. That could be easier.

There are also various external tools to automatically create Google Shopping campaigns. But you can also do this with scripts. This script connects to your Google Merchant Center and then places all products in the correct campaign(s) and adgroup. The script does not create the campaigns, you will have to do that yourself. If you want to use priority between campaigns, for example for a split in brand and non-brand, you will also have to do this manually with external tools. The adgroups are created by the script.

Current inventory in Google Shopping

The script performs two tasks:

  1. Remove productgroups that are no longer in the Google Merchant Center.
  2. Add productgroups to the appropriate adgroups if they aren't already in Google Shopping.

The selection of the right campaign and adgroup can be based on values ​​from Google Merchant Center. You can also use a static value to select the right campaign. The script-example below uses both. Based on CAMPAIGN_SELECTOR, all Google Shopping campaigns are selected that have 'Bestsellers' in their name. Because CAMPAIGNNAME_FIELD is also set, the campaign must also contain the brand name of the product. For example, for an Apple Macbook, a campaign called "Shopping - Bestsellers - Apple" is selected because it contains both "Bestsellers" and the brand name of the product.

Within this campaign, the adgroup will be included that has the contents of Custom Label 0 in its name. If this ad group doesn't already exist, it will be created automatically.

Settings

  • LOG: Specify whether the script should report the intermediate steps by adjusting the value to 'true'.
  • MERCHANT_ID: The ID of your Merchant Center account. The user running the script in Google Ads, needs to be added as a user in Merchant Center. Also, enable the advanced API 'Shopping Content' in the script (find the button 'Advanced API's on the right top).
  • DEFAULT_BID: The Max. CPC that is filled in for each product.
  • TARGET_COUNTRY: Only select products from GMC for a specific country. Leave blank if you don't want to use this.
  • TARGET_LANGUAGE: Only select products from GMC for a specific language. Leave blank if you don't want to use this.
  • CAMPAIGN_SELECTOR: The campaign name that is selected for each GMC product must contain this value. Leave blank if you don't want to use this.
  • CAMPAIGNNAME_FIELD: Use a field from GMC to select a campaign. The campaign selected for a product must have the contents of this field in the name. An overview of all available fields: https://developers.google.com/shopping-content/reference/rest/v2/products
  • ADGROUPNAME_FIELD: Select the appropriate ad group based on a value from GMC. The ad group that contains the content of this field is selected per product.

Scheduling: If you have more than 500 products, you set them the first time for each hour. When all products are in the campaigns, change this to daily. Make sure you choose a time after updating your productfeed in Merchant Center.

The script
// Copyright 2021. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
//
// Created 15-04-2019
// Last update: 23-07-2021 Filter country & language improved
//
// ABOUT THE SCRIPT
// All products in Google Merchant Center are checked for existance
// in the Google Shopping campaigns. Based on your input, products
// will be put in the right campaign and adgroup.
// The script also checks all active productgroups in your campaigns
// and removes productgroups that are not in GMC anymore.
//
////////////////////////////////////////////////////////////////////

var config = {

  LOG : true,

  // Connect Merchant Center. Add user to GMC that runs this script.
  // Also enable Advanced API 'Shopping content'.
  MERCHANT_ID : "123456789",
  DEFAULT_BID : 0.3,

  TARGET_COUNTRY : "BE",
  TARGET_LANGUAGE : "nl",

  // To select a (set of) Shopping campaign(s), all names must contain the following
  // Leave empty when you only want to use a field from GMC to select a campaign.
  CAMPAIGN_SELECTOR : "[B] Shopping",

  // To specify the corresponding campaign and adgroup for each product,
  // we'll use fields from GMC. Define which field contains (part of) the
  // campaign- and adgroupname.
  // Available fieldnames: https://developers.google.com/shopping-content/reference/rest/v2/products

  // Leave empty when you only want to use the CAMPAIGN_SELECTOR to select a campaign.
  CAMPAIGNNAME_FIELD : "",

  // use productTypes, customLabel0 (or 1,2,3,4) or brand
  ADGROUPNAME_FIELD : "productTypes"

}

////////////////////////////////////////////////////////////////////

function main(){

  // Connect Merchant Center and collect active brands and products
  var merchantCenter = connectMerchant();
  var products = merchantCenter["products"];
  var productsIds = merchantCenter["productsIds"];

  var productGroups = activeProductGroups();
  var activeProducts = productGroups["activeProducts"];
  var activeProductsIds = productGroups["activeProductsIds"];

  var activeSubcategories = activeAdGroups();
  var addProducts = {};
  var removeProducts = [];

  // Check which products need to be added
  // Loop through all products from Merchant Center
  for (var i = 0; i < products.length; i++) {

    // If product is in stock and not in any campaign, we'll add it
    if(products[i]["availability"] == "in stock" && activeProductsIds.indexOf(products[i]["offerId"]) < 0 && activeProductsIds.indexOf(products[i]["offerId"].toLowerCase())  < 0){

      // Make sure addProducts isn't empty
      if (addProducts[products[i]["offerId"]] == null) {
        addProducts[products[i]["offerId"]] = [];
      }
      if(config.ADGROUPNAME_FIELD == "productTypes"){
        addProducts[products[i]["offerId"]].push([products[i]["offerId"], products[i][config.ADGROUPNAME_FIELD][0], products[i][config.CAMPAIGNNAME_FIELD]]);
      } else {
        addProducts[products[i]["offerId"]].push([products[i]["offerId"], products[i][config.ADGROUPNAME_FIELD], products[i][config.CAMPAIGNNAME_FIELD]]);
      }
    }

  } // merchantCenterIterator

  if(config.LOG === true){
    Logger.log("Found " + Object.keys(addProducts).length + " to be added");
    Logger.log(" ");
  }

  // Check which products need to be removed
  // Loop through all products from the campaigns
  for (var i = 0; i < activeProducts.length; i++) {
    
    // If product is not in Merchant Center, we'll remove it
    if(productsIds.indexOf(activeProducts[i].getValue()) < 0 && productsIds.indexOf(activeProducts[i].getValue().toUpperCase()) < 0 && activeProducts[i].getDimension() == "ITEM_ID"){
        if(config.LOG === true){
          Logger.log("Removed " + activeProducts[i].getValue());
        }

      activeProducts[i].remove();

    }

  } // activeProductsIterator

  // Add products
  for (var key in addProducts) {

     // Skip product if adgroupname field is undefined
    if(typeof addProducts[key][0][1] == "undefined") continue;
    
    var shoppingAdGroups = AdsApp.shoppingAdGroups().withCondition("CampaignName CONTAINS '"+config.CAMPAIGN_SELECTOR+"'");

      // If a field is defined to select the campaignname
      if(config.CAMPAIGNNAME_FIELD != ""){
        shoppingAdGroups = shoppingAdGroups.withCondition("CampaignName CONTAINS '"+addProducts[key][0][2]+"'");
      }

    shoppingAdGroups = shoppingAdGroups.withCondition("Name = '"+addProducts[key][0][1]+"'").get();

    if(!shoppingAdGroups.hasNext()){
      var campaign = AdsApp.shoppingCampaigns().withCondition("Name CONTAINS '"+config.CAMPAIGN_SELECTOR+"'");

        // If a field is defined to select the campaignname
        if(config.CAMPAIGNNAME_FIELD != ""){
          campaign = campaign.withCondition("Name CONTAINS '"+addProducts[key][0][2]+"'");
        }

      campaign = campaign.get().next();

      // Adding adgroup to Shopping campaign
      var shoppingAdGroup = campaign
        .newAdGroupBuilder()
        .withName(addProducts[key][0][1])
        .withCpc(config.DEFAULT_BID)
        .withStatus("ENABLED")
        .build()
        .getResult();

      if(config.LOG === true){
        Logger.log(shoppingAdGroup.getName() + " added to " + campaign.getName());
      }

      // Adding an ad to the new adgroup
      var ad = shoppingAdGroup
        .newAdBuilder()
        .build()
        .getResult();

      // Creating the root productgroup 'All Products' and select it
      shoppingAdGroup.createRootProductGroup();
      var root = shoppingAdGroup.rootProductGroup();

      // Creating a productgroup for this product
        if(config.ADGROUPNAME_FIELD == "productTypes"){
          var child = root.newChild().productTypeBuilder()
            .withBid(config.DEFAULT_BID)
            .withValue(addProducts[key][0][1])
            .build();
        }

        if(config.ADGROUPNAME_FIELD.indexOf("customLabel") > -1){
          var type = "CUSTOM_LABEL_" + config.ADGROUPNAME_FIELD.slice(-1);
          var child = root.newChild().customLabelBuilder()
            .withBid(config.DEFAULT_BID)
            .withType(type)
            .withValue(addProducts[key][0][1])
            .build();
        }

        if(config.ADGROUPNAME_FIELD == "brand"){
          var child = root.newChild().brandBuilder()
            .withBid(config.DEFAULT_BID)
            .withValue(addProducts[key][0][1])
            .build();
        }

      // Putting item right under productType
      child.getResult().newChild().itemIdBuilder()
        .withBid(config.DEFAULT_BID)
        .withValue(addProducts[key][0][0])
        .build();

      // Making sure all other products in this adgroup are excluded
      var children = root.children().get();
      while (children.hasNext()) {
        var child = children.next();
        if (child.isOtherCase()) {
          child.exclude();
        }
      } // childrenIterator
    }

    // If adgroup already exists, we'll add the product
    while(shoppingAdGroups.hasNext()){
      var shoppingAdGroup = shoppingAdGroups.next();

      var type = shoppingAdGroup.rootProductGroup().children().withCondition("ProductGroup CONTAINS '"+addProducts[key][0][1].toLowerCase()+"'").get().next().asProductType();

      // Creating a productgroup for this product
      type.newChild().itemIdBuilder()
        .withBid(config.DEFAULT_BID)
        .withValue(addProducts[key][0][0])
        .build();

        if(config.LOG === true){
          Logger.log("Added " + addProducts[key][0][0] + " to " + shoppingAdGroup.getName());
        }
    } // shoppingAdGroupsIterator

  } // addProductsIterator

}

////////////////////////////////////////////////////////////////////

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 = [];
  var productsIds = [];

  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++) {

        // If targetcountry and language are set, only corresponding products will be selected
        if(config.TARGET_COUNTRY != ""){
          if(config.TARGET_LANGUAGE != ""){
            if(productList.resources[i]["targetCountry"] == config.TARGET_COUNTRY && productList.resources[i]["contentLanguage"] == config.TARGET_LANGUAGE){
              products.push(productList.resources[i]);
              productsIds.push(productList.resources[i]["offerId"].toLowerCase());
            }
          } else {
            if(productList.resources[i]["targetCountry"] == config.TARGET_COUNTRY){
              products.push(productList.resources[i]);
              productsIds.push(productList.resources[i]["offerId"].toLowerCase());
            }
          }
        } else {
          products.push(productList.resources[i]);
          productsIds.push(productList.resources[i]["offerId"].toLowerCase());
        }
      }
    }

    pageToken = productList.nextPageToken;
    pageNum++;
  } while (pageToken);

  return {products : products, productsIds : productsIds};

} // function connectMerchant

////////////////////////////////////////////////////////////////////

function activeProductGroups(){

  var activeProducts = [];
  var activeProductsIds = [];

  // We collect all active productGroups
  var productGroupIterator = AdsApp
    .productGroups()
    .withCondition("CampaignName CONTAINS '"+config.CAMPAIGN_SELECTOR+"'")
    .withCondition("AdGroupStatus != REMOVED")
    .withCondition("ProductGroup DOES_NOT_CONTAIN ' OtherCase'")
    .get();

  while(productGroupIterator.hasNext()){
    var productGroup = productGroupIterator.next();

    // Only add a product if it's an active, actual product
    if(productGroup.isOtherCase() === false && productGroup.getValue() != null){
      activeProducts.push(productGroup);
      activeProductsIds.push(productGroup.getValue());
    }
  } // productGroupIterator

  return {activeProducts : activeProducts, activeProductsIds : activeProductsIds};

} // activeProducts

////////////////////////////////////////////////////////////////////

function activeAdGroups(){

  var activeSubcategories = [];

  var adGroupIterator = AdsApp
    .shoppingAdGroups()
    .withCondition("CampaignName = '"+config.CAMPAIGN_SELECTOR+"'")
    .withCondition("Status != REMOVED")
    .get()

  while(adGroupIterator.hasNext()){
    var adGroup = adGroupIterator.next();

    // Add adgroupname as subcategory
    if(activeSubcategories.indexOf(adGroup.getName()) <0){
      activeSubcategories.push(adGroup.getName());
    }

  } // adGroupIterator

  return activeSubcategories;

} // activeAdGroups
Show whole script!
Loading Comments
The Experts
Tibbe van Asten Team Lead Performance Marketing
Nils Rooijmans Water Cooler Topics
Martijn Kraan Freelance PPC Specialist
Bas Baudoin Teamlead SEA @ Happy Leads
Jermaya Leijen Digital Marketing Strategist
Krzysztof Bycina PPC Specialist from Poland
How about you? JOIN US!
Sharing Knowledge
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.

Contact us

Training &
Workshop
Contact us!
Adsscripts Training & Workshop