I'm developing a Shopify App Embed Block to display a list of recommended products. The H1 tag ("Recommended Products") is successfully displayed on the page, but the product list is not rendered because the JavaScript code inside the <script>
tag is not executed. Here’s my code:
<div id="custom-product-list" class="product-list">
<!-- Products will be injected here by JavaScript -->
<h1>Recommended Products</h1>
</div>
<script>
// Mock products array (this can be fetched via AJAX instead)
export const products = [
{
_id: '1',
title: 'product 1',
description: 'Lorem ipsum product 1 dummy description.',
price: 100,
compare_at_price: 80,
featured_image: 'https://images.pexels.com/photos/985635/pexels-photo-985635.jpeg',
url: 'https://images.pexels.com/photos/985635/pexels-photo-985635.jpeg',
},
{
_id: '2',
title: 'product 2',
description: 'Lorem ipsum product 2 dummy description.',
price: 200,
featured_image: 'https://images.pexels.com/photos/147641/pexels-photo-147641.jpeg',
url: 'https://images.pexels.com/photos/147641/pexels-photo-147641.jpeg',
}
];
function renderProducts(productList) {
const productContainer = document.getElementById('custom-product-list');
productContainer.innerHTML = '';
productList.forEach((product) => {
const productItem = `
<div class="product-item">
<a href="${product.url}">
<img src="${product.featured_image}" alt="${product.title}">
</a>
<h3><a href="${product.url}">${product.title}</a></h3>
<p class="price">
${product.compare_at_price > product.price
? `<span class="sale-price">$${product.price}</span>
<span class="original-price">$${product.compare_at_price}</span>`
: `<span class="regular-price">$${product.price}</span>`}
</p>
</div>
`;
productContainer.insertAdjacentHTML('beforeend', productItem);
});
}
document.addEventListener('DOMContentLoaded', () => {
renderProducts(products);
});
</script>
{% schema %}
{
"name": "Custom Product List",
"target": "section",
"settings": [
{
"type": "number",
"id": "product_limit",
"label": "Number of Products",
"default": 5
}
]
}
{% endschema %}
The H1 tag appears on the page, but the script responsible for rendering the products does not run. This code is inside a Shopify Liquid template file for an App Embed Block.
Why is the JavaScript not executing in this Shopify App Embed Block, and how can I make sure the script is executed to display the product list?
After experimenting, I found that Shopify's strict Content Security Policy (CSP) blocks inline JavaScript in certain contexts. The best way to add JavaScript functionality in a Shopify App Embed Block is to:
assets
directory of your theme.{{ 'filename.js' | asset_url | script_tag }}
, which adds the script to the page as an external file rather than inline.Here’s how I modified my Liquid template to include the JavaScript correctly.
The liquid template contains the HTML structure for displaying products and references the external JavaScript file:
<h1>Related Products</h1>
<div id="custom-product-list"></div>
<!-- Load JavaScript from assets directory -->
{{ 'products.js' | asset_url | script_tag }}
{% schema %}
{
"name": "Product List",
"target": "section",
"settings": [
{
"type": "number",
"id": "product_limit",
"label": "Number of Products",
"default": 5
}
]
}
{% endschema %}
The JavaScript file is stored in the theme’s assets
directory. The JavaScript code fetches the product data and renders it in the custom-product-list
container.
const products = [
{
_id: '1',
title: 'product 1',
description: 'Lorem ipsum product 1 dummy description.',
price: 100,
compare_at_price: 80,
featured_image: 'https://images.pexels.com/photos/985635/pexels-photo-985635.jpeg',
url: 'https://images.pexels.com/photos/985635/pexels-photo-985635.jpeg',
},
{
_id: '2',
title: 'product 2',
description: 'Lorem ipsum product 2 dummy description.',
price: 200,
featured_image: 'https://images.pexels.com/photos/147641/pexels-photo-147641.jpeg',
url: 'https://images.pexels.com/photos/147641/pexels-photo-147641.jpeg',
}
];
function renderProducts(productList) {
const productContainer = document.getElementById('custom-product-list');
if (!productContainer) return;
productContainer.innerHTML = ''; // Clear any existing content
productList.forEach((product) => {
const productItem = `
<div class="product-item">
<a href="${product.url}">
<img src="${product.featured_image}" alt="${product.title}">
</a>
<h3><a href="${product.url}">${product.title}</a></h3>
<p class="price">
${product.compare_at_price > product.price
? `<span class="sale-price">$${product.price}</span>
<span class="original-price">$${product.compare_at_price}</span>`
: `<span class="regular-price">$${product.price}</span>`}
</p>
</div>
`;
productContainer.insertAdjacentHTML('beforeend', productItem);
});
}
document.addEventListener('DOMContentLoaded', () => {
renderProducts(products);
});
References: