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).
Date
June 9, 2025
Read
7 min
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.
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.
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.
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('
// @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!
If you have general questions about SuiteCommerce or more specific questions about how our team can support your business as you implement NetSuite or SuiteCommerce, feel free to contact us anytime. Anchor Group is a certified Oracle NetSuite Alliance Partner and Commerce Partner equipped to handle all kinds of NetSuite and SuiteCommerce projects, large or small!
We are a premium SuiteCommerce agency that creates powerful customer portals. Unlike our competitors, we have already solved your problems.
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!