Skip to content

RESOURCES / BLOG

How to Make Your Product Videos Shoppable Using Cloudinary and Shopify

Why It Matters

  • Build a shoppable video player that syncs with a Shopify store, allowing customers to click products within a video and be taken to the product page.
  • The Cloudinary and Shopify integration enables real-time inventory synchronization, dynamic product updates, and a streamlined purchase journey.
  • The guide outlines a comprehensive process for building the demo using HTML, CSS, and JavaScript.

Product videos are great for engagement, but terrible for conversion if shoppers can’t buy what they just saw.

Let’s say a customer watches your styling video, loves the sunglasses at the 12-second mark, and wants to buy them. But now they have to pause the video, search your catalog, maybe filter by “accessories,” and hope they find the exact pair. That’s too much friction, and it kills purchase intent.

As someone who’s helped dozens of Shopify stores solve this problem, I’ll show you how to turn any product video into an interactive shopping experience using Cloudinary’s shoppable video player and Shopify’s product API.

You’ll learn how to:

  • Sync your real-time inventory with video overlays.
  • Add clickable product hotspots.
  • Automatically link viewers to the right product page based on what they clicked.

By the end of this tutorial, you’ll have a fully working demo that connects your videos to your Shopify store, so your customers can watch, click, and buy without ever leaving the experience.

Shoppable Video Experience showcasing featured products a model is wearing: sunglasses, outfit, and accessories
Shoppable using Cloudinary and Shopify demo

Combining Cloudinary’s advanced video capabilities with Shopify’s robust e-commerce infrastructure creates a seamless solution to this conversion challenge. Here’s why this pairing works so effectively:

  1. Real-time inventory synchronization. Your videos automatically reflect product availability, preventing customer disappointment from out-of-stock items.
  2. Dynamic product information updates. Pricing, descriptions, and product details refresh automatically without manual updates.
  3. Streamlined purchase journey. Customers can click directly on products within the video and be instantly directed to the relevant product pages, eliminating the need to search through catalogs and maintaining their purchase intent.
  4. Scalable media management. Cloudinary automatically handles video optimization, delivery, and transformations.
  5. Developer-friendly integration. Both platforms offer comprehensive APIs that work together smoothly.

Now, let’s walk through the process of building this integration step by step.

You should have:

  • Basic knowledge of HTML, CSS, and JavaScript.
  • A local development server (Python, Node.js, or VS Code Live Server).
  • A free Cloudinary account for video optimization.
  • Shopify store or Partner account.

You need environment credentials from your Cloudinary dashboard to use Cloudinary video transformations and create optimized video delivery.

Log in to your Cloudinary dashboard to retrieve environment credentials such as the cloud name, API key, and API secret.

Cloudinary dashboard with Cloud name, API key, and API secret
Get environment variables from Cloudinary

Now let’s walk through the process of setting up your shoppable video infrastructure from scratch.

Start by setting up your development environment with the proper file structure:

# Create project directory

mkdir cloudinary-shoppable-tutorial

cd cloudinary-shoppable-tutorialCode language: PHP (php)

Next, create the basic file structure:

# Create the basic file structure

touch index.html script.jsCode language: CSS (css)

Then start or set up your local server with the following command:

# Using Live Server extension in VS Code (recommended)

# Just right-click index.html and select "Open with Live Server"

# Option 2: Using Node.js http-server

npm install -g http-server

http-serverCode language: PHP (php)

This structure keeps your code organized and makes it easy to test changes as you build.

Before you can create shoppable videos, you need to gather specific information from your Shopify store. Here’s exactly what you’ll need for each product you want to feature:

Required Shopify data points:

  • Product ID. Navigate to your Shopify admin > Products > select a product. The Product ID appears in the URL (e.g., /products/7234567890123).
  • Product Handle. This is the URL-friendly version of your product name (e.g., “uv400-sunglasses-black”).
  • Variant IDs. For products with different colors or sizes, you’ll need each variant’s ID.
  • Store Domain. Your full store URL (e.g., “yourstore.myshopify.com”).

How to find your product information:

// Navigate to: yourstore.myshopify.com/admin/products/PRODUCT_ID.json

// This will show you all product data including:

{

  "product": {

    "id": 7234567890123,

    "title": "UV400 Sunglasses",

    "handle": "uv400-sunglasses-black",

    "variants": [

      {

        "id": 41234567890123,

        "title": "Black",

        "price": "89.99",

        "inventory_quantity": 12

      }

    ]

  }

}Code language: JSON / JSON with Comments (json)

You can also use Shopify’s Admin API to fetch this data programmatically if you have API access.

To get your video public IDs and configure metadata for an individual asset, navigate to your Cloudinary dashboard, upload or select any of your existing videos, and then click to copy the video ID, as shown below.

A step-by-step tutorial of where to find your video public ID in your Cloudinary dashboard
Click to copy the video ID

Select any of your existing videos for collection assets, then click to copy the video ID inside the collection, as shown below.

Sunglasses folder with 2 assets. The video ID is copied by clicking the link icon
Click to copy the collection video ID

Now you’ll create your first interactive video player. This step transforms a standard video into a dynamic shopping experience where every product becomes clickable. This implementation uses vanilla HTML, CSS, and JavaScript.

Add the following code snippet inside the index.html file:

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>Shoppable Video Tutorial</title>

    <!-- Cloudinary Video Player CSS - Required for styling -->

    <link

      rel="stylesheet"

      href="https://cdn.jsdelivr.net/npm/cloudinary-video-player@2.3.5/dist/cld-video-player.min.css"

      crossorigin="anonymous"

      referrerpolicy="no-referrer"

    />

  </head>

  <body>

    <div class="container">

      <h1>🛍️ Shoppable Video Experience</h1>

      <div class="video-section">

        <!-- Status indicator to show what's happening -->

        <div class="status" id="status">Loading video player...</div>

        <!-- Video container where Cloudinary player will be initialized -->

        <div class="video-container" id="videoContainer">

          <video

            id="shoppable-player"

            controls

            muted

            class="cld-video-player"

          ></video>

        </div>

        <!-- Product information will be displayed here -->

        <div class="product-info" id="productInfo" style="display: none;">

          <h3>Featured Products:</h3>

          <div class="product-list" id="productList"></div>

        </div>

      </div>

    </div>

    <!-- Cloudinary Video Player JavaScript - Required for functionality -->

    <script

      src="https://cdn.jsdelivr.net/npm/cloudinary-video-player@2.3.5/dist/cld-video-player.min.js"

      crossorigin="anonymous"

      referrerpolicy="no-referrer"

    ></script>

  </body>

</html>Code language: HTML, XML (xml)

Now let’s add some basic styling to make it look professional. While styling is optional (you can use your own styles or skip this step entirely), we’ve prepared some professional CSS styles for users who want to follow along with the complete tutorial. You can find the complete CSS styles in this GitHub gist, simply copy the styles and add them in a <style> section within your <head> tag.

Create a shopify-config.js file to centralize your Shopify and Cloudinary settings. This keeps your sensitive info organized, makes updates easier, and ensures you’re only changing things in one place when your product details or domain change.

Let’s build this file piece by piece:

// shopify-config.js

// Your Cloudinary configuration

const CLOUDINARY_CONFIG = {

  cloudName: "your-cloud-name-here", // Replace with your actual Cloudinary cloud name

};

// Your Shopify store configuration  

const SHOPIFY_CONFIG = {

  storeDomain: "yourstore.myshopify.com", // Replace with your actual store domain

};Code language: PHP (php)

Next, create a helper function for product URLs. This function automatically generates the correct Shopify product URLs, including variant-specific links. It prevents typos and makes it easy to update URL structures later.

// shopify-config.js

//...

// Function to generate product URLs dynamically

function generateProductUrl(handle, variant = null) {

  let url = `https://${SHOPIFY_CONFIG.storeDomain}/products/${handle}`;

  if (variant) {

    url += `?variant=${variant}`;

  }

  return url;

}Code language: JavaScript (javascript)

Now, you can fetch real Shopify data by creating a function that connects to Shopify’s public API to get real-time product information, such as pricing and availability. This ensures your videos always show current information without manual updates.

//...

// Function to fetch real-time product data from Shopify

async function fetchShopifyProductData(productHandle) {

  try {

    // Using Shopify's public product API (no authentication required)

    const response = await fetch(

      `https://${SHOPIFY_CONFIG.storeDomain}/products/${productHandle}.js`

    );

    if (!response.ok) {

      throw new Error(`Product not found: ${productHandle}`);

    }

    const productData = await response.json();

    return {

      id: productData.id,

      title: productData.title,

      handle: productData.handle,

      price: (productData.price / 100).toFixed(2), // Convert from cents to dollars

      comparePrice: productData.compare_at_price

        ? (productData.compare_at_price / 100).toFixed(2)

        : null,

      available: productData.available,

      variants: productData.variants.map((variant) => ({

        id: variant.id,

        title: variant.title,

        price: (variant.price / 100).toFixed(2),

        available: variant.available,

        inventory: variant.inventory_quantity,

      })),

      url: `/products/${productData.handle}`,

    };

  } catch (error) {

    console.error("Error fetching product data:", error);

    return null;

  }

}Code language: JavaScript (javascript)

Continue building your configuration by mapping your videos to products. This object connects your Cloudinary videos to specific Shopify products, defining when products appear in the video, where clickable hotspots are placed, and what happens when users click them.

//..

// Map your video IDs to Shopify product information

const VIDEO_PRODUCTS = {

  "your-video-public-id": { // Replace with your actual Cloudinary video public ID

    shopify_handle: "your-product-handle", // Replace with your Shopify product handle

    products: [

      {

        productId: 1,

        productName: "Your Product Name", // Will be updated from Shopify

        startTime: 0,      // When this product appears in the video (seconds)

        endTime: 15,       // When this product disappears (seconds)

        publicId: "your-product-image", // Cloudinary image for overlay

        hotspots: [

          {

            time: "00:05",           // When hotspot appears (MM:SS format)

            x: "40%",                // Horizontal position on video

            y: "30%",                // Vertical position on video

            tooltipPosition: "right", // Where tooltip appears

          },

        ],

        onClick: {

          action: "goto",    // What happens when clicked

          pause: true,       // Pause video when clicked

          args: {

            url: "", // Will be populated dynamically from Shopify

          },

        },

      },

    ],

  },

};Code language: PHP (php)

Before connecting to your real Shopify data, let’s create a working demo using Cloudinary’s sample videos. Starting with a working demo lets you see immediate results and understand how each piece works before safely updating to your real store data. This prevents frustration and makes debugging much easier.

Replace your configuration with this working demo setup inside the script.js file.

Now, let’s build the core JavaScript that connects everything. You’ll create this step by step, with each function handling a specific aspect of your shoppable video experience. This script orchestrates everything: it initializes the video player, displays product information, handles user interactions, and updates the interface with real-time data.

Update your script.js file and start with the essential variables:

// script.js

// Variables to track current state

let currentPlayer = null;

let currentVideoId = "docs/shoppable_demo"; // Default video to loadCode language: JavaScript (javascript)

Next, create a status update function that provides visual feedback to users about what’s happening (i.e., loading, success, errors). 

// script.js

//...

// Function to update status display with different types

function updateStatus(message, type = "info") {

  const statusElement = document.getElementById("status");

  if (!statusElement) return;

  statusElement.textContent = message;

  // Remove existing status classes and add styling based on type

  statusElement.className = "status";

  if (type === "error") {

    statusElement.style.background = "#ffebee";

    statusElement.style.color = "#c62828";

  } else if (type === "success") {

    statusElement.style.background = "#e8f5e8";

    statusElement.style.color = "#2e7d32";

  } else {

    statusElement.style.background = "#e3f2fd";

    statusElement.style.color = "#1976d2";

  }

}Code language: JavaScript (javascript)

Then, you will build a product display function that shows users what products are featured in the current video, including pricing and availability. This will help users understand what they can shop for before starting the video.

// script.js

//...

// Function to display product information from video data

function displayProducts(videoProducts) {

  const productInfo = document.getElementById("productInfo");

  const productList = document.getElementById("productList");

  // Check if elements exist

  if (!productInfo || !productList) {

    console.warn("Product display elements not found");

    return;

  }

  // Hide product info if no products

  if (!videoProducts || videoProducts.length === 0) {

    productInfo.style.display = "none";

    return;

  }

  // Clear existing products and show container

  productList.innerHTML = "";

  productInfo.style.display = "block";

  // Create display for each product

  videoProducts.forEach((product) => {

    const productData = MOCK_PRODUCTS[product.productId];

    if (!productData) return;

    const productItem = document.createElement("div");

    productItem.style.cssText = `

      background: white;

      border-radius: 8px;

      padding: 15px;

      margin-bottom: 10px;

      box-shadow: 0 2px 4px rgba(0,0,0,0.1);

      display: flex;

      justify-content: space-between;

      align-items: center;

    `;

    productItem.innerHTML = `

      <div>

        <h4 style="margin-bottom: 5px; color: #333;">${productData.title}</h4>

        <div style="font-weight: bold; color: #2e7d32;">

          ${

            productData.comparePrice

              ? `<span style="text-decoration: line-through; color: #999; margin-right: 10px;">$${productData.comparePrice}</span>`

              : ""

          }

          $${productData.price}

        </div>

      </div>

      <div style="padding: 4px 8px; border-radius: 4px; font-size: 0.9rem; ${

        productData.available

          ? "background: #e8f5e8; color: #2e7d32;"

          : "background: #ffebee; color: #c62828;"

      }">

        ${

          productData.available

            ? `In stock${

                productData.stockLevel

                  ? ` (${productData.stockLevel} left)`

                  : ""

              }`

            : "Out of stock"

        }

      </div>

    `;

    productList.appendChild(productItem);

  });

}Code language: JavaScript (javascript)

You must create a Cloudinary Video Player, load your video content, and configure all the interactive elements. To do that, update the script.js file with the following code snippet:

//...

// Function to initialize the video player

function initializeVideo() {

  try {

    updateStatus("Initializing video player...");

    // Check if video element exists

    const videoElement = document.getElementById("shoppable-player");

    if (!videoElement) {

      updateStatus("Error: Video element not found", "error");

      return;

    }

    // Check if Cloudinary is loaded

    if (typeof cloudinary === "undefined") {

      updateStatus("Error: Cloudinary Player SDK not loaded", "error");

      return;

    }

    // Create new Cloudinary player instance

    currentPlayer = cloudinary.videoPlayer("shoppable-player", {

      cloudName: DEMO_CONFIG.cloudName,

      controls: true,

      muted: true,

      fluid: true, // Responsive sizing

    });

    // Get video configuration

    const videoData = VIDEO_PRODUCTS[currentVideoId];

    if (!videoData) {

      updateStatus(

        `Error: Video configuration for ${currentVideoId} not found.`,

        "error"

      );

      return;

    }

    // Display product information

    displayProducts(videoData.products);

    // Configure and load the shoppable video

    loadShoppableVideo(videoData);

  } catch (error) {

    updateStatus(`Error: ${error.message}`, "error");

    console.error("Initialization error:", error);

  }

}Code language: JavaScript (javascript)

Next, build the Shoppable Video loading function to set up the interactive elements, dynamic overlays and hotspots, and click behaviors that make your video shoppable.

//...

// Function to configure and load shoppable video

function loadShoppableVideo(videoData) {

  // Configure shoppable video if products exist

  if (videoData.products && videoData.products.length > 0) {

    const shoppableConfig = {

      shoppable: {

        startState: "openOnPlay", // Show products when video starts

        bannerMsg: "Shop this demo", // Call-to-action message

        autoClose: 5, // Auto-hide product bar after 5 seconds

        products: videoData.products,

      },

    };

    // Load video with shoppable configuration

    currentPlayer.source(currentVideoId, {

      ...shoppableConfig,

      transformation: [{ quality: "auto" }],

    });

  } else {

    // Load regular video without shoppable features

    currentPlayer.source(currentVideoId, {

      transformation: [{ quality: "auto" }],

    });

  }

  // Set up event handlers after loading

  setupVideoEventHandlers();

}Code language: JavaScript (javascript)

Now, add event handlers for user interactions. These event handlers respond to user actions like clicking on products, hovering over hotspots, and video loading events. They provide feedback and track meaningful interactions for analytics.

//...

// Function to set up video event handlers

function setupVideoEventHandlers() {

  if (!currentPlayer) return;

  // Handle product clicks

  currentPlayer.on("productClick", function (event) {

    updateStatus(`Clicked on: ${event.productName} at ${event.time}s`, 'success');

    // Track the click for analytics

    console.log("Product clicked:", {

      productId: event.productId,

      productName: event.productName,

      videoId: currentVideoId,

      timestamp: event.time

    });

    // Optional: Add custom analytics tracking

    trackProductClick(event);

  });

  // Handle product hover events

  currentPlayer.on("productHover", function (event) {

    console.log("Product hovered:", event);

  });

  // Handle successful video load

  currentPlayer.on("ready", function () {

    updateStatus("Demo loaded! Click play to see shoppable features.", 'success');

  });

  // Handle video errors

  currentPlayer.on("error", function (error) {

    if (error && (error.code === 4 || error.message?.includes("404"))) {

      updateStatus(`Video "${currentVideoId}" not found. Please check the video exists.`, 'error');

    } else {

      updateStatus("Error loading video. Please try again.", 'error');

      console.error("Video player error:", error);

    }

  });

}

Almost done! Now you need to initialize the video as soon as the page loads by adding the event listener to the script.js file.

//...

// Initialize video player when page loads

document.addEventListener("DOMContentLoaded", function () {

  if (typeof cloudinary !== "undefined") {

    initializeVideo();

  } else {

    updateStatus("Error: Cloudinary Player SDK failed to load.", 'error');

  }

});Code language: JavaScript (javascript)

Now, update your HTML to include the necessary scripts. Add these script tags just before the closing </body> tag in your index.html:

<!-- Include the shoppable extension -->

<script src="https://unpkg.com/cloudinary-video-player/shoppable"></script>

<script src="script.js"></script>Code language: HTML, XML (xml)

Your complete HTML should look like this at the bottom:

<!-- Cloudinary Video Player JavaScript - Required for functionality -->

    <script

      src="https://cdn.jsdelivr.net/npm/cloudinary-video-player@2.3.5/dist/cld-video-player.min.js"

      crossorigin="anonymous"

      referrerpolicy="no-referrer"

    ></script>

    <!-- Include the shoppable extension -->

    <script src="https://unpkg.com/cloudinary-video-player/shoppable"></script>

    <script src="script.js"></script>

  </body>

</html>Code language: HTML, XML (xml)

At this point, you should have a fully functional shoppable video demo! Open your index.html file in a browser (preferably through a local server) and you should see:

  • A video player with Cloudinary’s demo video.
  • Product information displayed below the video.
  • Clickable hotspots when you play the video.
  • Status updates showing what’s happening.
Shoppable Video Experience demo
Shoppable using Cloudinary and Shopify demo – 2

Now that your demo works perfectly, you can safely replace the mock data with your actual Shopify store information without breaking the functionality.

Replace the demo configuration in your script.js:

// Replace the DEMO_CONFIG with your actual Cloudinary settings

const DEMO_CONFIG = {

  cloudName: "your-actual-cloudinary-name", // Get this from your Cloudinary dashboard

};

// Add your actual Shopify store domain

const SHOPIFY_CONFIG = {

  storeDomain: "your-store.myshopify.com", // Your actual Shopify store URL

};Code language: PHP (php)

Replace the demo video configuration with your actual videos and Shopify product handles. This connects your real content to your real products.

Replace the VIDEO_PRODUCTS configuration with your actual data:

//...

// Update this with your actual video and product information

const VIDEO_PRODUCTS = {

  "your-video-public-id": { // Replace with your actual Cloudinary video public ID

    shopify_handle: "your-product-handle", // Replace with your Shopify product handle

    products: [

      {

        productId: 1,

        productName: "Your Product Name", // Will be updated from Shopify

        startTime: 0,

        endTime: 15,

        publicId: "your-product-image", // Your product image in Cloudinary

        hotspots: [

          {

            time: "00:05",

            x: "40%",

            y: "30%",

            tooltipPosition: "right",

          },

        ],

        onClick: {

          action: "goto",

          pause: true,

          args: {

            url: "", // Will be populated from Shopify

          },

        },

      },

    ],

  },

};Code language: PHP (php)

Now let’s integrate the real Shopify data fetching into your working demo. Add this enhanced initialization function. This modification fetches real Shopify data and updates your video configuration with current prices, availability, and product URLs.

//script.js

//...

// Enhanced function to work with real Shopify data

async function initializeVideoWithShopify() {

  try {

    updateStatus("Initializing video player...");

    const videoElement = document.getElementById("shoppable-player");

    if (!videoElement) {

      updateStatus("Error: Video element not found", 'error');

      return;

    }

    currentPlayer = cloudinary.videoPlayer("shoppable-player", {

      cloudName: DEMO_CONFIG.cloudName, // Use your actual cloud name

      controls: true,

      muted: true,

      fluid: true,

    });

    const videoData = VIDEO_PRODUCTS[currentVideoId];

    if (!videoData) {

      updateStatus(`Error: Video configuration not found.`, 'error');

      return;

    }

    // Fetch real Shopify product data if handle is provided

    if (videoData.shopify_handle && typeof fetchShopifyProductData !== 'undefined') {

      updateStatus("Fetching current product data from Shopify...");

      try {

        const shopifyData = await fetchShopifyProductData(videoData.shopify_handle);

        if (shopifyData) {

          // Update video products with real Shopify data

          updateProductsWithShopifyData(videoData, shopifyData);

        }

      } catch (error) {

        console.warn("Could not fetch Shopify data, using static data:", error);

        updateStatus("Using cached product data", 'info');

      }

    }

    // Display product information and load video

    displayProducts(videoData.products);

    loadShoppableVideo(videoData);

  } catch (error) {

    updateStatus(`Error: ${error.message}`, 'error');

    console.error("Initialization error:", error);

  }

}Code language: JavaScript (javascript)

Next, update products with real Shopify data:

//...

// Function to update products with real Shopify data

function updateProductsWithShopifyData(videoData, shopifyData) {

  videoData.products.forEach((product, index) => {

    const variant = shopifyData.variants && shopifyData.variants[index] 

      ? shopifyData.variants[index] 

      : shopifyData;

    product.productName = variant.title ? `${shopifyData.title} - ${variant.title}` : shopifyData.title;

    product.price = variant.price || shopifyData.price;

    product.comparePrice = variant.comparePrice || shopifyData.comparePrice;

    product.available = variant.available !== undefined ? variant.available : shopifyData.available;

    product.stockLevel = variant.inventory;

    // Generate the correct product URL using the helper function

    const productUrl = generateProductUrl(shopifyData.handle, variant.id);

    product.onClick.args.url = productUrl;

    if (product.hotspots) {

      product.hotspots.forEach(hotspot => {

        hotspot.clickUrl = productUrl;

      });

    }

  });

}Code language: JavaScript (javascript)

To connect your demo to your actual store, update these four configuration values, replace your:

  • Cloudinary cloud name. Update DEMO_CONFIG.cloudName with your actual Cloudinary cloud name.
  • Shopify domain. Update SHOPIFY_CONFIG.storeDomain with your actual store domain.
  • Video public ID. Update the key in VIDEO_PRODUCTS with your actual video public ID.
  • Product handle. Update shopify_handle with your actual Shopify product handle.

You’ve created a shoppable video experience connecting Cloudinary videos with your Shopify products! Your customers can now click directly on products in your videos and be redirected to your store for immediate purchase.

To complement your API integration and streamline your video production workflows, consider using Cloudinary Studio or MediaFlows for automated video processing and management tasks.

Sign up on Cloudinary to connect product videos with real-time Shopify inventory and add interactive shopping experiences.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free