How to add Schema to Matrix Child Items to Fix Google and Facebook Ads on SuiteCommerce

SuiteCommerce product detail pages have schema automatically inserted into them when enabled in SuiteCommerce configuration. However, if you have matrix items, Google Shopping Ads, Google Ads, and Facebook ads can be denied. The reason for this is that the native schema does not accommodate matrix child items (variant child items). There is a relatively simple method to resolve this via a SuiteCommerce extension and in this article, Anchor Group wants to give the code to the community for free as this is a critical component to running ads on matrix items in SuiteCommerce or SuiteCommerce Advanced.

SuiteCommerce Google Ad not working. Facebook ad not working. Schema fix
Knowledge Prerequisite: Ability to fetch and deploy themes / extensions on SuiteCommerce

As long as you know how to fetch and deploy extensions, then you will be able to leverage this code shown in the article for your SuiteCommerce/SuiteCommerce Advanced website to fix the schema markup of the matrix items.


Step 1: Understand Schema Markup, Rich Results Testing Tool, and Matrix Child Items

If you don't have a fundamental understanding of what Schema markup is and how to generate schema markup, you may want to look at free tools which provide sample schema output in order to have an initial visual. You can always test your product detail page schema using the free Google rich results testing tool. You can copy/paste your product detail page URL into this tool and see what schema elements are able to be read properly. It is a good idea to see what a matrix item looks like on this testing tool prior to the extension being activated as it will help you understand the "before and after" of the extension results.

Here is an example of what a product detail page of a matrix item looks like after the extension has been activated on the website. You will now see offers for each of the variant selections.

rich results testing tool matrix items on suitecommerce

Step 2: Fetch your SuiteCommerce Extensions

This is a common developer step on SuiteCommerce so if you don't know how to do this, feel free to contact Anchor Group about a NetSuite or SuiteCommerce support plan.

You will need to know how to make a SuiteCommerce extension in order to pull all the elements of this code together and deploy it to your SuiteCommerce account.

Step 3: Build the Extension Using this Code

You need to insert schema markup so that Google can read the schema for each of the variant options of the matrix item. This is primarily done through 'offers'. In addition to this, you may want to work in your Google Tag Manager to make tracking adjustments for your ads.

PDP Schema Entry Javascript

define('PDPSchema.Entry', [


    'PDPSchema.Item.Model',
    'PDPSchema.Product.Model',
    'PDPSchema.ProductDetails.Full.View'
], 
function () {
    'use strict';


    return {
        mountToApp: function (container) {
            //Load the three files above which extend their respective native files
        }
    };
});

PDP Schema Item Model Javascript

define('PDPSchema.Item.Model',


[
    'Item.Model',
    'Profile.Model',
    'SC.Configuration'
], 
function (
    ItemModel,
    ProfileModel,
    Configuration
) {
    'use strict';


    _.extend(ItemModel.prototype, {


        // @method containsMatrixChildItems
        // Returns true if the item contains sub matrix child items
        // @return {Boolean}
        containsMatrixChildItems: function () {
            return !! (this.get('_matrixChilds') && this.get('_matrixChilds').length);
        },


        // @method getJsonLdOffer
        // Returns the JSON-LD offer for an item
        // @return {Object}
        getJsonLdOffer: function getJsonLdOffer(itemUrl) {
            var stockInfo = this.getStockInfo();


            var offer = {
                '@type': 'Offer',
                availability: (stockInfo.isInStock) ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
                name: this.get('salesdescription'),
                mpn: this.get('mpn') || undefined,
                sku: this.getSku() || undefined,
                url: itemUrl || undefined
            };


            // Get price & currency
            var priceContainerObject = this.getPrice();
            var isPriceRange = priceContainerObject.min && priceContainerObject.max;
            if (!ProfileModel.getInstance().hidePrices()) {
                if (isPriceRange) {
                    offer.priceSpecification = {
                        '@type': 'PriceSpecification',
                        price: this.getPrice().price,
                        minPrice: priceContainerObject.min.price,
                        maxPrice: priceContainerObject.max.price
                    };
                } else {
                    offer.price = this.getPrice().price;
                }


                offer.priceCurrency = SC.getSessionInfo('currency') && SC.getSessionInfo('currency').code
                    ? SC.getSessionInfo('currency').code
                    : Configuration.get('siteSettings.shopperCurrency.code');
            }


            return offer;
        },


        // @method getStoreDescriptionAsText
        // Returns a text version of the item's web store description.  The HTML is removed, and multiple whitespace
        // characters are replaced with a single space.
        // @return {String}
        getStoreDescriptionAsText: function () {
            var htmlDescription = jQuery('<div/>').html(this.get('storedescription'));
            htmlDescription.find('script').remove();
            htmlDescription.find('style').remove();


            return htmlDescription.text().replace(/\s+/g, ' ').trim();
        }
        
    });
});

PDP Schema Product Model Javascript

// @module PDPSchema.Product.Model


define('PDPSchema.Product.Model',
[
    'Product.Model'
],
function (
    ProductModel
){
    'use strict';


    // @class PDPSchema.Product.Model custom properties and methods added to Product.Model
    _.extend(ProductModel.prototype, {


        // @method buildItemSeoDescription
        // Filters out quote and aposthrophe characters
        // text description.
        // @param {String} textDescription
        // @return {String}
        buildItemSeoDescription: function (textDescription) {
            var description = textDescription || '';


            // Filter out apostrophes and quotes
            description = description.replace(/['"]/g, '');


            return description;
        },


        // @method isMatrixItem
        // Returns true if item model is a matrix item.
        // @return {Boolean}
        isMatrixItem: function () {
            return this.get('item').containsMatrixChildItems();
        }


    });


});

PDP Schema Product Details Full View Javascript

define('PDPSchema.ProductDetails.Full.View', [


    'ProductDetails.Full.View',
    'SC.Configuration'
], function (
    ProductDetailsFullView,
    Configuration
) {
    'use strict';


    ProductDetailsFullView.prototype.getJsonLd = _.wrap(ProductDetailsFullView.prototype.getJsonLd, function (fn) {
    
        var jsonLdPromise = fn.apply(this, _.toArray(arguments).slice(1));
        var self = this;
    
        if (Configuration.get('structureddatamarkup.type') !== 'JSON-LD') {
            return jQuery.Deferred().resolve(null);
        }
    
        var modelItem = this.model.get('item');
        var offers = [];
        if (this.model.isMatrixItem()) {
            /*
             * Fetch item options and construct a unique matrix child item URL that contains the
             * item option names and values
             */
            var itemOptions = this.model.getVisibleOptions();
    
            var childItems = this.model.getSelectedMatrixChilds([]);
            _.each(childItems, function (childItem) {
                var itemOptionParameters = {};
                _.each(itemOptions, function(itemOption) {
                    var itemValue = childItem.get(itemOption.get('itemOptionId'));
                    if(itemValue) {
                        var itemOptionValue = _.find(itemOption.get('values'), function(value) {
                            return value.label === itemValue;
                        });
                        itemOptionParameters[itemOption.get('urlParameterName')] = itemOptionValue.internalid;
                    }
                });
    
                var itemUrl = self.model.generateURL(itemOptionParameters);
                itemUrl = itemUrl.replace(/\?quantity=1&/g, '?');
                itemUrl = itemUrl.replace(/\?quantity=1$/g, '');
    
                offers.push(childItem.getJsonLdOffer(itemUrl));
            });
        } else {
            offers.push(this.model.get('item').getJsonLdOffer());
        }
    
        var brand = {
            '@type': 'Brand',
            name: modelItem.get('_brand')
        };
    
        var storeDetailedDescription = this.model.buildItemSeoDescription(modelItem.getStoreDescriptionAsText()) || '';
        return jsonLdPromise.then(function (jsonLd) {
            return _.extend({}, jsonLd, {
                '@type': 'Product',
                name: self.page_header,
                brand: brand,
                image: self.model.getImages()[0].url.toString(),
                offers: offers,
                description: storeDetailedDescription.length > 0 ? storeDetailedDescription : undefined,
                sku: self.model.get('item').getSku() || undefined
            });
        });
    });


});
Note: this might not be a perfect extension but it will help you quickly get your ads running and then you can make adjustments to this code as you see best fit for your needs.

Step 4: Deploy and Activate the Extension

After you put together this extension, you will need to deploy and activate it through your 'extension manager'. Once you have done this step, make sure to test the product detail page via the Google rich results testing tool.


Get stuck in a step during this article?

That's all for now! Hopefully, this post successfully guided you through fixing your schema markup of matrix items for your SuiteCommerce website.

We like to update our blogs and articles to make sure they help resolve any troubleshooting difficulties you are having. Sometimes there is a related feature to enable or a field to fill out that we miss during the instructions. If this article didn't resolve the issue, please use the chat and let us know so that we can update this article!


FREE SuiteCommerce Book

If you liked this article, you'll LOVE our book on SuiteCommerce! It's full of tips and tricks for getting the most value from your SuiteCommerce site. So, what are you waiting for? Order the free SuiteCommerce book today, and we'll even pay for shipping!

SuiteCommerce book
 
 

Want to keep learning?

Our team of NetSuite professionals here at Anchor Group has written articles on a wide variety of NetSuite topics, from SuiteCommerce tips, to recommended NetSuite solutions, to available support services, and more! 

Your cart
    Checkout