javascriptshopifyliquidshopify-appshopify-app-extension

Script Tag Not Executing Inside Shopify App Embed Block


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?


Solution

  • 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:

    1. Move JavaScript code to a separate file in the assets directory of your theme.
    2. Reference the JavaScript file using liquid script tag {{ '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: