xml product sheet generator for google merchant center

Below you will find code that I use to generate my product sheets to be used by google. I simply click generate, then download, and upload the file directly to the google merchant center. Alternatively you can also copy the generated text to clipboard. The XML is correctly formatted to be accepted by google. Upon upload it does not produce any errors, and the updated product list is flagged “approved” for each product - success. It can now be used by google for search and advertising purposes. However, I am still discovering how the merchant center operates and I might add more fields to the generated XML down the line.

If you plan to use this for your own store, a lot will have to be changed to fit your products. For example, the category map will have to be populated with your own categories and you will have to look up which google code is applicable for each category (link in the code). Of course your web shop URL will be different, and a lot of product information, such as stock availability may have to be adjusted.

As for how the code works - I fetch a JSON of my shop page, then populate the XML structure I create, with the data from the JSON, along the way some manipulation is done to the text to be read correctly in the XML format. Fetching the exact color information proved troublesome for some products, I suggest adding designated tags for each product at a predefined position. For example, you can add a color as the first tag for each product, and then fetch the first product tag to fill in the google color tag. The same goes for product type which then can be tag number two. This will allow for a more granular approach, but will also require consistency and individual product editing, exactly what I didn’t want to do retrospectively. Note that if your product images include text, they may be flagged by google, but in a few days the flags will be resolved, if the text doesn’t contain advertising or any unsafe content.

HTML:

<textarea id="xmlOutput" placeholder="Your generated XML will appear here..."></textarea>
JAVASCRIPT:

document.addEventListener('DOMContentLoaded', function () {
const xmlTextArea = document.getElementById('xmlOutput');
const getURL = window.location.href;
const subString = "/productsheet";
if (getURL.includes(subString)) {
const generateButton = document.getElementById('block-yui_3_17_2_1_1725973683359_9167');
const copyButton = document.getElementById('block-74181b5749eca926bf14');
const downloadButton = document.getElementById('block-36b5b1de79a63cd630de');
const placeBlock = document.getElementById('block-9d9632b0692ebb10f751');
const placeBlockText = placeBlock.querySelector('p');
placeBlock.appendChild(xmlTextArea);
placeBlockText.textContent = '';
//initialize popover
let hideTimeout;
var popover = document.getElementById('custom-popover');
var popoverMessage = document.getElementById('popover-message');
function showPopover(message) {
popoverMessage.textContent = message;
popover.classList.add('show');
popover.style.pointerEvents = 'auto';
clearTimeout(hideTimeout);
hideTimeout = setTimeout(function() {
popover.classList.remove('show');
popover.style.pointerEvents = 'none';
}, 3000);
}
// reduced List of all (example) country codes we ship to
const shippingCountries = [
  { country: 'AE' }, // United Arab Emirates
  { country: 'MC' }, // Monaco
  { country: 'LU' } // Luxembourg
];
//to map product category IDs to Google category IDs and get category name
//https://www.google.com/basepages/producttype/taxonomy-with-ids.en-US.txt
function getGoogleCategoryInfo(productCategoryId, categoryIds) {
const categoryMap = {
'Prints': '500044',
'Clothing': '1604',
'Accessories': '166',
'Unique': '216',
'Other': '8'
};
let googleCategoryId = '5710'; // Default, Hobbies & Creative Arts
let googleCategoryName = 'Hobbies and Creative Arts'; // Default category name
categoryIds.forEach(category => {
if (category.id === productCategoryId) {
googleCategoryId = categoryMap[category.displayName] || googleCategoryId;
googleCategoryName = category.displayName || googleCategoryName;
}
});
return { googleCategoryId, googleCategoryName };
}
//to clean the product description for xml
function cleanDescription(description) {
const tempElement = document.createElement('textarea');
tempElement.innerHTML = description;
let plainText = tempElement.value;
plainText = plainText.replace(/<\/?[^>]+(>|$)/g, "");
plainText = plainText.replace(/<br\s*\/?>/gi, "\n");
plainText = plainText
//.replace(/•/g, '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
return plainText.trim();
}
//Fetch the shop JSON data
const shopJsonURL = 'https://www.polivantage.com/shop?format=json-pretty';
async function fetchProductData() {
try {
const response = await fetch(shopJsonURL);
const data = await response.json();
return {
products: data.items,
categories: data.nestedCategories.categories || []
};
} catch (error) {
console.error('Error fetching product data:', error);
return { products: [], categories: [] };
}
}
//Build the XML feed
//do not use any special characters like & in the title and desciption!
function buildXmlFeed(products, categories) {
const xmlHeader = `<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
<channel>
<title>Polivantage Art Shop</title>
<link>https://www.polivantage.com/shop</link>
<description>Art and Design Services and Concept Products</description>
`;
let xmlItems = '';
products.forEach(product => {
const id = product.id;
const title = product.title;
const description = cleanDescription(product.excerpt || 'No description available');
const link = `https://www.polivantage.com${product.fullUrl}`;
const image_link = product.items[0].assetUrl;
const availability = 'in stock';
const price = product.variants[0].priceMoney.value + ' EUR';
const brand = 'Polivantage';
const googleCategoryId = getGoogleCategoryInfo(product.categoryIds[0], categories).googleCategoryId;
const type = getGoogleCategoryInfo(product.categoryIds[0], categories).googleCategoryName;
const shippingPrice = '0 EUR';
const shippingService = 'Standard'; 
const language = 'en'; 
const gender = 'unisex';
const ageGroup = 'adult';
const shippingLabel = 'Free Shipping';
const deliveryTime = '14 days';
const transitTimeLabel = 'EU_Shipped';
const sizes = product.variants.map(variant => variant.attributes.Size || 'M');
const colors = product.variants.map(variant => variant.attributes.Color || 'Black').filter(Boolean).slice(0, 3);
// Generate the XML item for this product
xmlItems += `
<item>
<g:id>${id}</g:id>
<g:title>${title}</g:title>
<g:description>${description}</g:description>
<g:link>${link}</g:link>
<g:product_type>${type}</g:product_type>
<g:image_link>${image_link}</g:image_link>
<g:availability>${availability}</g:availability>
<g:price>${price}</g:price>
<g:brand>${brand}</g:brand>
<g:condition>new</g:condition>
<g:google_product_category>${googleCategoryId}</g:google_product_category>
<g:identifier_exists>false</g:identifier_exists>
<g:language>${language}</g:language>
<g:gender>${gender}</g:gender>
<g:age_group>${ageGroup}</g:age_group>
${colors.map(color => `<g:color>${color}</g:color>`).join('')}
${sizes.map(size => `<g:size>${size}</g:size>`).join('')}
${shippingCountries.map(config => `
<g:shipping>
<g:country>${config.country}</g:country>
<g:service>Standard</g:service>
<g:price>${shippingPrice}</g:price>
<g:shipping_label>${shippingLabel}</g:shipping_label>
<g:transit_time_label>${transitTimeLabel}</g:transit_time_label>
</g:shipping>
`).join('')}
</item>`;
});
const xmlFooter = `
</channel>
</rss>`;
return xmlHeader + xmlItems + xmlFooter;
}
// Generate the final XML
async function generateXmlFeed() {
const { products, categories } = await fetchProductData();
if (products.length === 0) {
alert('No products found or error fetching products.');
return;
}
const xmlFeed = buildXmlFeed(products, categories);
xmlTextArea.value = xmlFeed;
}
generateButton.addEventListener('click', function (event) {
event.preventDefault();
generateXmlFeed();
});
//copy to clipboard button
copyButton.addEventListener('click', function (event) {
event.preventDefault();
const text = xmlTextArea.value;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
$('#popoverMessage').off('click');
popoverMessage.style.color = "#ffc700";
showPopover('XML copied to clipboard');
}).catch(err => {
$('#popoverMessage').off('click');
popoverMessage.style.color = "#ea4b1a";
showPopover('XML failed to copy');
});
} else {
console.error('Clipboard API not supported');
}
});  
//download file button
downloadButton.addEventListener('click', function (event) {
event.preventDefault();
const text = xmlTextArea.value;
if (text === '') {
$('#popoverMessage').off('click');
popoverMessage.style.color = "#ea4b1a";
showPopover('Generate the XML first');
return;
}
const blob = new Blob([text], { type: 'application/xml' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.href = url;
link.download = 'PolivantageProductSheet.xml';
link.click();
URL.revokeObjectURL(url);
});  
//exit
} else {
xmlTextArea.remove();
}
});
CSS:

#block-9d9632b0692ebb10f751 {
  display: flex;
  justify-content: center;
  align-items: center;
}
#xmlOutput {
  width: 96%;
  resize: vertical;
  min-height: 50vh;
  background: transparent;
}
Previous
Previous

User Profile page with firebase - the dossier

Next
Next

Display number of visitors on each product page With Firebase